Kotlin细节文章笔记整理更新进度:
Kotlin系列 - 基础类型结构细节小结(一)
Kotlin系列 - 函数与类相关细节小结(二)
Kotlin系列 - 高阶函数与标准库中的常用函数(三)
Kotlin系列 - 进阶深入泛型协变逆变从java到Kotlin(四)
学习了Kotlin一整个系列了,但是协程这块迟迟没有整理成一篇博文。诶,最近状态有点不对 >_< || 。 但是无论如何,一定要加油!!最后一篇要划上个完美点的句号,撒个漂亮点的花。
关于协程的一个点在这里跟大家先说一下,协程并非什么很深奥的东西,说白了也是在线程上面的产物,并非凭空产生的一个新的概念。官网讲得可能有点高大上了,不过实际上你就当是它帮我们使用了线程池
跟Handler
进行一些自动切换线程的逻辑封装进而形成了这样子的一种API吧~~
添加基本的依赖
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0' 复制代码
官网定义:Global scope is used to launch top-level coroutines which are operating on the whole application lifetime and are not cancelled prematurely. Another use of the global scope is operators running in Dispatchers.Unconfined, which don’t have any job associated with them. Application code usually should use an application-defined CoroutineScope. Using async or launch on the instance of GlobalScope is highly discouraged. 大致意思就是:这个一般被用于顶级的协程,
application
生命周期级别的,不会过早的被取消。应用程序通常应该使用一个应用程序定义的CoroutineScope。使用异步或启动的实例GlobalScope非常气馁(不建议的)。
先来模拟一个场景,在一个ActivityA
调用globalScopeLaunch
,或者globalScopeLaunch
进行耗时操作,类似IO操作或者网络请求等。然后在它还没有返回的时候销毁ActivityA
再跳转到ActivityB
fun globalScopeLaunch(){ GlobalScope.launch(Dispatchers.Main) { delay(5000) Toast.makeText(this@MainActivity,"等待五秒弹出~~~",Toast.LENGTH_LONG).show() } } 复制代码
你会发现,它照样会弹出这个Toast
,但是这样子其实并非我们想要的结果。有些事务我们应该随着组件的生命周期结束而结束。否则一会造成资源的浪费或者内存泄露。(这里有个问题,如果你在生命周期结束的时候手动关闭的话,那就可以避免这种情况。但是这里就涉及到要你自己手动来控制了)
private fun globalScopeLaunch1(){ GlobalScope.launch(Dispatchers.Main) { launch (Dispatchers.Main){ delay(1000) Toast.makeText(this@MainActivity,"等待一秒弹出",Toast.LENGTH_LONG).show() } Toast.makeText(this@MainActivity,"立马弹出~~~",Toast.LENGTH_LONG).show() delay(5000) Toast.makeText(this@MainActivity,"等待五秒弹出~~~",Toast.LENGTH_LONG).show() } } 复制代码
在globalScopeLaunch1
中,立马弹出~~~
->等待一秒弹出
->"等待五秒弹出~~~
。这里之所以会先弹出来立马弹出~~~
这个信息。因为协程中,又开了一个新的协程,新的协程阻塞一秒不关外边协程的事情,外边协程继续执行。
private fun globalScopeLaunch(){ job = GlobalScope.launch(Dispatchers.Main) { launch (Dispatchers.Main){ runBlocking {//加了runBlocking这个协程作用域 delay(1000) Toast.makeText(this@MainActivity,"等待一秒弹出",Toast.LENGTH_LONG).show() } } Toast.makeText(this@MainActivity,"立马弹出~~~",Toast.LENGTH_LONG).show() delay(5000) Toast.makeText(this@MainActivity,"等待五秒弹出~~~",Toast.LENGTH_LONG).show() } } 复制代码
runBlocking
会阻塞导致立马弹出~~~
这个Toast
不会立刻显示出来,而是等了1秒后,再弹出来。
上面的代码可以简化一下
在协程作用域中,可以使用withContext(Dispatchers.Main)
替换launch (Dispatchers.Main)
job = GlobalScope.launch(Dispatchers.Main) { withContext(Dispatchers.Main){} } 复制代码
GlobalScope.launch(Dispatchers.Main)
这里我是分发到主线程Main
上面进行delay
但是并不会造成ANR
,可以简单看一下launch
怎么调用的
public fun CoroutineScope.launch( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit ): Job { val newContext = newCoroutineContext(context) val coroutine = if (start.isLazy) LazyStandaloneCoroutine(newContext, block) else StandaloneCoroutine(newContext, active = true) coroutine.start(start, coroutine, block) return coroutine } 复制代码
第三个参数 : block: suspend CoroutineScope.()
表示使用的协程作用域是CoroutineScope
并不会造成阻塞。这里的阻塞是指协程作用域外的代码阻塞,协程作用域内部还会被阻塞的。
CoroutineScope
是GlobalScope
的父类~
private fun globalScopeAsync(){ GlobalScope.launch(Dispatchers.Main){ val deferred = async(IO) { Thread.sleep(5000) "等待五秒弹出~~~" } Toast.makeText(this@MainActivity,"立马先弹出来~~",Toast.LENGTH_LONG).show()//这句是来验证sync是不会阻塞改async协程外的代码的 val message = deferred.await() Toast.makeText(this@MainActivity,message,Toast.LENGTH_LONG).show() } } 复制代码
async
会异步跑该作用域外层的协程的逻辑,我们可以看到"立马先弹出来~~"
弹出框会先弹出来,再等过五秒在弹出 "等待五秒弹出~~~"
再弹出来。在await
这里会阻塞等待deferred
返回回来再继续接下来的操作。
获取到对应协程的Job
对象,调用cancel()
var job = GlobalScope.launch(Dispatchers.Main) { } job.cancel() //Deferred的对象父类是Job var deferred = GlobalScope.async { } deferred.cancel() 复制代码
上面Global
的官方定义中已经提示我们使用自定义的协程。
MainScope
是kotlin
为我们自定义好的一个协程作用域。
代码定义:
@Suppress("FunctionName") public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main) 复制代码
基本使用:
class MyAndroidActivity { private val scope = MainScope() //使用MainScope并赋予它名字 // val mainScope = MainScope() + CoroutineName(this.javaClass.name) private fun mainScopeLaunch(){ scope.launch {} } override fun onDestroy() { super.onDestroy() scope.cancel() } } 复制代码
可以将这逻辑放到base
类中
//无需定义协程名字的时候 open class BaseCoroutineScopeActivity : AppCompatActivity() , CoroutineScope by MainScope() class MainActivity : BaseCoroutineScopeActivity(){ private fun mainScopeLaunch(){ launch { } } override fun onDestroy() { super.onDestroy() cancel() } } ----------------------------------------------------------------- //定义协程名字的时候 open class BaseCoroutineScopeActivity : AppCompatActivity() { val mainLaunch = MainScope()+ CoroutineName(this.javaClass.simpleName) } class MainActivity : BaseCoroutineScopeActivity(){ private fun mainScopeLaunch(){ mainLaunch.launch { } } override fun onDestroy() { super.onDestroy() mainLaunch.cancel() } } 复制代码
使用该协程首先要导入包
api 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0-rc02' 复制代码
代码定义:
private const val JOB_KEY = "androidx.lifecycle.ViewModelCoroutineScope.JOB_KEY" val ViewModel.viewModelScope: CoroutineScope get() { val scope: CoroutineScope? = this.getTag(JOB_KEY) if (scope != null) { return scope } return setTagIfAbsent(JOB_KEY, CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)) } internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope { override val coroutineContext: CoroutineContext = context override fun close() { coroutineContext.cancel() } } 复制代码
这段代码跟
MainScope
一样,只是外面多了一层CloseableCoroutineScope
的封装,这个是为什么呢?? 我们进去setTagIfAbsent
看一下
<T> T setTagIfAbsent(String key, T newValue) { T previous; synchronized (mBagOfTags) { previous = (T) mBagOfTags.get(key); if (previous == null) { mBagOfTags.put(key, newValue); } } T result = previous == null ? newValue : previous; if (mCleared) { closeWithRuntimeException(result); } return result; } 复制代码
这里可以看出,当mCleared = true
的时候它会自动帮我们关闭掉viewModelScope
,也就是它帮我们处理生命周期的问题了 我们只管使用就可以。
使用:
fun requestAhuInfo() { viewModelScope.launch { } } 复制代码
推荐一下
秉心说TM的 - 如何正确的在 Android 上使用协程 ?
里面有说了这几种kotlin
为我们提供的协程
viewModelScope.launch { var result1 = withContext(Dispatchers.IO) { Log.i(TAG, "result1-1") Log.i(TAG, "result1-2") Thread.sleep(4000) Log.i(TAG, "result1-3") "Hello" } var result2 = withContext(Dispatchers.IO) { Log.i(TAG, "result2-1") Log.i(TAG, "result2-2") Log.i(TAG, "result2-3") "world" } val result = result1 + result2 Log.i(TAG, result) } ------------------------------------------------------------------------------------ 2020-03-23 19:19:40.587 11021-11096/com.ldr.testcoroutines I/MainViewModel: result1-1 2020-03-23 19:19:40.587 11021-11096/com.ldr.testcoroutines I/MainViewModel: result1-2 2020-03-23 19:19:44.592 11021-11096/com.ldr.testcoroutines I/MainViewModel: result1-3 2020-03-23 19:19:44.602 11021-11096/com.ldr.testcoroutines I/MainViewModel: result2-1 2020-03-23 19:19:44.603 11021-11096/com.ldr.testcoroutines I/MainViewModel: result2-2 2020-03-23 19:19:44.603 11021-11096/com.ldr.testcoroutines I/MainViewModel: result2-3 2020-03-23 19:19:44.603 11021-11021/com.ldr.testcoroutines I/MainViewModel: Helloworld 复制代码
viewModelScope.launch { val deferred = async { Thread.sleep(4000) Log.i(TAG, "result1-1") Log.i(TAG, "result1-2") Log.i(TAG, "result1-3") "Hello" } val deferred1 = async { Log.i(TAG, "result2-1") Log.i(TAG, "result2-2") Log.i(TAG, "result2-3") "world" } var str = deferred.await() + deferred1.await() Log.i(TAG, str) } ------------------------------------------------------------------------------------ 2020-03-23 18:52:28.016 9155-9155/com.ldr.testcoroutines I/MainViewModel: result1-1 2020-03-23 18:52:28.016 9155-9155/com.ldr.testcoroutines I/MainViewModel: result1-2 2020-03-23 18:52:28.016 9155-9155/com.ldr.testcoroutines I/MainViewModel: result2-3 2020-03-23 18:52:28.018 9155-9155/com.ldr.testcoroutines I/MainViewModel: result2-1 2020-03-23 18:52:28.018 9155-9155/com.ldr.testcoroutines I/MainViewModel: result2-2 2020-03-23 18:52:28.018 9155-9155/com.ldr.testcoroutines I/MainViewModel: result2-3 2020-03-23 18:52:28.018 9155-9155/com.ldr.testcoroutines I/MainViewModel: Helloworld 复制代码
viewModelScope.launch { launch(Dispatchers.IO) { Log.i(TAG, "result1-1") Log.i(TAG, "result1-2") Thread.sleep(4000) Log.i(TAG, "result1-3") } launch(Dispatchers.IO) { Log.i(TAG, "result2-1") Log.i(TAG, "result2-2") Log.i(TAG, "result2-3") } } --------------------------------------------------------------------------------- 2020-03-23 19:21:29.781 11021-11128/com.ldr.testcoroutines I/MainViewModel: result1-1 2020-03-23 19:21:29.781 11021-11128/com.ldr.testcoroutines I/MainViewModel: result1-2 2020-03-23 19:21:29.782 11021-11096/com.ldr.testcoroutines I/MainViewModel: result2-1 2020-03-23 19:21:29.782 11021-11096/com.ldr.testcoroutines I/MainViewModel: result2-2 2020-03-23 19:21:29.782 11021-11096/com.ldr.testcoroutines I/MainViewModel: result2-3 2020-03-23 19:21:33.782 11021-11128/com.ldr.testcoroutines I/MainViewModel: result1-3 复制代码
val handler = CoroutineExceptionHandler { _, exception -> println("Caught $exception") } fun catchFun(): Unit { viewModelScope.launch(handler) { throw IOException() } } fun catch2Fun(): Unit { viewModelScope.launch(handler) { launch(Dispatchers.IO) { withContext(Dispatchers.Main){ throw IOException() } } } } ---------------------------------------------------------------------------------- 2020-03-23 19:57:21.205 12038-12096/com.ldr.testcoroutines I/System.out: Caught java.io.IOException 2020-03-23 19:59:23.221 12038-12096/com.ldr.testcoroutines I/System.out: Caught java.io.IOException 复制代码
经过上面的测试,可以知道CoroutineExceptionHandler
这种方法可以将多层嵌套下的异常也捕获到。
//错误的写法 协程外部是捕获不到异常的 fun catch1Fun(): Unit { try { viewModelScope.launch(Dispatchers.Main) { throw IOException() } }catch (e:Exception){ Log.i(this.javaClass.name,e.cause?.message?:"抛出了异常") } } //正确的写法 好吧,,,,我觉得我在说废话。。。 fun catch1Fun(): Unit { viewModelScope.launch(Dispatchers.Main) { try { throw IOException() }catch (e:Exception){ Log.i(this.javaClass.name,e.cause?.message?:"抛出了异常") } } } 复制代码
附带源码地址: github.com/lovebluedan…
以上就是简单的介绍了一下,协程的一些基本用法,关于里面很多原理性的东西,以后有机会再写吧~~ 说实话,我并没有用很深入,所以很多细节的东西还没理解好。以往可以写的深奥点,少点废话少点代码,文章写得精炼点~~