前言
时间: 23/10/12
AndroidStudio版本: Giraffe 2022.3.1 JDK:17 开发语言: Kotlin
Gradle版本: 8.0 Gradle plugin Version: 8.1.1
概述
上一节,讲述了什么是 mvvm 代码框架,并且通过使用之前学习的 jetpack 组件编写了一个 mvvm 框架的小 demo。
但是上一节只完成了登录的功能,哪这节就来完善一下这个 demo。
功能
密码加密
涉及到网络请求和用户数据,我们一般都需要对用户的一些信息进行加密处理,这是一个软件基本的要求。
目前来讲,各种加密算法都很常用。总的来说,就分为对称和非对称加密。简单讲就是可解密和不可解的两种加密算法。
我这里使用的是使用AES+固定密钥对用户的密码进行加密。
private val SECRET_KEY = "Jensen0906xxm423"
private val AES = "AES"
private val CHARSET = Charset.forName("utf-8")
private val CIPHER_ALGORITHM = "AES/CBC/PKCS7Padding"
fun String?.AESEncode(): String {
if (isNullOrEmpty()) {
return ""
}
val cipher = Cipher.getInstance(CIPHER_ALGORITHM)
val byteArray = SECRET_KEY.toByteArray(CHARSET)
val keySpec = SecretKeySpec(byteArray, AES)
val iv = IvParameterSpec(byteArray)
return try {
cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv)
val encrypted = cipher.doFinal(toByteArray(CHARSET))
Base64.encodeToString(encrypted, Base64.NO_WRAP)
} catch (e: Exception) {
e.printStackTrace()
""
}
}
加密之后会生成密文,密文也是一个字符串,只是直接看看不出任何意义。我们一般是用这个密文进行和服务器数据交互的,正常来讲存储到数据库中的也会是密文,我们在登录时直接通过对比密文,也就是字符串就能实现登录功能。
这里还用到了 Kotlin 的特性 —— 顶层函数 以及 扩展函数
-
顶层函数
可以看出上面的代码,包括变量和方法,都不存在类包括它。我们在 Java 中正常写方法或函数,是需要编写在类中的,方便其它方法或类调用。但是 Kotlin 可以像上面的代码一样写函数,而这个就是 顶层函数。
-
扩展函数
这是 Kotlin 的另一个特性,上面的 AESEncode() 方法前面有一个
String?.,这个就是 扩展函数 的特点,表示这个函数是扩展给 String 用的。总所周知,Java 中的 String 类是 final 修饰的,也就是说我们并不能重写 String 类。如果我们需要给 String 添加一些自定义逻辑的方法,就需要另外写一个工具类型的方法。并且这个方法需要传入待改动的参数 string1,返回改动后的 string2。如下public String AESEncode(String resouse) { String result; result = "resouse do AESEncode"; return result; } //调用 String result = AESEncode(password);而使用了 Kotlin 的扩展函数后,我们只需要用以下方法调用即可。
password.AESEncode()Java 的这种调用方式,看起来就不符合调用关系——调用者在方法里头。而 Kotlin 则是能清晰看出 password调用了AESEncode()这个方法。
使用
在之前编写好的代码的基础上,只需要修改 viewmodel 中请求的数据 user 即可。
fun login(user: User?) {
if (user == null || user.username.isNullOrEmpty() || user.password.isNullOrEmpty()) {
Toast.warning(appContext, appContext.getString(R.string.username_or_password_empty))
return
}
user.password = user.password.AESEncode() // 添加
val requestBody =
RequestBody.create(MediaType.parse("application/json;"), Gson().toJson(user))
viewModelScope.launch {
repository.login(_user, requestBody)
}
}
运行效果
可以看到,虽然登录成功了,但是在登录的时候密码变成了密文了。这是因为我们实现了 databinding 数据双向绑定,那么更改 user 的 username 或者 password 的值,都会实时更新到 UI 上。
修复bug也很简单,有两种方式。一是不使用双向绑定( 不推荐,因为代码可能涉及较多 );二是新建一个 User 对象,存放更改后的密码,并用作网络请求的数据。
同样的使用扩展函数。
fun User.makePassowrdEncode(): User {
val user = User()
val data = this
return user.apply {
id = data.id
username = data.username
password = data.password.AESEncode()
userStatus = data.userStatus
}
}
//UserViewModel.kt
val requestBody =
RequestBody.create(MediaType.parse("application/json;"), Gson().toJson(user.makePassowrdEncode()))
viewModelScope.launch {
repository.login(_user, requestBody)
}
这样就不会出现 密文显示出来的情况了。
用户注册
其实功能实现和用户登录一样,从数据层往 UI 层一步一步走就行。直接贴代码,就不多作细说了,如果有疑问,可以看同系列的上一篇文章。
//interface NetWorkApi
@POST("user/register")
suspend fun register(@Body body: RequestBody): ApiResult<User?>
//class UserRepository
suspend fun register(user: MutableLiveData<User?>, requestBody: RequestBody) {
execute({ api.register(requestBody) }, user)
}
//class UserViewModel
fun register(user: User?, pass2: String?) {
if (user == null || user.username.isNullOrEmpty() || user.password.isNullOrEmpty()) {
Toast.warning(appContext, appContext.getString(R.string.username_or_password_empty))
return
} else if (user.password != pass2) {
Toast.warning(appContext, appContext.getString(R.string.twice_password_not_same))
return
}
val requestBody =
RequestBody.create(MediaType.parse("application/json;"), Gson().toJson(user.makePassowrdEncode()))
viewModelScope.launch {
repository.register(_user, requestBody)
}
}
//class RegisterActivity
private lateinit var userViewModel: UserViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
userViewModel = ViewModelProvider(this)[UserViewModel::class.java]
val user = User()
binding.user = user
binding.btnRegister.setOnClickListener {
Log.d(TAG, user.toString())
userViewModel.register(user, binding.etPasswordAgain.text?.toString())
}
userViewModel.userLiveData.observe(this) {
Toast.success(applicationContext, "register success! --${user.username}--")
val intent = Intent(this@RegisterActivity, LoginActivity::class.java)
intent.putExtra(REGISTER_SUCCESS_NAME, it?.username)
// registerForActivityResult的返回,startActivityForResult的替代方法
setResult(TO_REGISTER_FOR_RESULT, intent)
finish()
}
}
布局界面就不贴了,文章末尾有源码,可以自己下载。
运行效果
其它
其实写到这里就已经没什么新鲜的东西可以说了,我相信举一反三不算难事。但是本篇涉及到了一些这个系列之前从未提及的东西。例如 在 MainActivity 中获取到 all books 的数据后使用 recycleview 来显示出来;又或者注册成功后跳转回登录界面,并且注册的用户名已经填充到 UI 等。这些其实和 mvvm 关系不大,都是平常开发经常用到的,所以我这里就不一一细说了。
总结
至此,jetpack 系列的学习就差不多落幕了,虽然还有一些东西还没有讲过,但是都无伤大雅( 除了 kotlin Flow ),因为 jetpack 出来这么久了,网上有很多相关的资料和书籍。后续有时间会补上一些没讲过的细节,可能是出一个 jetpack 进阶系列,也可能就直接整 compose 给大家了。不管怎样,希望这个 jetpack 学习系列能给你带来帮助。
最后我想说的是,开发本质就是 发现问题 - 调查问题 - 解决问题 三者的循环。
有想法、建议或者疑问的可以评论或私信。
万分感谢点赞、收藏 和 关注。