阅读 274

Kotlin协程(二)之——在Android当中应用

Kotlin协程(二)之——在Android当中应用

简单介绍

初步印象

在 Android Jetpack 中的 lifecycleLiveDataViewModel 已经集成了快速使用协程的方法

基本概念

viewModelScope 是预定义的 CoroutineScope,包含在 ViewModel KTX 扩展中

CoroutineScope 会跟踪它使用 launchasync 创建的所有协程,与调度器不同,CoroutineScope 不运行协程

Job是协程的句柄,使用 launchasync 创建的每个协程都会返回一个 Job 实例,该实例是相应协程的唯一标识并管理其生命周期

简单使用

基本使用

  1. 这里借用官方的改造Java Executors线程池的工程例程进行梳理,所以这里也印证了Kotlin协程(一)——之语言特性中所说的,协程本质还是线程框架

  2. 通过viewModelScope.launch 切换作用域,增加延时函数

    fun updateTaps() {
        viewModelScope.launch {
            _tapCount++
            delay(1_000)
            _taps.postValue("$_tapCount taps")
        }
    }
    复制代码

模拟网络请求

  1. kotlin 协程和 Retroft 的结合使用将在下一篇文章演示,这里参照例子模拟本地请求
  2. 同步代码形式发起异步请求,通过try...catch捕获异常
    private fun refreshTitle() {
        viewModelScope.launch {
            try {
                _spinner.value = true
                repository.refreshTitle()
            } catch (error: TitleRefreshError) {
                _snackBar.value = error.message
            } finally {
                _spinner.value = false
            }
        }
    }
    复制代码
  3. Repository当中触发网络请求
    suspend fun refreshTitle() {
        withContext(Dispatchers.IO) {
            val result = try {
                network.fetchNextTitle().execute()
            } catch (cause: Throwable) {
                throw TitleRefreshError("Unable to refresh title", cause)
            }
    
            if (result.isSuccessful) {
                wordDao.insert(Word(result.body()!!))
            } else {
                throw TitleRefreshError("Unable to refresh title", null)
            }
        }
    }
    复制代码
  4. 编写service
    // 这里通过拦截器用本地数据模拟网络请求
    interface MainService {
    
        @GET("next_title.json")
        fun fetchNextTitle(): Call<String>
    
        companion object {
            private const val BASE_URL = "http://localhost/"
    
            fun create(): MainService {
                val logger = HttpLoggingInterceptor()
                logger.level = HttpLoggingInterceptor.Level.BASIC
    
                val client = OkHttpClient.Builder()
                    .addInterceptor(logger)
                    .addInterceptor(SkipNetworkInterceptor())
                    .build()
                return Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .client(client)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build()
                    .create(MainService::class.java)
            }
        }
    }
    复制代码
  5. 通过Hilt 注册service
    @InstallIn(SingletonComponent::class)
    @Module
    object NetworkModule {
    	...
        @Provides
        @Singleton
        fun provideMainService(): MainService {
            return MainService.create()
        }
    }
    复制代码

优化网络请求

  1. Retrofit支持挂载函数,可以直接返回数据类型
    @GET("next_title.json")
    suspend fun fetchNextTitle(): String
    复制代码
  2. Repository 当中不需要手动切换线程了,挂载函数自动切换
    suspend fun refreshTitle() {
        try {
            val result = network.fetchNextTitle()
            wordDao.insert(Word(result))
        } catch (cause: Throwable) {
            throw TitleRefreshError("Unable to refresh title", null)
        }
    }
    复制代码
  3. 高阶函数封装具体的请求
    private fun refreshTitle() {
        launchDataLoad {
            repository.refreshTitle()
        }
    }
    
    private fun launchDataLoad(block: suspend () -> Unit): Job =
        viewModelScope.launch {
            try {
                _spinner.value = true
                block()
            } catch (error: TitleRefreshError) {
                _snackBar.value = error.message
            } finally {
                _spinner.value = false
            }
        }
    复制代码

协程测试

  1. updateTaps() 方法测试
    // coroutineRule 是 TestWatcher的实现类,指定协程作用域
    @Test
    fun testDefaultValues() = coroutineRule.runBlockingTest {
        viewModel.updateTaps()
        Truth.assertThat(viewModel.taps.getOrAwaitValue()).isEqualTo("0 taps")
        coroutineRule.testDispatcher.advanceTimeBy(1_000)
        Truth.assertThat(viewModel.taps.getOrAwaitValue()).isEqualTo("1 taps")
        viewModel.updateTaps()
        coroutineRule.testDispatcher.advanceTimeBy(1_000)
        Truth.assertThat(viewModel.taps.getOrAwaitValue()).isEqualTo("2 taps")
    }
    复制代码
  2. 网络测试
  • Retroft一起梳理

相关知识点

知识点一: Kotlin协程使用调度器确定线程 ,调用 withContext() 来创建一个在指定线程中运行的代码块

  • Dispatchers.Main - 使用此调度器可在 Android 主线程上运行协程。
  • Dispatchers.IO - 此调度器经过了专门优化,适合在主线程之外执行磁盘或网络 I/O
  • Dispatchers.Default - 此调度器经过了专门优化,适合在主线程之外执行占用大量 CPU 资源的工作,默认调度器

知识点二:进程和线程

  • 当应用组件启动且该应用未运行任何其他组件时,Android 系统会使用单个执行线程为应用启动新的 Linux 进程
  • 默认情况下,同一应用的所有组件均在相同的进程中运行,且大多数应用都不应改变这一点。组件元素均支持 android:process 属性,此属性可指定该组件应在哪个进程中运行
  • 启动应用时,系统会为该应用创建一个称为“main”(主线程)的执行线程
  • Android 提供了几种途径,以便从其他线程访问界面线程
    • Activity.runOnUiThread(Runnable)
    • View.post(Runnable)
    • View.postDelayed(Runnable, long)

相关链接

参考资料

文章分类
Android
文章标签