IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 协程-Android端使用场景入门 -> 正文阅读

[移动开发]协程-Android端使用场景入门

前言

我们在学习一些新技术的时候,首先会关注的他的应用场景以及一些使用有点,满足我们的需求后在项目中使用,然后再研究底层的实现原理及本质,在遇到问题的时候能够快速解决。因此对协程,我们的首要目标还是熟练使用。

协程对于Java开发人员来说相对陌生,Java语言本身没协程概念。Kotlin从版本1.3中才引入进来的。官方解释协程一种并发设计模式,使用它来简化异步编程代码,用同步的编码方式实现异步的效果。下面通过一些示例来说明协程的使用。

协程使用

添加依赖项

    // 添加kotlinx-coroutines-android依赖会自动引入kotlinx-coroutines-core-jvm
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1'

一、在Fragment或者Activity中使用协程

在业务的开发中,我们或多或少会在UI控制器中执行一些耗时或者阻塞主线程的操作,以前大多都是自己写线程或者使用AsyncTask来实现。

下面通过读取文件md5值来演示协程在Fragment或者Activity中的使用。

在APK的下载安装过程中,下载完成后一般我们都会进行MD5校验,保证文件的完整性,然后再进行安装,如果apk的比较小,即使在主线程进行MD5比对,也没有太大的影响,如果APK比较大,在主线程进行对比的话就会有明显的卡顿现象。下面结合协程来实现该需求。

1.1 使用lifecycle扩展库来创建协程

为了避免内存泄漏,我们需要考虑在UI控制的生命周期内使用协程,生命周期结束后结束协程。为此,依赖官网封装好的lifecycle扩展库:

    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0-alpha03'

下面是校验MD5代码:

    private fun checkApkMD5(fileMD5: String) {
        val file = File("")
        lifecycleScope.launch {
            val md5 = withContext(Dispatchers.IO) {
                getFileMd5(file)
            }
            if (fileMD5 == md5){
                //install apk
            }
        }
    }
    
    private fun getFileMd5(file: File): String? {
    
        ......
        return null
    }

上面的代码演示了利用协程来完成了文件的md5读取,下面分析一下checkApkMD5函数中的协程相关代码:

  • 我们知道协程都必须在一个作用域内(CoroutineScope)运行,一个CoroutineScope管理一个或者多个相关的协程,因此我们需要创建一个CoroutineScope。这里我们使用lifecycleScope
  • lifecycleScope是LifecycleOwner的扩展属性LifecycleCoroutineScope,也就是定义了一个协程作用域,内部会在LifeCycle生命周期结束后自动取消协程。
  • launch 是一个函数,用户创建协程并将函数主体分派给相应的调度程序。
  • lifecycleScope的默认调度程序Dispatchers.Main
  • 通过withContext将文件MD5的读取操作移至I/O线程。

最后checkApkMD5函数按以下方式执行:

  1. 在主线程中运行checkApkMD5函数
  2. 通过lifecycleScope launch创建一个新的协程,然后主线中执行协程的函数主体
  3. 运行到withCotext()块的时候,会挂起协程
  4. 在withCotext()块中,将文件的MD5读取操作移至I/O线程
  5. withCotext块结束运行后,协程执行恢复操作,回到主线程继续执行,完成MD5的校验以及apk的安装

通过上面的代码以及执行过程分析,我们看到,文件的MD5读取放在I/O线程,读取完成后又恢复到主线程。对比Java语言的开发,这里明显的减少异步的回调代码,看起来就是同步的代码风格完成了异步的执行流程。因此在Android中使用协程的核心竞争力就是:简化异步并发代码方式,用同步的方式写出了异步代码

1.2 自定义CoroutineScope来创建协程

lifecycleScope是Lifecycle的作用域,当然我们创建自己的CoroutineScope,如下:

    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
    private fun checkApkMD5(fileMD5: String) {
        val file = File("")
        scope.launch {
            val md5 = withContext(Dispatchers.IO) {
                println("threadName:"+Thread.currentThread().name)
                getFileMd5(file)
            }
            println("threadName:"+Thread.currentThread().name)
            if (fileMD5 == md5) {
                //install apk
            }
        }
    }

    //UI控制器生命周期结束的时候取消相关协程
    override fun onDestroy() {
        super.onDestroy()
        clearUp()
    }

    private fun clearUp() {
        scope.cancel()
    }

自定义CoroutineScope和使用lifecycleScope的功能一样,只是自定义的CoroutineScope我们需要手动在生命结束时取消作用域。而lifecycleScope系统已经帮我们做好了封装。因此单从方面使用的角度来讲,还是建议使用lifecycleScope。

二、网络请求中使用协程

我们平时开发App,一般遵循谷歌推荐的应该用架构指南,ui+viewModel + repository + remoteData,其中remoteData利用retrofit实现。

目前大多数接口返回的都是json格式的数据,因此先:

2.1 定义一个接收对象

private const val CODE_SUCCESS = 0
class ApiResponse<T> {
    var code: Int = -1
    var message: String? = null
    var data: T? = null

    fun isSuccess(): Boolean {
        return code == CODE_SUCCESS
    }
}

2.2、定义Retrofit接口API

interface PersonApi {

    @GET("/user/userinfo")
    suspend fun loadUser(@Query("userId") userId: String): ApiResponse<UserInfo>
}

2.3、在repository中获取数据
获取数据主要做线程的切换操作,代码如下:

class UserRepository(private val personApi: PersonApi) {

    suspend fun loadUserInfo(userId: String): ApiResponse<UserInfo> {
        return withContext(Dispatchers.IO) {
            personApi.loadUser(userId)
        }
    }
}

这里personApi.loadUser需要在I/O线程中进行操作,我们通过withContext()将其移至I/O操作线程,其中withContext是挂起函数,需要在协程或者挂起函数中执行,因此需要将loadUserInfo定义为挂起函数。

2.4、在viewModel中通过repository获取数据

获取数据主要通过以下代码实现:

class UserViewModel(private val userRepository: UserRepository) : ViewModel() {
    private val _user = MutableLiveData<Resource<UserInfo>>()
    val user = _user
    //获取用户信息
    fun loadUser(userId: String) {
        viewModelScope.launch {
            try {
                val apiResponse = userRepository.loadUserInfo(userId)
                if (apiResponse.isSuccess()) {
                    user.value = Resource.Success(apiResponse.data)
                } else {
                    user.value = Resource.Error(apiResponse.message)
                }
            } catch (ex: Exception) {
                ex.printStackTrace()
                user.value = Resource.Error(ex.message)
            }
        }
    }
}

2.5、分析执行流程

下面我们分析loadUser函数的执行过程:

  1. 通过viewModelScope的launch创建一个新的协程,在主线程上发出网络请求,然后该协程开始执行。其中viewModelScope中的CoroutineContext会在viewModel的销毁时执行cancel()操作。
  2. 在协程内,执行userRepository.loadUserInfo()会挂起协程,直至loadUserInfo()中的withContext代码块运行结束
  3. withContext代码块运行结束后,loadUser()中的协程在主线程上恢复执行操作,并返回网络请求的结果

协程在网络请求中的使用大致流程如上面代码所示,但是到了具体的项目中需要根据实际需求进一步调整。

注意: 上面的网络请求示例只是为了展示协程的使用,正在的项目开发中还需要进一步的封装,减少不必要的样板代码。后续结合Flow一起来使用,更能发挥其价值。

三、在App进程生命周期内使用CoroutineScope

在我们App的开发中,也许会出现这种业务场景,用户退出当前UI后我们需要做一些操作,比如记录日志,向服务器发送数据。这种场景下使用viewModelScope或者lifecycleScope就不合适,因为这个两个做用户会在对应的UI生命周期结束后取消协程。下面通过一个具体的案例是说明这种业务场景中协程的使用。

3.1 需求

当前UI有一个点赞按钮,用户点击该按钮有点赞获取取消点赞两种功能,通常情况下用户点击一次我们提交一次数据。但是用户可以不停的点击,这种场景下服务端就会要求我们前端在用户离开UI后再提交点赞数据,从而减少对服务端的压力。

3.2 具体实现

3.2.1 定义CoroutineScope

为了实现上面的需求,我们定义一个Application级别的CoroutineScope:

 class MyApplication : Application() {
    companion object {
        //整个APP的协程作用域,生命周期跟随该进程,不同于viewModelScope或者lifecycleScope
        val applicationScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
    }

}

提示: 为了更方便我们定制CoroutineScope,建议我们手动定义CoroutineScope,而不使用GlobalScope

3.2.2 实现UI对应的ViewModel

class MyViewModel(
    private val repository: NyRepository,
    private val applicationScope: CoroutineScope
) : ViewModel() {

    ...省略其他业务代码...
    ......
    
    
    fun reportData() {
        applicationScope.launch {
            repository.reportData().collect { }
        }
    }

}

3.2.3 其他模块的实现

其他模块Repository以及Retrofit的定义可以参考上面协程在网络中的使用讲解。这里的重点是演示如何使用applicationScope。

3.2.4 在UI控制器(Activity或者Fragment)中使用

上述的的需求场景我们通常在onDestory()中调用reportData,如下:

    override fun onDestroy() {
        myViewModel.reportData()
        super.onDestroy()
    }
    

四、取消单个协程

一般开发中,大部分业务场景使用viewModelScope或者lifeCycleScope来启动协程,并由他们自动控制所启动协程的生命周期。但是有时也需要单独管理我们启动的协程,比如登录的过程中弹出了一个Loading框,用户按了返回按钮取消了登录操作,那么我们就需要取消该协程。否则服务端返回正确的数据后会执行我们正常的业务流程。那么使用协程该如何解决这种场景呢?可参考下面的方式进行控制:

private var loginJob: Job? = null

private fun login(){
    //需要loginViewModel.login()返回一个协程的句柄
    loginJob = loginViewModel.login()
}

//取消登录协程的后续操作
private fun cancelLogin(){
    loginJob?.cancel()
}

LoginViewModel参考示例

class LoginViewModel(private val loginRepository: LoginRepository) : ViewModel() {
    
    fun login():Job{
        return viewModelScope.launch{
            loginRepository.login()
        }
    }
}

最后:

  1. 协程帮我们简化了异步并发代码的编写方式,可用同步的方式写异步代码
  2. 从上面的使用示例可以看出,使用协程方便了Android上的线程切换操作,代码层面减少了很多回调函数。

通过上面的应该场景,了解了协程的基本使用招式,但是背后的实现逻辑是什么呢?后续我们进一步探讨。

参考:

Android官网的协程指南

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-09-24 10:40:21  更:2021-09-24 10:43:13 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 20:58:39-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码