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'
複製代碼
官網定義: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
再跳轉到ActivityB
bash
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
並不會形成阻塞。這裏的阻塞是指協程做用域外的代碼阻塞,協程做用域內部還會被阻塞的。 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(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
複製代碼
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…
以上就是簡單的介紹了一下,協程的一些基本用法,關於裏面不少原理性的東西,之後有機會再寫吧~~ 說實話,我並無用很深刻,因此不少細節的東西還沒理解好。以往能夠寫的深奧點,少點廢話少點代碼,文章寫得精煉點~~