Android線程間通訊

0. 前言

Android 系統中,應用在運行時是一個獨立的進程,可是每一個進程中能夠包含多個線程提升運行效率。在多線程開發中,有一個很重要的原則:不能在子線程中更新 UI 。java

Only the original thread that created a view hierarchy can touch its views.git

爲解決這個問題,目前有多重方案實現子線程和主線程(UI 線程)之間的通訊。github

1. 判斷代碼執行的線程

在一些簡單代碼邏輯中,也許可以很清晰的辨別出運行在子線程或主線程中。一般在複雜的類關係依賴、函數嵌套調用中,可能須要花費很大精力去閱讀代碼以後去判斷。不過,巧法子也是有的,一行代碼解決。多線程

Log.d("TAG","test");
複製代碼

日誌內容中,2368-2393 表示是在子線程中輸出日誌。ide

11-16 01:08:31.584 2368-2393/com.flueky.demo D/TAG: test函數

其中 2368 表示 PID 指進程id, 2393 表示 TID 指線程id 。若是 TID 也是 2368 ,則表示日誌輸出在主線程中。oop

可能也有人聽過 UID ,應用第一次安裝在設備上時,系統會分配一個序號給應用,做爲其惟一標識。UID 在覆蓋安裝時不會變化,卸載安裝時系統會從新分配一個。post

下面是在代碼中獲取三個 id 的方式。ui

// 獲取 tid
Process.myTid()
// 獲取 pid
Process.myPid()
// 獲取 uid
Process.myUid()
複製代碼

遇到須要在子線程中更新 UI 操做時,能夠經過下面的這些方式解決。this

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 ,經過訂閱的方式,告知函數運行在哪一個線程中。爲使訂閱函數在主線程中執行,使用註解 MAINMAIN_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 能夠做爲線程間的數據交互的載體。對此 HandlerEventBus 表示不服。

EventBus 如以前所示,能夠將 Runnable 對象換成任意實例。

Handler 也能夠經過 sendMessage 方法發送 Message 對象。其中 Message.obj 用做傳遞對象數據的載體。

建議使用 Message.obtain() 方法複用 Message 實例。

順便提下,BroadcastReceiver 也能夠做爲此類用途,只不過沒有 EventBusHandler 方便。

以爲有用?那打賞一個唄。去打賞

我的主頁已經更新 ,歡迎收藏flueky.github.io/

相關文章
相關標籤/搜索