1. 程式人生 > 其它 >Android多執行緒:HandlerThread的原理及使用

Android多執行緒:HandlerThread的原理及使用

前言

在Andorid實現多執行緒的方式中, HandlerThread 的使用並不常見,最近開始紮實Android基礎,我們都知道,若是在子執行緒中建立Handler例項並呼叫 sendMessage() 方法時,子執行緒由於並不會建立 LopperMessageQueue 物件,等同於訊息沒有入隊(MessageQuue),訊息也無法實現出隊迴圈(Looper),故在子執行緒傳送的訊息任務無法執行,這時候需要呼叫方法 Looper.prepare()和Looper.loop() 實現訊息的入隊、出隊、迴圈分發給指定的Handler。

為了解決這個問題,Android封裝了自己的HandlerThread,在內部呼叫方法 Looper.prepare()和Looper.loop()

,方便了開發人員的使用。

下面我們來揭開HandlerThread的神祕面紗:

1.是什麼

HandlerThread是Android封裝好的非同步訊息處理類,其原理即是繼承自Thread並封裝了Handler

2.為什麼

  • 保證多執行緒併發需要更新UI執行緒時的執行緒安全
  • 不需要使用任務執行緒(繼承自Thread)+ Handler的複雜組合,方便了開發人員使用建立新執行緒與其他執行緒通訊的過程。

3.怎麼做

原理:繼承的Thread類 + 封裝的Handler類

  • 繼承的Thread類:快速建立一個帶有Looper的工作執行緒
  • 封裝的Handler類:快速建立Handler與其他執行緒通訊

4.使用步驟

1)建立ThreadHandler例項物件mThreadHandler
2)開啟執行緒mThreadHandler.start()
3)建立工作執行緒的Handler例項,workHandler,並實現handleMessage方法
4)使用工作執行緒workHandler向工作執行緒的訊息佇列傳送訊息
workHandler.sendMessage(msg);
5)停止執行緒
mThreadHandler.quit()
mThreadHandler.quitSafely()

  • demo 示例完整程式碼
    模擬視窗賣票,每個視窗有6張票,工作執行緒workHandler傳送一條訊息則開啟一個視窗開始買票
public class HandlerThreadActivity extends AppCompatActivity {

    private int j = 1;
    private String note;
    private TextView note_text;
    private Handler workHandler = new Handler();
    private HandlerThread handlerThread;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler_thread);
        initView();
        initHandlerThread();
    }

    private void initHandlerThread() {

        //step1: 建立HandlerThread例項,傳入的引數為執行緒名稱
        handlerThread = new HandlerThread("based on yourself");

        //step2: 手動呼叫start()開啟執行緒
        handlerThread.start();

        //step3:
        //建立Handler,關聯HandlerThread的Looper
        //複寫handleMessage根據訊息更新UI佈局,處理訊息的執行緒即是建立的執行緒handlerThread
        workHandler = new Handler(handlerThread.getLooper()) {
            @Override
            public void handleMessage(final Message msg) {
                super.handleMessage(msg);
                switch (msg.what) {
                    case 1:
                        break;
                    case 2:
                        for (int i = 1; i < 7; i++) {
                            try {
                                Thread.sleep(1000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            note = msg.obj + "賣出" + i + "張票";
                            note_text.setText(note);
                            Log.d("workHandler----", note);
                        }
                        break;
                }
            }
        };
    }

    private void initView() {
        note_text = findViewById(R.id.note_text);
        Button startThread = findViewById(R.id.start_thread);
        startThread.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //step4: 點選一次Button,工作執行緒workHandler向工作執行緒佇列傳送訊息
                Message msg = Message.obtain(); //不用new一個Message,採用obtain
                msg.what = 2;
                msg.obj = "視窗" + j;
                workHandler.sendMessage(msg);
                j++;
            }
        });

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //step5: 停止handlerThread
        handlerThread.quit(); //結束執行緒,效率高
        handlerThread.quitSafely(); //結束執行緒
    }
}
  • 效果展示

Log日誌

  • 結論
    從demo展示中和列印的Log中看到,第一次點選Button視窗1開始賣票,視窗1賣第3張票時,再次點選Button,HandlerThread不會停止當前視窗賣票,而是等待當前賣完6張票之後後再開啟視窗2執行賣票任務。

5.應用場景

使用HandlerThread處理本地的 I/O操作(資料庫,檔案,SharePreferences),推薦使用postAtFrontOfQueue(),快速將讀取操作加入佇列的前端執行,必要時更新主執行緒UI。示例場景,從資料庫讀取資料顯示在ListView中。

6.總結

  • HandlerThread 繼承自 Thread,在複寫的 run() 方法中,呼叫了Looper.prepare()和Looper.loop(),方便在建立的子執行緒中使用Handler時需要自己手動呼叫Looper.prepare()和Looper.loop()。
  • HandlerThread 內部處理訊息佇列時是按順序 序列執行 ,即在處理完一條訊息任務後再處理下一條訊息任務,不適合處理 網路請求 需要並行處理的耗時任務,更適合處理本地的需要按序執行的 I/O操作
  • 缺點:若其中一條任務處理時間過長,仍然需要等待此條任務處理完成之後才處理下一條任務,會導致後續任務延時執行,造成 執行緒阻塞 的現象。
  • 一個執行緒對應一個Looper
  • Looper.prepare() 自動建立一個Looper物件,Looper建立時,自動建立MessageQueue物件,並賦值mThread為當前Thread,ThreadLocal中存入建立的Looper物件
  • Looper.loop() 開啟訊息迴圈

思考

  • 什麼是執行緒阻塞,造成原因有哪些?
  • HandlerThread需要手動呼叫quit()/quitSafely(),為什麼新建的子執行緒不用手動呼叫釋放Looper和MessageQueue呢?

連結:https://www.jianshu.com/p/ea66c76b66f5