--- tags: Android, thread, coroutine, Kotlin --- # Android的threading與asynchronous ## 概要 為了良好的使用者體驗,Android對thread的限制十分嚴格(主要是UI thread),每次太久沒碰Android都會忘記該怎麼用,這邊簡單筆記一下 如果想直接看官方Android threading的doc的話就點[這裡](https://developer.android.com/guide/components/processes-and-threads) 最重要且不太會忘記的部分應該就是UI thread不能被block住,只要[block超過五秒左右系統就會跳沒有APP回應的對話視窗](https://developer.android.com/topic/performance/threads#internals)。 接著就是thread或是asynchronous要怎麼產生怎麼用,主要就分成以下幾種機制 1. Thread 2. AsyncTask (deprecated in API level 30) 3. Coroutines (Kotlin) 4. Rx (RxJava) 其實AsyncTask我有寫過記得滿好用的,但既然要被淘汰了那就算了XD Rx感覺要學一下,好像是跟Observer pattern和asynchronous有關,但我還沒有用過,所以這次筆記不會寫 以下就針對Thread跟Coroutines做簡單筆記 ## Process and Thread in Android 在開始之前,先來講一下Android中process和thread的關係。 預設情況下,每個app會有屬於自己的一個Linux user ID,且每個app會在一個自己的Linux process中運行,然後所有component(activity, service, broadcast receiver和content provider)的function(像是onCreate()等等以及自己寫的function)都是同步地跑在main thread中。 以上都是預設的情況,你也可以透過更改AndroidManifest中各個component的android:process名字來設定各個component要用哪個process;或是透過Thread、Coroutines等機制來切換使用不同的thread以及異步的處理。 ## Thread 這裡有兩種thread 1. Thread: 一次性,做完就關閉 2. HandlerThread: 可重複使用,做完就待命 關於Thread的用法,官方的教學系列文在[這邊](https://developer.android.com/training/multiple-threads)。 HandlerThread的部分以及Handler、thread pool等等的我就先不寫了,改天我有要用到再回來補充,這邊我就只放上Thread的用法(從官網抓來的)來了解一下就好。Thread很簡單,如下: ``` fun onClick(v: View) { Thread(Runnable { // a potentially time consuming task val bitmap = processBitMap("image.png") imageView.post { imageView.setImageBitmap(bitmap) } }).start() } ``` 首先你要有一個Runable當作Thread的引數,然後直接叫這個Thread做run()。然後他就開始跑了,跑完就關閉,就這麼簡單。 如果對Kotlin還不熟的話可以注意一下,這個start()不是Thread的static method,因為Kotlin沒有new這個keyword,所以這邊Thread(...)其實已經是一個新的instance了,這個start()是這個新instance所執行的method。 Runable的部分它其實是一個interface,上面的程式碼是直接放一個anonymous class作為建構Thread的引述(不是[new一個interface](https://stackoverflow.com/a/9157809/11189726)喔),但你也可以在其他地方寫一個class來實作Runable,這樣比較好重複使用。 ## Coroutines coroutine是Kotlin的東西,如果你要查相關的document的話要去Kotlin那邊查,現在在Android開發中常常會用到。它基本用起來算簡單好用,但他背後若要細細探究也是可以花上很多時間,以下只介紹基本的用法,比較深的我自己也沒鑽研。 簡單來說,couroutine就是在一個thread中實現異步的一個機制,核心的class(明確地說是以下大寫的都是interface)有幾個: 1. **CoroutineScope** 就是coroutine存在的範圍,官方頁面[在此](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/)。 2. **CoroutineContext** 就是這個coroutine的使用場景(情境、環境),是CoroutineScope的一個property,讓coroutine builders知道現在的場景是什麼。具體來說它是一個像是map的東西,裡面放了些coroutine context elements(明確來說是Element這個interface)。CoroutineContext裡面通常會放像是Job跟CoroutineDispatcher的Element,它們都是繼承Element的interface。可以透過this.coroutineContext[Job]這種方式來access該context裡面的各個Job或是其他Element。更多細節可以看[官方頁面](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/coroutine-context.html),而這篇[SO回答](https://stackoverflow.com/a/47689985/11189726)也寫得很好。 3. **coroutine builders** 是用來創造以及操作coroutine的function,其實就是CoroutineScope的一些public method,像是launch、async和cancel等等,會直接使用該CoroutineScope的context。 4. **Job** 就是coroutine中實際在背景被執行的程式碼片段,也就是coroutine本人。感覺起來有點像Java的Runable,但我覺得Runable比較強調run,而Job比較強調cancel。Job是繼承Element(coroutine context的element)的一個interface,所以可以被放在CoroutineContext中,官方頁面[在此](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/),分為兩種Job: - Coroutine job: 由launch所產生的Job,當該程式碼片段執行完後即completed。 - CompletableJob: 由Job()這個factory function所產生的Job,要調用CompletableJob.complete才會使它completed。 5. **CoroutineDispatcher**: CoroutineDispatcher決定了這個coroutine會在哪個thread上面運行。官方頁面[在此](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/),而更多細節可以看這個[官方說明](https://kotlinlang.org/docs/reference/coroutines/coroutine-context-and-dispatchers.html#dispatchers-and-threads)。實際在使用上,我們會用Kotlin提供的Dispatchers這個class,以下簡單介紹: - Dispatchers.Default: 這是預設使用的CoroutineDispatcher,他會在背景的thread pool裡面挑一個thread來用,是worker thread不是main thread。適合CPU密集的coroutine。 - Dispatchers.IO 這也是從背景的thread pool裡面挑一個worker thread來用,適合IO密集的coroutine。(這裡的超連結是空的,因為點IO好像會產生超連結我弄不掉XD) - Dispatchers.Main: 這個會讓coroutine在main thread中運行。這個好像是Android特有的,需要Android相關的dependency。 在coroutine基本的元件都知道後,接下來就可以看程式了。 以下介紹兩種用法,第一種是用GlobalScope,不建議使用,但因為寫法太簡單通常都是剛開始學Coroutine時必學的東西,程式碼如下: ``` import ... class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // launch test GlobalScope.launch { println("I'm here!") } } } ``` 可以發現基本上就是加了個GlobalScope.launch{}而已,而這邊要講一下GlobalScope,他是實作CoroutineScope,要使用coroutine的時候一定要有這個CoroutineScope才行,因為這個scope定義了coroutine可運行的範圍,你可以把這個scope跟lifecycle綁在一起(就是待會要介紹的第二種用法)、跟ViewModel綁一起或是跟LiveData綁在一起。而[不建議使用GlobalScope](https://kotlinlang.org/docs/reference/coroutines/basics.html#structured-concurrency)的原因是它是top-level的,就算你的Activity或Fragment被砍掉了GlobalScope依舊存在,所以要留好它的reference來手動停止它。此外它也會使用較多的記憶體。 下面介紹第二種,我把CoroutineScrope跟Activity的lifecycle綁在一起(就是讓Activity去實作CoroutineScope),也很簡單,如果要用基本的coroutine就直接照下面的用吧: ``` import ... class MainActivity : AppCompatActivity(), CoroutineScope { override val coroutineContext: CoroutineContext get() = Job() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // launch test launch { println("I'm here!") println("${this.coroutineContext.isActive}") } // launch using specific dispatcher launch(Dispatchers.IO) { val url = "https://xxx.com/api" val client = OkHttpClient(); val request = Request.Builder() .url(url) .build() try { val response = client.newCall(request).execute() println(response) } catch (e: IOException) { println(e) } } } } ``` 這邊放了一個Job作為coroutine的context,這也是官方說傳統上會使用的方法,讓並發更結構化(structured concurrency實在是太難翻譯了QQ),而不是粗暴地使用top-level的GlobalScope。 上面程式碼當中,第一個launch沒有指定dispatcher,所以會用Dispatchers.Default,而第二個launch因為有指定,所以會用Dispatchers.IO(這邊這個超連結也是空的,一樣是因為點IO)。 好目前就先筆記到這樣,之後如果有新內容再補充。
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up