為了良好的使用者體驗,Android對thread的限制十分嚴格(主要是UI thread),每次太久沒碰Android都會忘記該怎麼用,這邊簡單筆記一下
如果想直接看官方Android threading的doc的話就點這裡
最重要且不太會忘記的部分應該就是UI thread不能被block住,只要block超過五秒左右系統就會跳沒有APP回應的對話視窗。
接著就是thread或是asynchronous要怎麼產生怎麼用,主要就分成以下幾種機制
其實AsyncTask我有寫過記得滿好用的,但既然要被淘汰了那就算了XD
Rx感覺要學一下,好像是跟Observer pattern和asynchronous有關,但我還沒有用過,所以這次筆記不會寫
以下就針對Thread跟Coroutines做簡單筆記
在開始之前,先來講一下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的用法,官方的教學系列文在這邊。
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喔),但你也可以在其他地方寫一個class來實作Runable,這樣比較好重複使用。
coroutine是Kotlin的東西,如果你要查相關的document的話要去Kotlin那邊查,現在在Android開發中常常會用到。它基本用起來算簡單好用,但他背後若要細細探究也是可以花上很多時間,以下只介紹基本的用法,比較深的我自己也沒鑽研。
簡單來說,couroutine就是在一個thread中實現異步的一個機制,核心的class(明確地說是以下大寫的都是interface)有幾個:
在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的原因是它是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)。
好目前就先筆記到這樣,之後如果有新內容再補充。