kotlin
官方文檔說:本質上,協程是輕量級的線程。
從 Android 開發者的角度去理解它們的關係:android
NetworkOnMainThreadException
,對於在主線程上的協程也不例外,這種場景使用協程仍是要切線程的。咱們學習Kotlin
中的協程,一開始確實能夠從線程控制的角度來切入。由於在 Kotlin
中,協程的一個典型的使用場景就是線程控制。就像 Java 中的 Executor
和 Android 中的 AsyncTask
,Kotlin
中的協程也有對 Thread API 的封裝,讓咱們能夠在寫代碼時,不用關注多線程就可以很方便地寫出併發操做。數據庫
小結:express
Kotlin
協程的話,就是封裝好的線程池,也能夠理解成一個線程框架。
RxJava
能夠解決回調問題,一樣咱們能夠用協程解決回調問題。網絡
解釋說明:多線程
Kotlin
版本: 1.3.+app/build.gradle
裏添加 Kotlin
協程庫的依賴以下所示。//kotlin 標準庫 implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" //依賴協程核心庫 ,提供Android UI調度器 implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1" //依賴當前平臺所對應的平臺庫 (必須) implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1'
方式 | 做用 |
---|---|
launch:job |
建立一個不會阻塞當前線程、沒有返回結果的 Coroutine,但會返回一個 Job 對象,能夠用於控制這個 Coroutine 的執行和取消,返回值爲Job。 |
runBlocking:T |
建立一個會阻塞當前線程的Coroutine,經常使用於單元測試的場景,開發中通常不會用到 |
async/await:Deferred |
async 返回的 Coroutine 多實現了 Deferred 接口,簡單理解爲帶返回值的launch函數 |
實現方式一: GlobalScope.launch
,使用 GlobalScope 單例對象, 能夠直接調用 launch 開啓協程。併發
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_thread) loadData() } private fun loadData() { GlobalScope.launch(Dispatchers.IO) { //在IO線程開始 //IO 線程里拉取數據 val result = fetchData() //主線程裏更新 UI withContext(Dispatchers.Main) { //執行結束後,自動切換到UI線程 tvShowContent.text = result } } } //關鍵詞 suspend private suspend fun fetchData(): String { delay(2000) // delaying for 2 seconds to keep JVM alive return "content" }
咱們最經常使用的用於啓動協程的方式,它最終返回一個Job類型的對象,這個Job類型的對象其實是一個接口,它包涵了許多咱們經常使用的方法。 該方式啓動的協程任務是不會阻塞線程的* app
實現方式二:使用 runBlocking
頂層函數 框架
runBlocking {}
是建立一個新的協程同時阻塞當前線程,直到協程結束。這個不該該在協程中使用,主要是爲main
函數和測試設計的 。異步
fun main(args: Array<String>) = runBlocking { // start main coroutine launch { // launch new coroutine in background and continue delay(1000L) println("World!") } println("Hello,") // main coroutine continues here immediately delay(2000L) // delaying for 2 seconds to keep JVM alive }
實現方式三:async
+await
async
private fun testAysnc() = GlobalScope.launch { val deferred = async(Dispatchers.IO) { delay(3000L) "Show Time" } // 此處獲取耗時任務的結果,咱們掛起當前協程,並等待結果 val result = deferred.await() //掛起協程切換至UI線程 展現結果 withContext(Dispatchers.Main) { tvShowContent.text = result } }
那咱們平日裏經常使用到的調度器有哪些?
Dispatchers 種類 |
做用 |
---|---|
Dispatchers.Default | 共享後臺線程池裏的線程(適合 CPU 密集型的任務,好比計算) |
Dispatchers.Main | Android中的主線程 |
Dispatchers.IO | 共享後臺線程池裏的線程(針對磁盤和網絡 IO 進行了優化,適合 IO 密集型的任務,好比:讀寫文件,操做數據庫以及網絡請求) |
Dispatchers.Unconfined | 不限制,使用父Coroutine的現場 |
回到咱們的協程,它從 suspend
函數開始脫離啓動它的線程,繼續執行在 Dispatchers
所指定的 IO 線程。
緊接着在 suspend
函數執行完成以後,協程爲咱們作的最爽的事就來了:會自動幫咱們把線程再切回來。
這個"切回來"是什麼意思?
咱們的協程本來是運行在主線程的,當代碼遇到 suspend 函數的時候,發生線程切換,根據 Dispatchers
切換到了 IO 線程;
當這個函數執行完畢後,線程又切了回來,"切回來"也就是協程會幫我再 post
一個 Runnable
,讓我剩下的代碼繼續回到主線程去執行。
從相冊中直接讀取圖片,這是一個典型的IO操做使用場景,操做不當,可能會出現ANR。
版本1.0實現方式
val mImageUri = MediaStore.Images.Media.INTERNAL_CONTENT_URI val bitmap = MediaStore.Images.Media.getBitmap(contentResolver, mImageUri) imageView.setImageBitmap(bitmap)
版本2.0 咱們可能會引入Handler
或 AysnTask
來經過異步的方式實現
版本3.0 咱們能夠這樣用doAsync
實現 這種方式也不錯
doAsync{ //後臺執行 val mImageUri = MediaStore.Images.Media.INTERNAL_CONTENT_URI val bitmap = MediaStore.Images.Media.getBitmap(contentResolver,mImageUri) //回到主線程 uiThread{ imageView.setImageBitmap(bitmap) } }
版本4.0 時咱們就能夠用協程來實現。
val job = launch(Background) { val mImageUri = MediaStore.Images.Media.INTERNAL_CONTENT_URI val bitmap = MediaStore.Images.Media.getBitmap(contentResolver,mImageUri) launch(UI) { imageView.setImageBitmap(bitmap) }
這裏的參數Background是一個CoroutineContext對象,確保這個協程運行在一個後臺線程,確保你的應用程序不會因耗時操做而阻塞和崩潰。你能夠像下邊這樣定義一個CoroutineContext:
internal val Background = newFixedThreadPoolContext(2, "bg")
人個感受 最後兩種方式均可取。
後面介紹的三種使用方式在實現前須要分別添加如下的依賴包
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0-rc02' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-rc02' implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-rc02'
爲應用程序中的每一個ViewModel
定義ViewModelScope
。若是清除ViewModel
,則在此做用域中啓動的任何協同程序都將自動取消。
當只有在ViewModel處於活動狀態時才須要完成工做時,協程在這裏很是有用。
例如,若是要爲佈局計算某些數據,則應將工做範圍設置爲ViewModel,以便在清除ViewModel時,自動取消工做以免消耗資源。
能夠經過ViewModel的viewModelScope屬性訪問ViewModel的協同做用域,以下例所示:
class MyViewModel :ViewModel() { init { viewModelScope.launch { // Coroutine that will be canceled when the ViewModel is cleared. } } }
爲每一個Lifecycle
定義LifecycleScope
。當 Lifecycle
銷燬時,在此範圍內啓動的任何協同程序都將被取消。
您能夠經過Lifecycle.CoroutineScope
或lifecycleOwner.lifecycleScope
屬性訪問Lifecycle
的 CoroutineScope
。
下面的示例演示如何使用lifecycleOwner.lifecycleScope
異步建立預計算文本:
class MyFragment: Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) viewLifecycleOwner.lifecycleScope.launch { val params = TextViewCompat.getTextMetricsParams(textView) val precomputedText = withContext(Dispatchers.Default) { PrecomputedTextCompat.create(longTextContent, params) } TextViewCompat.setPrecomputedText(textView, precomputedText) } } }
使用LiveData時,可能須要異步計算值。例如,您可能但願檢索用戶的首選項並將其提供給您的UI。在這些狀況下,可使用liveData builder函數調用suspend函數,將結果做爲liveData對象提供。
在下面的示例中,loadUser()是在別處聲明的掛起函數。使用liveData 構建函數異步調用loadUser(),而後使用emit()發出結果。
val user: LiveData<User> = liveData { val data = database.loadUser() // loadUser is a suspend function. emit(data) }
LiveData構建塊充當協同路由和liveData之間的結構化併發原語。代碼塊在LiveData變爲活動時開始執行,而且在LiveData變爲非活動時通過可配置的超時後自動取消。若是在完成以前取消,則在LiveData再次激活時從新啓動。若是在上一次運行中成功完成,則不會從新啓動。請注意,只有在自動取消時纔會從新啓動。若是因爲任何其餘緣由(例如拋出異常CancelationException)而取消塊,則不會從新啓動它。
也能夠從塊中發射多個值。每次emit()調用都會暫停塊的執行,直到在主線程上設置LiveData值。
val user: LiveData<Result> = liveData { emit(Result.loading()) try { emit(Result.success(fetchUser())) } catch(ioException: Exception) { emit(Result.error(ioException)) } }
咱們也能夠和 LifeCycle
中的Transformations
結合使用,以下例所示:
class MyViewModel: ViewModel() { private val userId: LiveData<String> = MutableLiveData() val user = userId.switchMap { id -> liveData(context = viewModelScope.coroutineContext + Dispatchers.IO) { emit(database.loadUserById(id)) } } }
retrofit 2.6.0(2019-06-05)中的更新日誌以下:
Support
suspend
modifier on functions for Kotlin! This allows you to express the asynchrony of HTTP requests in an idiomatic fashion for the language.
@GET("users/{id}") suspend fun user(@Path("id") id: Long): User
Behind the scenes this behaves as if defined asfun user(...): Call
and then invoked withCall.enqueue
. You can also returnResponse
for access to the response metadata.
在函數前加上 suspend
函數直接返回你須要對象類型不須要返回Call
對象
本文總結了kotlin中的協程的相關知識點,協程是值得深刻研究的。 將來的項目中運用是趨勢所在,現將學習的心得總結於此,方便將來迭代中作爲技術的儲備。若有不足之處,歡迎留言討論。
參考資料:
3.Kotlin 的協程用力瞥一眼 - 學不會協程?極可能由於你看過的教程都是錯的
5.【碼上開學】Kotlin 協程的掛起好神奇好難懂?今天我把它的皮給扒了