Android版kotlin協程入門(四):kotlin協程開發實戰
kotlin協程在Android中的基礎應用
透過前面的三個章節,現在我們已經瞭解了
kotlin
協程的基本使用和相關基礎知識點。如:
- 協程的基礎使用方式和基本原理。
-
CoroutineContext
:協程上下文中包含的Element
以及下上文的作用,傳遞。 -
CoroutineDispatcher
:協程排程器的使用 -
CoroutineStart
:協程啟動模式在不同模式下的區別 -
CoroutineScope
:協程作用域的分類,以及不同作用域下的異常處理。 - 掛起函式以及
suspend
關鍵字的作用,以及Continuation
的掛起恢復流程。 -
CoroutineExceptionHandler
:協程異常處理,結合supervisorScope
和SupervisorJob
的使用。
這一章節中,我們將主要講解kotlin協程在Android中的基礎使用。我們先引入相關擴充套件庫元件庫:
implementation "androidx.activity:activity-ktx:1.2.2" implementation "androidx.fragment:fragment-ktx:1.3.3"複製程式碼
Android使用kotlin協程
我們在之前的章節中使用協程的方式都是透過
runBlocking
或者使用
GlobalScope
的
launch
、
async
方式啟動,當然也可以透過建立一個新的
CoroutineScope
,然後透過
launch
或者
async
方式啟動一個新的協程。我們在講解
協程異常處理
的篇章中就提到,透過
SupervisorJob
和
CoroutineExceptionHandler
實現了一個和
supervisorScope
相同的作用域。
private fun testException(){ val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable -> Log.d("exceptionHandler", "${coroutineContext[CoroutineName].toString()} 處理異常 :$throwable") } val supervisorScope = CoroutineScope(SupervisorJob() + exceptionHandler) with(supervisorScope) { launch{ } //省略... } } 複製程式碼
在第一節中我們提到
runBlocking
它會將常規的阻塞程式碼連線到一起,主要用於main函式和測試中。而
GlobalScope
又是一個全域性頂級協程,我們在之前的案例中基本都使用這種方式。但是這個協程是在整個應用程式生命週期內執行的,如果我們用
GlobalScope
啟動協程,我們啟動一個將會變得極其繁瑣,而且需要對於各種引用的處理以及管控異常取消操作。
我們可以先忽略
CoroutineExceptionHandler
協程異常處理。因為不管是任何方式啟動協程,如果不在程上下文中新增
CoroutineExceptionHandler
,當產生未捕獲的異常時都會導致應用崩潰。
那麼下面程式碼會出現什麼問題?
private fun start() { GlobalScope.launch{ launch { //網路請求1... throw NullPointerException("空指標") } val result = withContext(Dispatchers.IO) { //網路請求2... requestData() "請求結果" } btn.text = result launch { //網路請求3... } } } 複製程式碼
-
因為我們的
GlobalScope
預設使用的是Dispatchers.Default
,這會導致我們在非主執行緒上重新整理UI。 -
子協程產生異常會產生相互干擾。子協程異常取消會導致父協程取消,同時其他子協程也將會被取消。
-
如果我們這個時候
activity
或者framgent
退出,因為協程是在GlobalScope
中執行,所以即使activity
或者framgent
退出,這個協程還是在執行,這個時候會產生各種洩露問題。同時此協程當執行到重新整理操作時,因為我們的介面已經銷燬,這個時候執行UI重新整理將會產生崩潰。
如果我們要解決上面的問題。我們得這麼做:
var job:Job? = nullprivate fun start() { job = GlobalScope.launch(Dispatchers.Main + SupervisorJob()) { launch { throw NullPointerException("空指標") } val result = withContext(Dispatchers.IO) { //網路請求... "請求結果" } launch { //網路請求3... } btn.text = result } }override fun onDestroy() { super.onDestroy() job?.cancel() } 複製程式碼
我們先需要透過
launch
啟動時加入
Dispatchers.Main
來保證我們是在主執行緒重新整理UI,同時還需要再
GlobalScope.launch
的協程上下文中加入
SupervisorJob
來避免子協程的異常取消會導致整個協程樹被終結。 最後我們還得把
每次
透過
GlobalScope
啟動的
Job
儲存下來,在
activity
或者
framgent
退出時呼叫
job.cancel
取消整個協程樹。這麼來一遍感覺還行,但是我們不是寫一次啊,每次寫的時候會不會感覺超麻煩,甚至懷疑人生。
所以官方在kotlin協程中提供了一個預設在主執行緒執行的協程:
MainScope
,我們可以透過它來啟動協。
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main) 複製程式碼
我們可以看到
MainScope
的建立預設就使用了
SupervisorJob
和
Dispatchers.Main
。說明我們可以透過
MainScope
來處理UI元件重新整理。同時由於
MainScope
採用的是
SupervisorJob
,所以我們各個子協程中的異常導致的取消操作並不會導致
MainScope
的取消。這就很好的簡化了我們透過
GlobalScope
去啟動一個協程的過程。
private val mainScope = MainScope()private fun start() { mainScope.launch { launch { throw NullPointerException("空指標") } val result = withContext(Dispatchers.IO) { //網路請求... "請求結果" } launch { //網路請求3... } btn.text = result } } override fun onDestroy() { super.onDestroy() mainScope.cancel() } 複製程式碼
透過使用
MainScope
我們是不是省略了很多操作。同時我們也不需要儲存每一個透過
MainScope
啟動的
Job
了,直接在最後銷燬的時候呼叫
mainScope.cancel()
就能取消所有透過
mainScope
啟動的協程。
這裡多提一點:可能這裡有的人會想,我使用
GlobalScope
也不儲存啟動的
Job
,直接
GlobalScope.cancel
不行嗎?如果是這樣的話,那麼恭喜你喜提超級崩潰BUG一個。這裡就不擴充套件了。可以自己動手去試試,畢竟實踐出真理。
那可能還有人想,我連建立
MainScope
都懶得寫,而且腦子經常不好使,容易忘記呼叫
mainScope
進行
cancel
操作怎麼辦。
官方早就為我們這些懶人想好了解決方案,這個時候我們只需要再整合一個ktx執行庫就可以了。
在Activity與Framgent中使用協程
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"複製程式碼
這個時候我們就可以在
activity
或者
framgent
直接使用
lifecycleScope
進行啟動協程。我們看來看看activity中的
lifecycleScope
實現
public val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope get() = lifecycle.coroutineScope 複製程式碼
我們可以到
lifecycleScope
它是透過
lifecycle
得到一個
coroutineScope
,是一個
LifecycleCoroutineScope
物件。
public val Lifecycle.coroutineScope: LifecycleCoroutineScope get() { while (true) { val existing = mInternalScopeRef.get() as LifecycleCoroutineScopeImpl? if (existing != null) { return existing } val newScope = LifecycleCoroutineScopeImpl( this, SupervisorJob() + Dispatchers.Main.immediate ) if (mInternalScopeRef.compareAndSet(null, newScope)) { newScope.register() return newScope } } } 複製程式碼
我們可以看到
lifecycleScope
採用的和
MainScope
一樣的建立
CoroutineScope
,同時它又透過結合
lifecycle
來實現當
lifecycle
狀態處於
DESTROYED
狀態的時候自動關閉所有的協程。
public abstract class LifecycleCoroutineScope internal constructor() : CoroutineScope { internal abstract val lifecycle: Lifecycle public fun launchWhenCreated(block: suspend CoroutineScope.() -> Unit): Job = launch { lifecycle.whenCreated(block) } public fun launchWhenStarted(block: suspend CoroutineScope.() -> Unit): Job = launch { lifecycle.whenStarted(block) } public fun launchWhenResumed(block: suspend CoroutineScope.() -> Unit): Job = launch { lifecycle.whenResumed(block) } }internal class LifecycleCoroutineScopeImpl( override val lifecycle: Lifecycle, override val coroutineContext: CoroutineContext ) : LifecycleCoroutineScope(), LifecycleEventObserver { init { if (lifecycle.currentState == Lifecycle.State.DESTROYED) { coroutineContext.cancel() } } fun register() { launch(Dispatchers.Main.immediate) { if (lifecycle.currentState >= Lifecycle.State.INITIALIZED) { lifecycle.addObserver(this@LifecycleCoroutineScopeImpl) } else { coroutineContext.cancel() } } } override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { if (lifecycle.currentState <= Lifecycle.State.DESTROYED) { lifecycle.removeObserver(this) coroutineContext.cancel() } } } 複製程式碼
同時我們也可以透過
launchWhenCreated
、
launchWhenStarted
、
launchWhenResumed
來啟動協程,等到
lifecycle
處於對應狀態時自動觸發此處建立的協程。
比如我們可以這麼操作:
class MainTestActivity : AppCompatActivity() { init { lifecycleScope.launchWhenResumed { Log.d("init", "在類初始化位置啟動協程") } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } } 複製程式碼
D/onResume: onResume D/init: 在類初始化位置啟動協程 複製程式碼
按照我們正常情況載入順序,是不是應該
init
先執行輸出?然而在實際情況中它是在等待
Activity
進入
onResume
狀態以後才執行接著看
launchWhenResumed
中呼叫的
whenResumed
實現。
public suspend fun <T> Lifecycle.whenResumed(block: suspend CoroutineScope.() -> T): T { return whenStateAtLeast(Lifecycle.State.RESUMED, block) }public suspend fun <T> Lifecycle.whenStateAtLeast( minState: Lifecycle.State, block: suspend CoroutineScope.() -> T ): T = withContext(Dispatchers.Main.immediate) { val job = coroutineContext[Job] ?: error("when[State] methods should have a parent job") val dispatcher = PausingDispatcher() val controller = LifecycleController(this@whenStateAtLeast, minState, dispatcher.dispatchQueue, job) try { withContext(dispatcher, block) } finally { controller.finish() } }@MainThreadinternal class LifecycleController( private val lifecycle: Lifecycle, private val minState: Lifecycle.State, private val dispatchQueue: DispatchQueue, parentJob: Job ) { private val observer = LifecycleEventObserver { source, _ -> if (source.lifecycle.currentState == Lifecycle.State.DESTROYED) { handleDestroy(parentJob) } else if (source.lifecycle.currentState < minState) { dispatchQueue.pause() } else { dispatchQueue.resume() } } init { if (lifecycle.currentState == Lifecycle.State.DESTROYED) { handleDestroy(parentJob) } else { lifecycle.addObserver(observer) } } private inline fun handleDestroy(parentJob: Job) { parentJob.cancel() finish() } @MainThread fun finish() { lifecycle.removeObserver(observer) dispatchQueue.finish() } } 複製程式碼
我們可以看到,實際上是呼叫了
whenStateAtLeast
,同時使用了
withContext
進行了一個同步操作。然後在
LifecycleController
中透過新增
LifecycleObserver
來監聽狀態,透過
lifecycle
當前狀態來對比我們設定的觸發狀態,最終決定是否恢復執行。
現在我們對於
Activity
中的
lifecycleScope
的建立以及銷燬流程有了一個大概的瞭解。同理
Fragment
中的
lifecycleScope
實現原理也是和
Activity
是一樣的,這裡我們就不再重複講解。我們做個簡單的實驗:
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) lifecycleScope.launch { delay(2000) Toast.makeText(this@MainActivity,"haha",Toast.LENGTH_SHORT).show() } } } 複製程式碼
這個時候是不是比之前的使用方式簡單多了,我們既不用關心建立過程,也不用關心銷燬的過程。
這個時候我們就需要提到
CoroutineExceptionHandler
協程異常處理。透過之前的章節我們知道,啟動一個協程以後,如果未在協程上下文中新增
CoroutineExceptionHandler
情況下,一旦產生了未捕獲的異常,那麼我們的程式將會崩潰退出。
class MainActivity : AppCompatActivity() { val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable -> Log.d("exceptionHandler", "${coroutineContext[CoroutineName]} $throwable") } fun load() { lifecycleScope.launch(exceptionHandler) { //省略... } lifecycleScope.launch(exceptionHandler) { //省略... } lifecycleScope.launch(exceptionHandler) { //省略... } } } 複製程式碼
當出現這種情況的時候,像筆者這種有嚴重偷懶情結的人就開始抓狂了。為什麼要寫這麼多遍
lifecycleScope.launch
,同時每一次啟動都要手動新增
CoroutineExceptionHandler
。難道就不能再簡便一點嗎?
當然可以,首先我們自定義一個異常處理,,我們在實現上只做一個簡單的異常日誌輸出:
/** * @param errCode 錯誤碼 * @param errMsg 簡要錯誤資訊 * @param report 是否需要上報 */ class GlobalCoroutineExceptionHandler(private val errCode: Int, private val errMsg: String = "", private val report: Boolean = false) : CoroutineExceptionHandler { override val key: CoroutineContext.Key<*> get() = CoroutineExceptionHandler override fun handleException(context: CoroutineContext, exception: Throwable) { val msg = exception.stackTraceToString() Log.e("$errCode","GlobalCoroutineExceptionHandler:${msg}") } } 複製程式碼
然後我們在透過kotlin的擴充套件函式來簡化我們的使用,去掉重複寫
lifecycleScope.launch
和
exceptionHandler
的過程,我們就定義三個常用方法。
inline fun AppCompatActivity.requestMain( errCode: Int = -1, errMsg: String = "", report: Boolean = false, noinline block: suspend CoroutineScope.() -> Unit) { lifecycleScope.launch(GlobalCoroutineExceptionHandler(errCode, errMsg, report)) { block.invoke(this) } } inline fun AppCompatActivity.requestIO( errCode: Int = -1, errMsg: String = "", report: Boolean = false, noinline block: suspend CoroutineScope.() -> Unit): Job { return lifecycleScope.launch(Dispatchers.IO + GlobalCoroutineExceptionHandler(errCode, errMsg, report)) { block.invoke(this) } } inline fun AppCompatActivity.delayMain( errCode: Int = -1, errMsg: String = "", report: Boolean = false, delayTime: Long, noinline block: suspend CoroutineScope.() -> Unit) { lifecycleScope.launch(GlobalCoroutineExceptionHandler(errCode, errMsg, report)) { withContext(Dispatchers.IO) { delay(delayTime) } block.invoke(this) } } 複製程式碼
這個時候我們就可以愉快的在
Activity
中使用了
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) requestMain { delay(2000) Toast.makeText(this@MainActivity,"haha",Toast.LENGTH_SHORT).show() } requestIO { loadNetData() } delayMain(100){ Toast.makeText(this@MainActivity,"haha",Toast.LENGTH_SHORT).show() } } private suspend fun loadNetData(){ //網路載入 } } 複製程式碼
同樣的我們再擴充套件一套基於
Fragment
的方法
inline fun Fragment.requestMain( errCode: Int = -1, errMsg: String = "", report: Boolean = false, noinline block: suspend CoroutineScope.() -> Unit) { lifecycleScope.launch(GlobalCoroutineExceptionHandler(errCode, errMsg, report)) { block.invoke(this) } } inline fun Fragment.requestIO( errCode: Int = -1, errMsg: String = "", report: Boolean = false, noinline block: suspend CoroutineScope.() -> Unit) { lifecycleScope.launch(Dispatchers.IO + GlobalCoroutineExceptionHandler(errCode, errMsg, report)) { block.invoke(this) } } inline fun Fragment.delayMain( errCode: Int = -1, errMsg: String = "", report: Boolean = false, delayTime: Long, noinline block: suspend CoroutineScope.() -> Unit) { lifecycleScope.launch(GlobalCoroutineExceptionHandler(errCode, errMsg, report)) { withContext(Dispatchers.IO) { delay(delayTime) } block.invoke(this) } } 複製程式碼
然後也可以愉快的在
Fragment
中使用了
class HomeFragment:Fragment() { init { lifecycleScope.launchWhenCreated { Toast.makeText(context,"Fragment建立了", Toast.LENGTH_SHORT).show() } } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return inflater.inflate(R.layout.fragment_main,container,false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) requestMain { //... } requestIO { //... } delayMain(100){ //... } } } 複製程式碼
這裡需要提一下,可能有的人不太明白,為什麼要把
Activity
和
Fragment
都分開寫,他們都是使用的
lifecycleScope
,我們直接透過
lifecycleScope
擴充套件就不可以了嗎。假如我們這麼擴充套件:
inline fun LifecycleCoroutineScope.requestMain( errCode: Int = -1, errMsg: String = "", report: Boolean = false, noinline block: suspend CoroutineScope.() -> Unit) { launch(GlobalCoroutineExceptionHandler(errCode, errMsg, report)) { block.invoke(this) } } 複製程式碼
我們以
Dailog
為例,來啟動一個協程:
val dialog = Dialog(this) dialog.show() (dialog.context as LifecycleOwner).lifecycleScope.requestMain { withContext(Dispatchers.IO){ //網路載入 } // 重新整理UI} dialog.cancel() 複製程式碼
那麼可能會出現一個什麼問題?是的,記憶體洩露的問題以及錯誤的引用問題。雖然我的
dialog
被銷燬了,但是我們
lifecycleScope
並不處於
DESTROYED
狀態,所以我們的協程依然會執行,這個時候我們就會出現記憶體洩露和崩潰問題。
透過上面的學習,我們已經基本掌握了協程在
Activity
和
Fragment
中的使用方式。接下來我們講解在
Viewmodel
中使用協程。
ViewModel中使用協程
如果我們想和在
Activity
和
Fragment
中一樣的簡便、快速的在
ViewModel
使用協程。那麼我們就需要整合下面這個官方的
ViewModel
擴充套件庫。
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"複製程式碼
與
Activity
和
Fragment
不同的是,在
ViewModel
我們使用的不是
lifecycleScope
,而是使用
viewModelScope
,使用
viewModelScope
,使用
viewModelScope
。重要的事情說三遍。
這裡一定要注意噢,之前就有好幾個人問我為什麼在
viewmodel
裡面用不了協程,我開始納悶半天咋就用不了呢。最後一問結果是
在
ViewModel
使用
lifecycleScope
,
這樣做是不對滴。
public 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() } } 複製程式碼
viewModelScope
相比較
lifecycleScope
實現會稍微簡單一點。都是使用的
SupervisorJob() + Dispatchers.Main
上下文,同時最終的取消操作也類似
lifecycleScope
,只不過
viewModelScope
取消是在
ViewModel
的銷燬的時候取消。
final void clear() { mCleared = true; if (mBagOfTags != null) { synchronized (mBagOfTags) { for (Object value : mBagOfTags.values()) { closeWithRuntimeException(value); } } } onCleared(); } <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; }private static void closeWithRuntimeException(Object obj) { if (obj instanceof Closeable) { try { ((Closeable) obj).close(); } catch (IOException e) { throw new RuntimeException(e); } } } 複製程式碼
同樣的透過上面的總結,我們也為
ViewModel
擴充套件一套常用的方法
inline fun ViewModel.requestMain( errCode: Int = -1, errMsg: String = "", report: Boolean = false, noinline block: suspend CoroutineScope.() -> Unit) { viewModelScope.launch(GlobalCoroutineExceptionHandler(errCode, errMsg, report)) { block.invoke(this) } } inline fun ViewModel.requestIO( errCode: Int = -1, errMsg: String = "", report: Boolean = false, noinline block: suspend CoroutineScope.() -> Unit) { viewModelScope.launch(Dispatchers.IO + GlobalCoroutineExceptionHandler(errCode, errMsg, report)) { block.invoke(this) } } inline fun ViewModel.delayMain( errCode: Int = -1, errMsg: String = "", report: Boolean = false, delayTime: Long, noinline block: suspend CoroutineScope.() -> Unit) { viewModelScope.launch(GlobalCoroutineExceptionHandler(errCode, errMsg, report)) { withContext(Dispatchers.IO) { delay(delayTime) } block.invoke(this) } } 複製程式碼
然後我們就可以愉快的在
ViewModel
進行使用協程了。
class MainViewModel:ViewModel() { init { requestMain { Log.d("MainViewModel", "主執行緒中啟動協程") } requestIO { Log.d("MainViewModel", "IO執行緒中啟動協程進行網路載入") } delayMain(100){ Log.d("MainViewModel", "主執行緒中啟動協程並延時一定時間") } } } 複製程式碼
好了,常規使用協程的方式我都已經學會。但是我們在一些環境下如法使用使用
lifecycleScope
和
viewModelScope
的時候我們又該怎麼辦。比如:在
Service
、
Dialog
、
PopWindow
以及一些其他的環境中又該如何使用。
其他環境下使用協程
在這些環境中我們可以採用通用的方式進行處理,其實還是根據協程作用域的差異分為兩類:
-
協同作用域
:這一類我們就模仿MainScope
自定義一個CoroutineScope
。 -
主從(監督)作用域
:這一類我們直接使用MainScope
,然後在此基礎上做一些擴充套件即可。
如果對這兩個概念還不理解的,麻煩移步到第二章節裡面仔細閱讀一遍,這裡就不再解釋。
我們接下來模仿
MainScope
建立一個
CoroutineScope
,它是在主執行緒下執行,並且它的
Job
不是
SupervisorJob
。
@Suppress("FunctionName")public fun NormalScope(): CoroutineScope = CoroutineScope(Dispatchers.Main) 複製程式碼
然後我再基於
NormalScope
和
MainScope
進行使用。我們就以
Service
為例來實現。
abstract class BaseService :Service(){ private val normalScope = NormalScope() override fun onDestroy() { normalScope.cancel() super.onDestroy() } protected fun requestMain( errCode: Int = -1, errMsg: String = "", report: Boolean = false, block: suspend CoroutineScope.() -> Unit) { normalScope.launch(GlobalCoroutineExceptionHandler(errCode, errMsg, report)) { block.invoke(this) } } protected fun requestIO( errCode: Int = -1, errMsg: String = "", report: Boolean = false, block: suspend CoroutineScope.() -> Unit): Job { return normalScope.launch(Dispatchers.IO + GlobalCoroutineExceptionHandler(errCode, errMsg, report)) { block.invoke(this) } } protected fun delayMain( delayTime: Long,errCode: Int = -1, errMsg: String = "", report: Boolean = false, block: suspend CoroutineScope.() -> Unit) { normalScope.launch(GlobalCoroutineExceptionHandler(errCode, errMsg, report)) { withContext(Dispatchers.IO) { delay(delayTime) } block.invoke(this) } } } 複製程式碼
我們建立一個抽象類
BaseService
類,然後再定義一些基礎使用方法後,我就可以快速的使用了
class MainService : BaseService() { override fun onBind(intent: Intent): IBinder? = null override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { requestIO { //網路載入 } return super.onStartCommand(intent, flags, startId) } } 複製程式碼
同理在
Dialog
、
PopWindow
以及一些其他的環境中可以依照此方法,定義符合我們自己需求的
CoroutineScope
。
一定要記得不要跨域使用,以及及時的關閉協程。
又到了文章末尾,在此章節中我們已經瞭解了協程結合
Activity
、
Fragment
、
Lifecycle
、
Viewmodel
的基礎使用,以及如何簡單的自定義一個協程,如果還有不清楚的地方,可在下方留言。
更多Android技術分享可以關注@我,也可以加入QQ群號:Android進階學習群:345659112,一起學習交流。
作者:一個被攝影耽誤的程式猿
來源:稀土掘金
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69983917/viewspace-2795553/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Android Kotlin協程入門AndroidKotlin
- Android版kotlin協程入門(三):kotlin協程的異常處理AndroidKotlin
- Android入門教程 | Kotlin協程入門AndroidKotlin
- Android版 kotlin協程入門(二):kotlin協程的關鍵知識點初步講解AndroidKotlin
- 在 Android 開發中使用 Kotlin 協程 (一) -- 初識 Kotlin 協程AndroidKotlin
- Kotlin協程快速入門Kotlin
- Android Kotlin 協程初探AndroidKotlin
- 【Kotlin】協程Kotlin
- Kotlin(android)協程中文翻譯KotlinAndroid
- Kotlin Coroutine(協程): 二、初識協程Kotlin
- Kotlin Coroutine(協程): 三、瞭解協程Kotlin
- Kotlin 協程一 —— CoroutineKotlin
- Kotlin Coroutine(協程)簡介Kotlin
- Kotlin Coroutines(協程)講解Kotlin
- Kotlin協程快速進階Kotlin
- 揭開Kotlin協程的神秘面紗Kotlin
- Kotlin coroutine之協程基礎Kotlin
- Kotlin Coroutine(協程) 基本知識Kotlin
- Kotlin協程學習之路【一】Kotlin
- kotlin協程的掛起suspendKotlin
- 【譯】kotlin 協程官方文件(1)-協程基礎(Coroutine Basics)Kotlin
- [譯] Kotlin 協程高階使用技巧Kotlin
- Kotlin 1.4.0-RC協程除錯Kotlin除錯
- 【譯】使用kotlin協程提高app效能KotlinAPP
- 真香!Kotlin+MVVM+LiveData+協程 打造 Wanandroid!KotlinMVVMLiveDataNaNAndroid
- 扒一扒Kotlin協程的底褲Kotlin
- 【譯】kotlin 協程官方文件(6)-通道(Channels)Kotlin
- 【譯】第一次走進 Android 中的 Kotlin 協程AndroidKotlin
- [譯] 使用 Kotlin 協程改進應用效能Kotlin
- kotlin中將回撥改寫為協程Kotlin
- 【思貨】kotlin協程優雅的與Retrofit纏綿-kotlin DSL簡述Kotlin
- rxjava回撥地獄-kotlin協程來幫忙RxJavaKotlin
- Kotlin 入門開發__安卓小專案實戰Kotlin安卓
- Kotlin + 協程 + Retrofit + MVVM優雅的實現網路請求KotlinMVVM
- 資源混淆是如何影響到Kotlin協程的Kotlin
- 一篇文章帶你瞭解——Kotlin協程Kotlin
- Android中用Kotlin Coroutine(協程)和Retrofit進行網路請求和取消請求AndroidKotlin
- Android技術分享| 利用Kotlin協程,多工並行,測試RTM SDK效能AndroidKotlin並行