前言
學習了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
返回回來再繼續接下來的操作。
協程分發
協程的取消
獲取到對應協程的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?:"拋出了異常")
}
}
}
總結
以上就是簡單的介紹了一下,協程的一些基本用法,關于里面很多原理性的東西,以后有機會再寫吧~~ 說實話,我并沒有用很深入,所以很多細節的東西還沒理解好。以往可以寫的深奧點,少點廢話少點代碼,文章寫得精煉點~~