0. 前言
Android 系統中,應用在執行時是一個獨立的程式,但是每個程式中可以包含多個執行緒提高執行效率。在多執行緒開發中,有一個很重要的原則:不能在子執行緒中更新 UI 。
Only the original thread that created a view hierarchy can touch its views.
為解決這個問題,目前有多重方案實現子執行緒和主執行緒(UI 執行緒)之間的通訊。
1. 判斷程式碼執行的執行緒
在一些簡單程式碼邏輯中,也許能夠很清晰的辨別出執行在子執行緒或主執行緒中。通常在複雜的類關係依賴、函式巢狀呼叫中,可能需要花費很大精力去閱讀程式碼之後去判斷。不過,巧法子也是有的,一行程式碼解決。
Log.d("TAG","test");
複製程式碼
日誌內容中,2368-2393
表示是在子執行緒中輸出日誌。
11-16 01:08:31.584 2368-2393/com.flueky.demo D/TAG: test
其中 2368 表示 PID
指程式id, 2393 表示 TID
指執行緒id 。如果 TID
也是 2368 ,則表示日誌輸出在主執行緒中。
可能也有人聽過 UID
,應用第一次安裝在裝置上時,系統會分配一個序號給應用,作為其唯一標識。UID
在覆蓋安裝時不會變化,解除安裝安裝時系統會重新分配一個。
下面是在程式碼中獲取三個 id 的方式。
// 獲取 tid
Process.myTid()
// 獲取 pid
Process.myPid()
// 獲取 uid
Process.myUid()
複製程式碼
遇到需要在子執行緒中更新 UI 操作時,可以通過下面的這些方式解決。
2. 使用 View.post
子執行緒程式碼執行在 Activity 或 Fragment 中,能獲取到任意 view 的引用時,可以使用此方式將需要實現的程式碼放在主執行緒中執行。
// post 方法在子執行緒中呼叫
textView.post(new Runnable() {
@Override
public void run() {
// 此處程式碼會在 UI 執行緒執行
}
});
複製程式碼
3. 使用 Activity.runOnUiThread
如果能夠直接獲取到 Activity 例項,使用 runOnUiThread 方法。
// runOnUiThread 方法在子執行緒中呼叫
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
// 此處程式碼會在 UI 執行緒執行
}
});
複製程式碼
4. 使用 Handler.post
使用 Handler 比較講究,因為需要考慮到 Handler 例項初始化的位置。
// post 方法在子執行緒中呼叫
handler.post(new Runnable() {
@Override
public void run() {
// handler 在主執行緒中初始化時,此處程式碼在主執行緒中執行
// handler 在子執行緒中初始化事,此處程式碼在子執行緒中執行
}
});
複製程式碼
以上說法其實不夠嚴謹,存在下面的情況,初始化 handler 例項時傳入 Looper.getMainLooper() ,則 handler.post 也在主執行緒中執行。
// 下面的程式碼在子執行緒中執行
Looper.prepare();
handler = new Handler(Looper.getMainLooper());
Looper.loop();
複製程式碼
5. 使用 EventBus
EventBus
出自 greenrobot ,通過訂閱的方式,告知函式執行在哪個執行緒中。為使訂閱函式在主執行緒中執行,使用註解 MAIN 或 MAIN_ORDERED 。
/**
* eventbus 簡單示例
*/
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
/**
* 訂閱函式,
* ThreadMode.MAIN 表示在主執行緒中執行,可能會阻塞子執行緒。
* ThreadMode.MAIN_ORDERED 表示在主執行緒中執行,不會阻塞子執行緒。
*/
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(Object event) {
if(event instanceof Runnable)
((Runnable)event).run();
}
@Override
protected void onStart() {
super.onStart();
// 註冊 eventbus 監聽
EventBus.getDefault().register(this);
}
@Override
protected void onStop() {
super.onStop();
// 登出 eventbus 監聽
EventBus.getDefault().unregister(this);
}
}
// 在子執行緒中傳送訊息
EventBus.getDefault().post(new Runnable() {
@Override
public void run() {
// 此處程式碼會在 UI 執行緒執行
}
});
複製程式碼
6. 傳遞資料
前面四種方式演示瞭如何在子執行緒中做更新 UI 操作。 AsyncTask
也具備相同用法,但是有點牽強,因為只有 execute 方法在主執行緒中執行,onPostExecute 才會在主執行緒中呼叫。由於 onPostExecute 可以接收到子執行緒傳遞的任意型別的物件資料,所以 AsyncTask
可以作為執行緒間的資料互動的載體。對此 Handler 和 EventBus 表示不服。
EventBus 如之前所示,可以將 Runnable 物件換成任意例項。
Handler 也可以通過 sendMessage 方法傳送 Message 物件。其中 Message.obj 用作傳遞物件資料的載體。
建議使用 Message.obtain() 方法複用 Message 例項。
順便提下,BroadcastReceiver
也可以作為此類用途,只不過沒有 EventBus 和 Handler 方便。
覺得有用?那打賞一個唄。去打賞
個人主頁已經更新 ,歡迎收藏flueky.github.io/。