淺析HandlerThread
背景
首先呢胖腾?HandlerThread面試的時(shí)候有的會(huì)問拖刃,但是面試官不直接問你是否知道HandlerThread以及用途和實(shí)現(xiàn)?面試官會(huì)問你:面試必問的一個(gè)題目:
handler的消息機(jī)制等一系列問題洋侨,如果你說的還算可以矾瑰,那么問題來了?
接下來會(huì)問你假如在一個(gè)子線程(工作線程)中怎么使用handler酵镜?嘻嘻 碉碉,如果你知道handler的消息機(jī)制,那么這個(gè)問題很好回答了淮韭,代碼如下:
private final class WorkThread extends Thread {
private Handler mHandler;
public Handler getHandler() {
return mHandler;
}
public void quit() {
mHandler.getLooper().quit();
}
@Override
public void run() {
super.run();
//創(chuàng)建該線程對(duì)應(yīng)的Looper,
// 內(nèi)部實(shí)現(xiàn)
// 1垢粮。new Looper()
// 2。將1步中的lopper 放在ThreadLocal里靠粪,ThreadLocal是保存數(shù)據(jù)的蜡吧,主要應(yīng)用場(chǎng)景是:線程間數(shù)據(jù)互不影響的情況
// 3。在1步中的Looper的構(gòu)造函數(shù)中new MessageQueue();
//其實(shí)就是創(chuàng)建了該線程對(duì)用的Looper占键,Looper里創(chuàng)建MessageQueue來實(shí)現(xiàn)消息機(jī)制
//對(duì)消息機(jī)制不懂得同學(xué)可以查閱資料昔善,網(wǎng)上很多也講的很不錯(cuò)。
Looper.prepare();
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.d("WorkThread", (Looper.getMainLooper() == Looper.myLooper()) + "," + msg.what);
}
};
//開啟消息的死循環(huán)處理即:dispatchMessage
Looper.loop();
//注意這3個(gè)的順序不能顛倒
Log.d("WorkThread", "end");
}
}
這里我們可以測(cè)試下在另外一個(gè)子線程里通過WorkThread的getHandler給它發(fā)消息看下結(jié)果:
public void send(View view) {
new SendThread(mWorkThread.getHandler()).start();
}
private final class SendThread extends Thread {
private Handler mHandler;
SendThread(Handler handler) {
this.mHandler = handler;
}
@Override
public void run() {
super.run();
for (int i = 0; i < 3; i++) {
mHandler.sendEmptyMessage(0x1);
SystemClock.sleep(1000);
}
}
}
log:可以看到收到了消息并打印了是在非主線程
02-15 10:45:19.290 22660-22738/com.gxz.study D/WorkThread: false,1
02-15 10:45:20.290 22660-22738/com.gxz.study D/WorkThread: false,1
02-15 10:45:21.291 22660-22738/com.gxz.study D/WorkThread: false,1
這里有同學(xué)問 小伙子畔乙,你這里不對(duì)君仆, Log.d("WorkThread", "end");沒打印。是的,Looper.loop();為阻塞函數(shù)返咱,只有當(dāng)調(diào)用mHandler.getLooper().quit()或者quitSafely()方法后钥庇,loop才會(huì)中止.那2個(gè)方法有毛區(qū)別呢?
當(dāng)我們調(diào)用Looper的quit方法時(shí)洛姑,實(shí)際上執(zhí)行了MessageQueue中的removeAllMessagesLocked方法上沐,該方法的作用是把MessageQueue消息池中所有的消息全部清空,無論是延遲消息(延遲消息是指通過sendMessageDelayed或通過postDelayed等方法發(fā)送的需要延遲執(zhí)行的消息)還是非延遲消息楞艾。
當(dāng)我們調(diào)用Looper的quitSafely方法時(shí)参咙,實(shí)際上執(zhí)行了MessageQueue中的removeAllFutureMessagesLocked方法,通過名字就可以看出硫眯,該方法只會(huì)清空MessageQueue消息池中所有的延遲消息蕴侧,并將消息池中所有的非延遲消息派發(fā)出去讓Handler去處理,quitSafely相比于quit方法安全之處在于清空消息之前會(huì)派發(fā)所有的非延遲消息两入。
無論是調(diào)用了quit方法還是quitSafely方法只會(huì)净宵,Looper就不再接收新的消息。即在調(diào)用了Looper的quit或quitSafely方法之后裹纳,消息循環(huán)就終結(jié)了择葡,這時(shí)候再通過Handler調(diào)用sendMessage或post等方法發(fā)送消息時(shí)均返回false,表示消息沒有成功放入消息隊(duì)列MessageQueue中剃氧,因?yàn)橄㈥?duì)列已經(jīng)退出了敏储。
需要注意的是Looper的quit方法從API Level 1就存在了,但是Looper的quitSafely方法從API Level 18才添加進(jìn)來朋鞍。
HandlerThread的用法
前面扯遠(yuǎn)了已添,但是你必須知道的東西。那就有同學(xué)問了好麻煩啊上面的滥酥,假入我以后在工作中用的話我還得注意 Looper.prepare(); new mHandler(); Looper.loop();的順序更舞。呵呵。坎吻。缆蝉。
哈哈,同學(xué)禾怠,這個(gè)HandlerThread就是解決這個(gè)的返奉。google工程師早知道你會(huì)這么問。
我們先看怎么用吗氏,我們把上面的測(cè)試?yán)佑肏andlerThread實(shí)現(xiàn)然后在分析內(nèi)部原理芽偏。
//1.初始化,參數(shù)為名字,也就是線程的名字弦讽,后面我們會(huì)結(jié)合源碼來看
mHandlerThread = new HandlerThread("WorkThread");
//必須調(diào)用start方法污尉,因?yàn)镠andlerThread繼承自Thread來啟動(dòng)線程
mHandlerThread.start();
//初始化Handler,只是傳遞了一個(gè)mHandlerThread內(nèi)部的一個(gè)looper
mHandler = new Handler(mHandlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.d("WorkThread", (Looper.getMainLooper() == Looper.myLooper()) + "," + msg.what);
}
};
//2.使用
public void send(View view) {
new SendThread(mHandler).start();
}
private final class SendThread extends Thread {
private Handler mHandler;
SendThread(Handler handler) {
this.mHandler = handler;
}
@Override
public void run() {
super.run();
for (int i = 0; i < 3; i++) {
mHandler.sendEmptyMessage(0x1);
SystemClock.sleep(1000);
}
}
}
//3.別忘記釋放膀哲,停止Looper消息循環(huán),內(nèi)部還是調(diào)用了looper的quit,及時(shí)釋放防止內(nèi)存泄漏哦1煌搿某宪!
mHandlerThread.quit();
結(jié)果(一毛一樣哦):
02-15 11:11:24.738 13536-13787/com.gxz.study D/WorkThread: false,1
02-15 11:11:25.739 13536-13787/com.gxz.study D/WorkThread: false,1
02-15 11:11:26.740 13536-13787/com.gxz.study D/WorkThread: false,1
因此
- 如果我們有耗時(shí)的任務(wù)處理可以通過HandlerThread獲取looper,looper進(jìn)而構(gòu)造Handler,然后通過Handler的post(Runnable r)在handleMessage里進(jìn)行處理耗時(shí)處理锐朴。
- 很方便進(jìn)行線程間的通信兴喂。
HandlerThread的源碼分析
- 原理:HandlerThread其實(shí)是extends Thread,內(nèi)部的run方法調(diào)用了Looper.prepare()方法和 Looper.loop()焚志;哈哈衣迷,和我們之前寫的第一種方法一樣。
public class HandlerThread extends Thread {
int mPriority;//線程優(yōu)先級(jí)
int mTid = -1;//線程id
Looper mLooper;//Looper對(duì)象
public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
- 對(duì)于線程酱酬,我們還是主要看run方法壶谒,源碼很短,直接全部貼了膳沽。我們清楚的看到了Looper.prepare和Looper.loop的創(chuàng)建汗菜,這里和最開始說的一樣。
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
//后面會(huì)分析
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
但是這多了一個(gè)onLooperPrepared()方法挑社,這里可以根據(jù)名字看出來陨界,當(dāng)Looper.prepare()創(chuàng)建完后,我們可以做一些初始化的東西痛阻,這當(dāng)然是在子線程里普碎,我們看下這個(gè)方法的實(shí)現(xiàn)÷计剑空實(shí)現(xiàn),如果你要做些初始化的準(zhǔn)備工作可以extends HandlerThread重寫onLooperPrepared方法即可缀皱。
/**
* Call back method that can be explicitly overridden if needed to execute some
* setup before Looper loops.
*/
protected void onLooperPrepared() {
}
- 退出/結(jié)束方法:quit()方法或quitSafely()方法斗这。實(shí)際上是調(diào)用了looper.quit方法和looper.quitSafely方法,上文中的區(qū)別也說到了啤斗。
public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}
public boolean quitSafely() {
Looper looper = getLooper();
if (looper != null) {
looper.quitSafely();
return true;
}
return false;
}
- 最后還有一點(diǎn)表箭,就是在上面的例子中我們構(gòu)造Handler的時(shí)候傳入了Looper,通過HandlerThread的getLooper(),源碼如下钮莲。這里如果線程沒有start活著一些其它原因免钻,該線程沒有處于一種存活的狀態(tài)會(huì)返回null,
/**
* This method returns the Looper associated with this thread. If this thread not been started
* or for any reason is isAlive() returns false, this method will return null. If this thread
* has been started, this method will block until the looper has been initialized.
* @return The looper.
*/
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;
}
該函數(shù)有可能阻塞崔拥,目的是為了防止獲取的looper對(duì)象為空极舔,使用了wait方法,并且使用了局部synchronized链瓦,鎖住了當(dāng)前的對(duì)象拆魏,那什么時(shí)候喚醒等待呢盯桦?當(dāng)然是初始化完該線程關(guān)聯(lián)looper對(duì)象的地方,也就是run方法渤刃。也就是你構(gòu)造完HandlerThread必須調(diào)用start方法拥峦。對(duì)于synchronized和notifyAll和wait一些線程同步處理的操作,可以參考一些資料卖子。
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
結(jié)束
這里我們學(xué)習(xí)了HandlerThread類的簡(jiǎn)單用法以及內(nèi)部源碼以及實(shí)現(xiàn)原理略号。
同時(shí)也介紹了一些面試必問handler的消息機(jī)制。so洋闽,當(dāng)再有面試官問你文章最開始的在一個(gè)子線程(工作線程)中怎么使用handler的問題玄柠,你可以說下怎么使用,同時(shí)最后說一個(gè)android提供的類-HandlerThread也很不錯(cuò)哦:暗荨随闪!