Koltin系列 - 协程从认识到安卓中的使用(五)

Kotlin细节文章笔记整理更新进度:
Kotlin系列 - 基础类型结构细节小结(一)
Kotlin系列 - 函数与类相关细节小结(二)
Kotlin系列 - 高阶函数与标准库中的经常使用函数(三)
Kotlin系列 - 进阶深刻泛型协变逆变从java到Kotlin(四)java

前言

学习了Kotlin一整个系列了,可是协程这块迟迟没有整理成一篇博文。诶,最近状态有点不对 >_< || 。 可是不管如何,必定要加油!!最后一篇要划上个完美点的句号,撒个漂亮点的花。android

关于协程的一个点在这里跟你们先说一下,协程并不是什么很深奥的东西,说白了也是在线程上面的产物,并不是凭空产生的一个新的概念。官网讲得可能有点高大上了,不过实际上你就当是它帮咱们使用了线程池Handler进行一些自动切换线程的逻辑封装进而造成了这样子的一种API吧~~git

协程的一些基础使用

添加基本的依赖github

implementation  'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0'
复制代码

GlobalScope

官网定义: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很是气馁(不建议的)。api

先来模拟一个场景,在一个ActivityA调用globalScopeLaunch,或者globalScopeLaunch进行耗时操做,相似IO操做或者网络请求等。而后在它尚未返回的时候销毁ActivityA再跳转到ActivityBbash

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秒后,再弹出来。app

上面的代码能够简化一下 在协程做用域中,可使用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并不会形成阻塞。这里的阻塞是指协程做用域外的代码阻塞,协程做用域内部还会被阻塞的。 CoroutineScopeGlobalScope的父类~

两种协程做用域,以及结构化并发.png

协程的启动方式launch与Async

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返回回来再继续接下来的操做。

协程的启动方式.png

协程分发

image.png

协程的取消

获取到对应协程的Job对象,调用cancel()

var job = GlobalScope.launch(Dispatchers.Main) { }
job.cancel()
//Deferred的对象父类是Job
var deferred =  GlobalScope.async {  }
deferred.cancel()
复制代码

Android上使用协程的正确姿式

MainScope

上面Global的官方定义中已经提示咱们使用自定义的协程。 MainScopekotlin为咱们自定义好的一个协程做用域。 代码定义:

@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()
    }
}

复制代码

ViewModelScope

使用该协程首先要导入包

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 {         }
    }
复制代码

LiveData && LifecycleScope 这两个我本身并无使用。

推荐一下

秉心说TM的 - 如何正确的在 Android 上使用协程 ? 里面有说了这几种kotlin为咱们提供的协程

协程中的多种任务状况

  • 多个任务串行( launch+ withContext多个)
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
复制代码
  • 多个任务并行( launch+ async多个)(launch + launch多个)
viewModelScope.launch {
            val deferred = async(Dispatchers.IO) {
                Log.i(TAG, "result1-1")
                Log.i(TAG, "result1-2")
                Thread.sleep(4000)
                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-25 13:36:00.736 6221-6253/com.ldr.testcoroutines I/MainViewModel: result1-1
2020-03-25 13:36:00.736 6221-6253/com.ldr.testcoroutines I/MainViewModel: result1-2
2020-03-25 13:36:00.737 6221-6221/com.ldr.testcoroutines I/MainViewModel: result2-1
2020-03-25 13:36:00.737 6221-6221/com.ldr.testcoroutines I/MainViewModel: result2-2
2020-03-25 13:36:00.737 6221-6221/com.ldr.testcoroutines I/MainViewModel: result2-3
2020-03-25 13:36:04.738 6221-6253/com.ldr.testcoroutines I/MainViewModel: result1-3
2020-03-25 13:36:04.750 6221-6221/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
复制代码

协程的异常处理

  • CoroutineExceptionHandler
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这种方法能够将多层嵌套下的异常也捕获到。

  • try { }catch(){}
//错误的写法 协程外部是捕获不到异常的
    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…

总结

以上就是简单的介绍了一下,协程的一些基本用法,关于里面不少原理性的东西,之后有机会再写吧~~ 说实话,我并无用很深刻,因此不少细节的东西还没理解好。以往能够写的深奥点,少点废话少点代码,文章写得精炼点~~

相关文章
相关标签/搜索