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結合起來用的。