前言
学习了Kotlin一整个系列了,但是协程这块迟迟没有整理成一篇博文。诶,最近状态有点不对 >_< || 。
但是无论如何,一定要加油!!最后一篇要划上个完美点的句号,撒个漂亮点的花。
关于协程的一个点在这里跟大家先说一下,协程并非什么很深奥的东西,说白了也是在线程上面的产物,并非凭空产生的一个新的概念。官网讲得可能有点高大上了,不过实际上你就当是它帮我们使用了线程池
跟Handler
进行一些自动切换线程的逻辑封装进而形成了这样子的一种API吧~~
协程的一些基础使用
添加基本的依赖
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非常气馁(不建议的)。
先来模拟一个场景,在一个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
的父类~
协程的启动方式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
返回回来再继续接下来的操作。
协程分发
image.png协程的取消
获取到对应协程的Job
对象,调用cancel()
var job = GlobalScope.launch(Dispatchers.Main) { }
job.cancel()
//Deferred的对象父类是Job
var deferred = GlobalScope.async { }
deferred.cancel()
Android上使用协程的正确姿势
MainScope
上面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()
}
}
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 {
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
协程的异常处理
- 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?:"抛出了异常")
}
}
}
总结
以上就是简单的介绍了一下,协程的一些基本用法,关于里面很多原理性的东西,以后有机会再写吧~~ 说实话,我并没有用很深入,所以很多细节的东西还没理解好。以往可以写的深奥点,少点废话少点代码,文章写得精炼点~~
网友评论