Android中HandlerThread的使用及原始碼解析

孫群發表於2015-08-06

關於Hanlder的基本使用可以參見博文《Android中Handler的使用》,如果想了解Handler、Looper、Thread等的相互關係以及內部實現原理可以參見博文《深入原始碼解析Android中的Handler,Message,MessageQueue,Looper》

Android中的API中對HandlerThread的描述是:
Handy class for starting a new thread that has a looper. The looper can then be used to create handler classes. Note that start() must still be called.
意思是HandlerThread類可以很方便地建立一個帶有looper的新執行緒。該looper可以被用來建立hanlder物件。需要注意的是start方法必須要呼叫。

先拋開HanlderThread,我們不用這個類看看怎麼使用Handler、Thread、Looper。

我們可以通過Looper.myLooper()方法得到當前執行緒所關聯的looper物件。在建立一個新執行緒的時候,初始情況下新執行緒是沒有關聯looper以及對應的訊息佇列MessageQueue的,對外表現出來就是在該新執行緒中呼叫Looper.myLooper()返回null。如果我們沒有意識到這一點,那麼我們在新執行緒中使用Handler肯能就會遇到問題。

假設為了在新執行緒中使用使用Handler,我們可能會寫出如下的程式碼:

class TestThread extends Thread {
      public Handler mHandler;

      public void run() {
          mHandler = new Handler() {
              public void handleMessage(Message msg) {
                  // process incoming messages here
              }
          };
      }
  }

但是在實際執行的時候會發現當執行到mHandler = new Handler()這一句時就會丟擲異常:
Can’t create handler inside thread that has not called Looper.prepare()
之所以會丟擲異常,可參見Handler建構函式的原始碼

丟擲異常的原因是: 我們在建構函式中沒有傳遞Looper,這樣Hanlder在建構函式中就使用預設的looper,預設的looper是通過呼叫Looper.myLooper()得來的。當我們呼叫了Looper.prepare()之後,我們就會將looper關聯到當前執行緒中。因此只有在呼叫了Looper.prepare()這個方法之後,Looper.myLooper()才能得到looper物件。所以這裡提示我們要先呼叫Looper.prepare()方法才行。

為了能在新執行緒中正常建立使用Handler,我們將程式碼改成如下所示:

class LooperThread extends Thread {
      public Handler mHandler;
      public void run() {
          Looper.prepare();
          mHandler = new Handler() {
              public void handleMessage(Message msg) {
                  //此處處理訊息
              }
          };
          Looper.loop();
      }
}

我們在新執行緒的run方法中,首先呼叫了Looper.prepare()方法,這樣就將looper物件關聯到當前執行緒中了,然後執行new Handler(),在Hanlder的建構函式內部會呼叫Looper.myLooper()得到當前執行緒所關聯的looper物件。在建立完Hanlder物件之後,我們需要呼叫Looper.loop()方法讓訊息佇列迴圈起來。

通過上面的程式碼我們就可以在一個新執行緒中建立並使用Handler物件了,但是問題是每次這麼寫感覺很羅嗦,不方便。為了讓能開發者更方便地在新執行緒中建立並使用Handler,Android提供了HandlerThread這個類,HandlerThread是繼承自Thread類的。

使用HandlerThread的示例程式碼如下:

HandlerThread handlerThread = new HandlerThread("TestHandlerThread");
handlerThread.start();
Handler handler = new Handler(handlerThread.getLooper()) {
    public void handleMessage(Message msg) {
        //此處處理訊息
    };
};

我們建立了HandlerThread之後需要先呼叫其start方法,呼叫start方法之後,run方法就會在HanlderThread執行緒中執行了。

HandlerThread這個類的run方法的原始碼如下所示:

public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
}

我們可以看到,在該run方法中也是先呼叫了Looper.prepare()方法,然後通過Looper.myLooper()方法得到該執行緒所關聯的looper物件,最後會呼叫Looper.loop()方法讓訊息佇列迴圈起來。由此可以看出,HandlerThread的run方法主要就是將我們上面給出的正常情況下在新執行緒中建立Handler的程式碼做了一些封裝而已。 在建立HandlerThread物件並呼叫其start方法之後,該HandlerThread執行緒就已經關聯了looper物件(通過Looper.prepare()方法關聯),並且該執行緒內部的訊息佇列迴圈了起來(通過Looper.loop()方法)。 最後我們只需要在建立Handler物件的時候通過handlerThread.getLooper()將handlerThread執行緒所關聯的looper物件傳遞給Handler的建構函式即可。 正如本文開頭API對HandlerThread所解釋的那樣: HandlerThread類可以很方便地建立一個帶有looper的新執行緒。該looper可以被用來建立hanlder物件。需要注意的是start方法必須要呼叫。

HandlerThread使用起來之所以感覺方便,是因為HandlerThread這個類在run方法內部對Looper做了一些工作(呼叫Looper.prepare()和Looper.loop()方法),這樣我們開發者在使用的時候就不需要太多的與Looper打交道了,從而提升開發的便利性。HandlerThread並不是很高深的,只是對我們常見的開發流程做了封裝而已,因此我們不用HandlerThread而自己去實現也是可以的,具體用不用HandlerThread根據自己的喜好而定。

相關文章