1. 程式人生 > >Android HandlerThread原始碼解析

Android HandlerThread原始碼解析

在上一章Handler原始碼解析文章中,我們知道App的主執行緒通過Handler機制完成了一個執行緒的訊息迴圈。那麼我們自己也可以新建一個執行緒,線上程裡面建立一個Looper,完成訊息迴圈,可以做一些定時的任務或者寫日誌的功能。這就是HandlerThread的作用
Android Handler訊息機制原始碼解析

1 使用方法如下

在MainActivity中新增一個HandlerThread的變數,如下:

public class MainActivity extends AppCompatActivity {
    HandlerThread thread = new HandlerThread("test");
    Handler handler;

在 onCreate()函式中開啟執行緒,獲取執行緒的looper,如下:

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //1 開啟執行緒
        thread.start();

        //2 獲取執行緒對應的looper,並用這個looper構造出一個Handler
        //3 並重寫Handler的handleMessage()方法
        handler = new Handler(thread.getLooper()){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                if(msg.what == 100){
                    Log.e("TAG","執行緒名=" + Thread.currentThread().getName());
                    Log.e("TAG","接收到的資料為:" + msg.obj.toString());
                }
            }
        };

        findViewById(R.id.tv_hello).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e("TAG","執行緒名:" + Thread.currentThread().getName());
                Message message = handler.obtainMessage();
                message.what = 100;
                message.obj = "hello world";
                handler.sendMessage(message);
            }
        });
    }

點選事件,輸出如下:

2018-11-24 12:49:06.575 13589-13589/com.fax E/TAG: 執行緒名:main
2018-11-24 12:49:06.576 13589-13612/com.fax E/TAG: 執行緒名=test
2018-11-24 12:49:06.576 13589-13612/com.fax E/TAG: 接收到的資料為:hello world

由上面可以看到,我們新建了一個Handler,對應的looper是從HandlerThread例項thead獲取的,我們在點選事件中,獲取一個訊息並用handler分發,主執行緒傳送的訊息,在子執行緒中處理了。

比如我們有這樣一個需求:
在使用者使用APP的時候,需要記錄使用者的行為,需要把日誌記錄到本地檔案中,等到一定的時機我們再統一一次性把檔案上傳到我們的伺服器。

那麼我們就可以開一個執行緒,在後臺等待寫日誌的任務的訊息到來,收到訊息後就把日誌順序的寫入到檔案中。這時就可以用HandlerThread,省去了我們自己開執行緒,寫任務佇列,完成訊息迴圈,這些HandlerThread都幫我們封裝好了。下面我們來分析HandlerThread的原始碼。

2 HandlerThread原始碼分析

首付在使用的時候,我們直接 new 了一個HandlerThread物件 HandlerThread thread = new HandlerThread("test");

HandlerThread類定義如下:

public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;
    private @Nullable Handler mHandler;
  
    ......
}

HandlerThread從字面意思上看,是一個和Handler結合起來用的Thread。
再看HandlerThread的建構函式:

public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }

建構函式僅僅是對執行緒的優先順序和名字進行賦值。
接著往下看,我們呼叫了 thread.start() ,由於HandlerThread是一個繼承Thread,所以會呼叫run()方法,原始碼如下:

  @Override
    public void run() {
        //1 儲存執行緒的id,沒什麼好說的
        mTid = Process.myTid();
        
        //2 主要是這句,呼叫了Looper.prepare()
        // 由上篇Handler原始碼分析可知,這裡建立了一個Looper物件
        Looper.prepare();
        
        //3 獲取當前執行緒對應的Looper物件,儲存起來
        // 加鎖是為了防止多執行緒問題
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        
        //4 在迴圈之前有一個回撥,空實現
        onLooperPrepared();
        
        //5 進行訊息迴圈
        Looper.loop();
        mTid = -1;
    }

通過上面可知:
1 HandlerThread就是一個執行緒類,在run()方法的開頭呼叫了Looper.prepare()來建立一個執行緒對應的Looper物件,並儲存起來。
2 線上程的最後面呼叫了Looper.loop()對訊息進行迴圈。

所以如果外面想要用的話,HandlerThread必須有一個對外的方法,來返回當前執行緒對應的Looper物件,找一下原始碼,果然有一個getLooper()方法:原始碼如下:

    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        
        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

返回當前執行緒的Looper例項,這樣外面想用這個的時候,就可以呼叫getLooper()獲取Looper物件,然後再建立一個Handler物件,並把looper傳入,這樣就可以在其它執行緒中傳送訊息,在當前建立的子執行緒中處理了。

既然這樣,那麼有沒有這樣一個方法,直接返回對應的Handler呢,裡面就儲存了Looper物件。還真有這樣一個方法,如下:

  /**
     * @return a shared {@link Handler} associated with this thread
     * @hide
     */
    @NonNull
    public Handler getThreadHandler() {
        if (mHandler == null) {
            mHandler = new Handler(getLooper());
        }
        return mHandler;
    }

看這個方法,new 了一個Handler物件,並呼叫getLooper()把當前的Looper物件傳入了,並返回了當前這個Handler物件

但是我們注意到,這個方法是@hide,就是我們在外面並不能呼叫這個方法,為什麼Google已經寫了這個方法但是又把這個方法給隱藏起來了不讓我們呼叫呢?

個人猜測是因為我們呼叫getThreadHandler()的前提是得先呼叫start()方法,有了Looper物件後才能呼叫這個方法,要不獲取到的Handler裡面是沒有Looper例項的,也就沒法完成訊息迴圈,所以Google把這個方法給隱藏了。

所以我們還是像上面的那樣用法,先start(),再獲取Looper物件,再建立Handler物件。

那麼執行緒有執行的時候,也應該有退出的時候,當前有,我們看quit()方法:

 public boolean quit() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quit();
            return true;
        }
        return false;
    }

這就是HandlerThread的原始碼,下篇我們講IntentService的原始碼,和HandlerThread結合起來用的。