在公司APP项目研发中由于使用了MVP架构,随着业务需求的增长,造成了类文件和接口文件过多,增大了包体积;而且还存在内存泄露问题,当用户关闭了View层,如果此时Model层还在进行耗时操作,因为Presenter层也持有View层的引用,所以垃圾回收器无法回收View层,这样就导致了内存泄露。虽然有一些方案,可以解决,比如,在onDestroy()方法做回收Presenter操作,或者利用弱引用来解决。但这些足以表明MVP架构确实存在问题。为此我找到了改进方案,MVVM架构可以从根本上消除这些问题。
现在kotlin是Android官方第一指定语言,经过三年多的使用,墙裂建议还没有转的小伙伴尽快迁移,真香~
这里我们采用Kotlin的Coroutines(协程),以及Jatpack中的ViewMoudle、LiveData组件来搭建MVVM架构。一般是在Activity/Fragment中调用LiveData的observe()方法和LiveData建立观察绑定关系,当LiveData中的数据发生变化时会通过主线程通知观察者,处在活跃状态的Activity/Fragment会去更新UI,由于ViewModel和LiveData具有生命周期感知能力,在Activity/Fragment销毁时,LiveData会自动清理、释放数据,从而解决了内存泄露问题。
至于为什么没用DataBinding?
一开始进行MVVM架构项目重构时,确实有使用,一段时间后发现,DataBinding可能对Java使用者的确友好,方便,对于Kotlin我觉得没有“kotlin-android-extensions”自动引用布局控件这么香,DataBinding是一个可选项,即使没有,丝毫不会影响我们的MVVM整体架构。
代码中的注释较多,在此不再做过多的分析。如有疑问,欢迎留言‘骚扰’!
dependencies {
// 添加Jetpack中架构组件的依赖,注意viewmodel要添加viewmodel-ktx的依赖
api "androidx.lifecycle:lifecycle-livedata:${rootProject.ext.lifecycle}"
api "androidx.lifecycle:lifecycle-viewmodel-ktx:${rootProject.ext.lifecycle}"
api "androidx.lifecycle:lifecycle-extensions:${rootProject.ext.lifecycle}"
}
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
/**
* ViewModel扩展方法:启动协程
* @param block 协程逻辑
* @param onError 错误回调方法
* @param onComplete 完成回调方法
*/
fun ViewModel.launch(
block: suspend CoroutineScope.() -> Unit,
onError: (e: Throwable) -> Unit = {},
onComplete: () -> Unit = {}
) {
viewModelScope.launch(
CoroutineExceptionHandler { _, throwable ->
run {
// 这里统一处理错误
ExceptionUtil.catchException(throwable)
onError(throwable)
}
}
) {
try {
block.invoke(this)
} finally {
onComplete()
}
}
}
/**
* ViewModel扩展属性:加载状态
*/
val ViewModel.loadState: MutableLiveData<LoadState>
get() = MutableLiveData()
/**
* 加载状态
* @author ssq
* sealed 关键字表示此类仅内部继承
*/
sealed class LoadState(val msg: String) {
/**
* 加载中
*/
class Loading(msg: String = ""): LoadState(msg)
/**
* 成功
*/
class Success(msg: String = ""): LoadState(msg)
/**
* 失败
*/
class Fail(msg: String = ""): LoadState(msg)
}
import android.os.Bundle
import android.widget.Toast
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProviders
/**
* Activity 基类
* MV ViewModel 架构
* @author ssq
*/
abstract class BaseViewModelActivity<VM : ViewModel> : : AppCompatActivity(), CoroutineScope by MainScope() {
protected lateinit var mViewModel: VM
protected abstract fun getLayoutId(): Int
/**
* 提供ViewModel类
*/
protected abstract fun providerVMClass(): Class<VM>?
protected abstract fun initView()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(getLayoutId())
providerVMClass()?.let { mViewModel = ViewModelProviders.of(this).get(it) }
initView()
}
override fun onDestroy() {
cancel()// 取消协程
super.onDestroy()
}
protected open fun showFail(message: String? = null) {
message?.let { Toast.makeText(this, it, Toast.LENGTH_LONG).show() }
}
}
import android.content.Context
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class MyActivityVM: ViewModel() {
val myData = MutableLiveData<MyData>()
/**
* 获取用户信息
*/
fun getData(context: Context?) = launch({
if (!MyUtil.validateLogin()) return@launch
loadState.value = LoadState.Loading()
myData.value = Repository.getMyData(context)
loadState.value = LoadState.Success()
}, {
loadState.value = LoadState.Fail()
})
}
object Repository {
// 接口API服务
private val api by lazy { ApiFactory.createService(ApiConstant.BASE_URL, ApiService::class.java) }
/**
* 预处理数据(错误)
* @param context 跳至登录页的上下文
*/
private suspend fun <T : BaseBean> preprocessingData(baseBean: T, context: Context? = null): T = if (baseBean.code == 0) {// 成功
// 返回数据
baseBean
} else {// 失败
// 验证登录是否过期
validateCode(context, baseBean.code)
// 抛出接口异常 ApiException是自定义的异常类
throw ApiException(baseBean.code, baseBean.message)
}
// HashMap() 是请求的参数
suspend fun getMyInfo(context: Context? = null): MyData = api.getMyData(HashMap())
).let {
preprocessingData(it, context)
}
}
/**
* 接口请求工厂
* @author ssq
*/
object ApiFactory {
// OkHttpClient客户端
private val mClient: OkHttpClient by lazy { newClient() }
/**
* 创建API Service接口实例
*/
fun <T> createService(baseUrl: String, clazz: Class<T>): T = Retrofit.Builder().baseUrl(baseUrl).client(mClient)
.addConverterFactory(GsonConverterFactory.create(Gson.getInstance()))
.build().create(clazz)
/**
* OkHttpClient客户端
*/
private fun newClient(): OkHttpClient = OkHttpClient.Builder().apply {
connectTimeout(30, TimeUnit.SECONDS)// 连接时间:30s超时
readTimeout(10, TimeUnit.SECONDS)// 读取时间:10s超时
writeTimeout(10, TimeUnit.SECONDS)// 写入时间:10s超时
}.build()
}
/**
* 网络服务接口(协程)
* @author ssq
* @JvmSuppressWildcards 用来注解类和方法,使得被标记元素的泛型参数不会被编译成通配符?
*/
@JvmSuppressWildcards
interface ApiService {
/**
* 异步请求数据
*/
@FormUrlEncoded
@POST("app/my")
suspend fun getMyData(@FieldMap map: Map<String, Any>): MyData
}
class MyActivity : BaseViewModelActivity<MyActivityVM>() {
override fun getLayoutId(): Int = R.layout.activity_my
override fun providerVMClass(): Class<MyActivityVM>? = MyActivityVM::class.java
override fun initView() {
super.initView()
// 监听加载状态变化 从而改变页面
mViewModel.loadState.observe(this, Observer { changeState(it) })
// 监听数据变化,从而显示数据在页面
mViewModel.myData.observe(this, Observer { showMyData(it) })
}
/**
* 显示数据到页面
*/
private fun showMyData(data: MyData){
nameTv.text = data.name
}
}
界面布局就不贴了 只有一个TextView。
Demo点这里github源码
如有疑问,欢迎留言!