Android:玩转网络请求架构 Retrofit+Kotlin协程简单使用(MVVM架构模式)

文章目录

  • MVVM架构模式(Jetpack)
    • 一、MVP带来的问题
    • 二、MVVM架构说明
    • 三、项目代码实现
      • 1. 引入lib,APP模块:build.gradle
      • 2. ViewModel扩展类
      • 3. LoadState.kt
      • 4. Activity基类
      • 5. ViewModel类
      • 6. Repository.kt
      • 7. ApiFactory.kt
      • 8. ApiService.kt
      • 9. MyActivity.kt

MVP架构请求请看: 上一篇

MVVM架构模式(Jetpack)

一、MVP带来的问题

在公司APP项目研发中由于使用了MVP架构,随着业务需求的增长,造成了类文件和接口文件过多,增大了包体积;而且还存在内存泄露问题,当用户关闭了View层,如果此时Model层还在进行耗时操作,因为Presenter层也持有View层的引用,所以垃圾回收器无法回收View层,这样就导致了内存泄露。虽然有一些方案,可以解决,比如,在onDestroy()方法做回收Presenter操作,或者利用弱引用来解决。但这些足以表明MVP架构确实存在问题。为此我找到了改进方案,MVVM架构可以从根本上消除这些问题。

二、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整体架构。

三、项目代码实现

代码中的注释较多,在此不再做过多的分析。如有疑问,欢迎留言‘骚扰’!

1. 引入lib,APP模块:build.gradle

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}"
    }

2. ViewModel扩展类

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()

3. LoadState.kt

/**
 * 加载状态
 * @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)
}

4. Activity基类

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() }
    }
}

5. ViewModel类

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()
    })
}

6. Repository.kt

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)
    }
}

7. ApiFactory.kt

/**
 * 接口请求工厂
 * @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()
}

8. ApiService.kt

/**
 * 网络服务接口(协程)
 * @author ssq
 * @JvmSuppressWildcards 用来注解类和方法,使得被标记元素的泛型参数不会被编译成通配符?
 */
@JvmSuppressWildcards
interface ApiService {

    /**
     * 异步请求数据
     */
    @FormUrlEncoded
    @POST("app/my")
    suspend fun getMyData(@FieldMap map: Map<String, Any>): MyData
}

9. MyActivity.kt

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源码

如有疑问,欢迎留言!

你可能感兴趣的:(Android进阶)