Android 上的 Kotlin 协程

协程是一种并发设计模式,您可以在 Android 平台上使用它来简化异步执行的代码。协程是在版本 1.3 中添加到 Kotlin 的,它基于来自其他语言的既定概念。

在 Android 上,协程有助于管理长时间运行的任务,如果管理不当,这些任务可能会阻塞主线程并导致应用无响应。使用协程的专业开发者中有超过 50% 的人反映使用协程提高了工作效率。本主题介绍如何使用 Kotlin 协程解决以下问题,从而让您能够编写出更清晰、更简洁的应用代码。

特点

协程是我们在 Android 上进行异步编程的推荐解决方案。值得关注的特点包括:

  • 轻量:您可以在单个线程上运行多个协程,因为协程支持挂起,不会使正在运行协程的线程阻塞。挂起比阻塞节省内存,且支持多个并行操作。
  • 内存泄漏更少:使用结构化并发机制在一个作用域内执行多项操作。
  • 内置取消支持取消操作会自动在运行中的整个协程层次结构内传播。
  • Jetpack 集成:许多 Jetpack 库都包含提供全面协程支持的扩展。某些库还提供自己的协程作用域,可供您用于结构化并发。

MVVM开发模式简述

View:Activity/Fragment

ViewModel:Jetpack ViewModel & Jetpack LiveData

Model:Repository仓库,包含本地持久性数据和服务端的数据

以下是Google Android官网对LiveData和ViewModel的定义

LiveData 是一种可观察的数据存储器类。与常规的可观察类不同,LiveData 具有生命周期感知能力,意指它遵循其他应用组件(如 Activity、Fragment 或 Service)的生命周期。这种感知能力可确保 LiveData 仅更新处于活跃生命周期状态的应用组件观察者。

ViewModel 类旨在以注重生命周期的方式存储和管理界面相关的数据。ViewModel 类让数据可在发生屏幕旋转等配置更改后继续留存。

1、项目中使用到的库

// Kotlin
    implementation "androidx.activity:activity-ktx:1.2.0"
    implementation 'androidx.fragment:fragment-ktx:1.2.5'

    //协程
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1'
    
    //retrofit
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'

2、创建retrofit和okhttpclient的静态类

object RetrofitClient {

    private const val BASE_URL = "https://wanandroid.com/"

    private var retrofit: Retrofit? = null

    val service: HttpService by lazy {
        getRetrofit().create(HttpService::class.java)
    }

    private fun getRetrofit(): Retrofit {
        if (retrofit == null) {
            retrofit = Retrofit.Builder()
                .baseUrl(BASE_URL)
                .client(getOkHttpClent())
                .addConverterFactory(GsonConverterFactory.create())
                //.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build()
        }
        return retrofit!!
    }


    private fun getOkHttpClent(): OkHttpClient {
        val builder = OkHttpClient().newBuilder()

        val cacheFile = File(MyApplication.instance.cacheDir, "cache")
        val cache = Cache(cacheFile, 1024 * 1024 * 50)// 50M 的缓存大小

        builder.run {
            cache(cache)
            connectTimeout(60, TimeUnit.SECONDS)
            readTimeout(60, TimeUnit.SECONDS)
            writeTimeout(60, TimeUnit.SECONDS)
            retryOnConnectionFailure(true)//错误重连
        }

        return builder.build()
    }

}

3、新建接收数据的基类

//接受数据的基类
data class ResponseData<out T>(

    val errorCode: Int,
    val errorMsg: String,
    val data: T
)

//获取banner为例
data class Banner(
    val desc: String,
    val id: Int,
    val imagePath: String,
    val isVisible: Int,
    val order: Int,
    val title: String,
    val type: Int,
    val url: String
)

4、新建基类中间层BaseRepository,统一处理请求结果

open class BaseRepository {

    suspend fun <T : Any> request(call: suspend () -> ResponseData<T>): ResponseData<T> {
        return withContext(Dispatchers.IO) {
            call.invoke()
        }.apply {
            LogUtil.e("接口返回数据---------->,${this}")
            when (errorCode) {
                0, 200 -> this
                100, 401 -> ""
                403 -> ""
                404 -> ""
                500 -> ""
                else -> ""

            }
        }
    }

}

5、新建ViewModel的基类BaseViewModel

open class BaseViewModel : ViewModel(), LifecycleObserver {


    //运行在UI线程的协程
    fun launchUI(block: suspend CoroutineScope.() -> Unit) = viewModelScope.launch {
        try {
            withTimeout(15 * 1000) {
                block()
            }
        } catch (e: Exception) {
            //此处接收到BaseRepository里的request抛出的异常
            //根据业务逻辑自行处理代码...
          
        }
    }

    
}

6、新建BaseActivity,做一些简单的封装

abstract class BaseActivity<T : ViewBinding, VM : ViewModel> : AppCompatActivity() {

    lateinit var binding: T
    protected lateinit var viewModel: VM

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = getViewBinding()
        providerVMClass()?.let {
            viewModel = ViewModelProvider(this).get(it)
        }
        setContentView(binding.root)
        initView()
        setListener()

    }

    abstract fun providerVMClass(): Class<VM>?

    protected abstract fun getViewBinding(): T

    abstract fun initView()

    abstract fun setListener()
}

7、以请求banner模块为例,撸一遍代码

interface HttpService {


    @GET("banner/json")
    suspend fun getBanner(): ResponseData<List<Banner>>


}

class BannerRepository : BaseRepository() {

    suspend fun getBanner(): ResponseData<List<Banner>> = request {
        RetrofitClient.service.getBanner()
    }


}

class BannerViewModel : BaseViewModel() {

    private val repository by lazy {
        BannerRepository()
    }

    private val bannerData by lazy {
        MutableLiveData<List<Banner>>()
    }

    fun getBanner(): LiveData<List<Banner>> {

        launchUI {
            val result = repository.getBanner()
            bannerData.value = result.data
        }
      
        return bannerData
    }
}

class BannerActivity : BaseActivity<ActivityBannerBinding, BannerViewModel>() {


    override fun getViewBinding() = ActivityBannerBinding.inflate(layoutInflater)

    override fun initView() {
        viewModel.getBanner().observe(this, { it ->
            it.forEach {
                LogUtil.e(it.imagePath)
            }
        })
    }

    override fun setListener() {
    }


    override fun providerVMClass() = BannerViewModel::class.java
}

8、一个retrofit配合kotlin 协程的MVVM模式的网络请求框架搭建好了