Kotlin协程(二)之——在Android当中应用
简单介绍
初步印象
在 Android Jetpack 中的
lifecycle
、LiveData
和ViewModel
已经集成了快速使用协程的方法
基本概念
viewModelScope
是预定义的CoroutineScope
,包含在ViewModel
KTX 扩展中
CoroutineScope
会跟踪它使用launch
或async
创建的所有协程,与调度器不同,CoroutineScope
不运行协程
Job
是协程的句柄,使用launch
或async
创建的每个协程都会返回一个Job
实例,该实例是相应协程的唯一标识并管理其生命周期
简单使用
基本使用
-
这里借用官方的改造Java
Executors
线程池的工程例程进行梳理,所以这里也印证了Kotlin协程(一)——之语言特性中所说的,协程本质还是线程框架 -
通过
viewModelScope.launch
切换作用域,增加延时函数fun updateTaps() { viewModelScope.launch { _tapCount++ delay(1_000) _taps.postValue("$_tapCount taps") } } 复制代码
模拟网络请求
- kotlin 协程和
Retroft
的结合使用将在下一篇文章演示,这里参照例子模拟本地请求 - 同步代码形式发起异步请求,通过
try...catch
捕获异常private fun refreshTitle() { viewModelScope.launch { try { _spinner.value = true repository.refreshTitle() } catch (error: TitleRefreshError) { _snackBar.value = error.message } finally { _spinner.value = false } } } 复制代码
- 在
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) } } } 复制代码
- 编写
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) } } } 复制代码
- 通过
Hilt
注册service
@InstallIn(SingletonComponent::class) @Module object NetworkModule { ... @Provides @Singleton fun provideMainService(): MainService { return MainService.create() } } 复制代码
优化网络请求
Retrofit
支持挂载函数,可以直接返回数据类型@GET("next_title.json") suspend fun fetchNextTitle(): String 复制代码
- 在
Repository
当中不需要手动切换线程了,挂载函数自动切换suspend fun refreshTitle() { try { val result = network.fetchNextTitle() wordDao.insert(Word(result)) } catch (cause: Throwable) { throw TitleRefreshError("Unable to refresh title", null) } } 复制代码
- 高阶函数封装具体的请求
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 } } 复制代码
协程测试
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") } 复制代码
- 网络测试
- 和
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)
相关链接
参考资料
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END