一、前言
其實(shí),這篇文章本來應(yīng)該叫做"Handler源碼解析"的,但是想想寫Handler源碼的文章太多了,還是緩一緩,先寫些不一樣的,今天,我們從源碼的角度來總結(jié)一下應(yīng)用Handler的一些場景的原理。
二、Handler 的應(yīng)用
2.1 線程間的通信
在平時(shí)的開發(fā)當(dāng)中,Handler最常見的用法就是用于線程之間的通信,特別是當(dāng)我們在子線程中去處理耗時(shí)的任務(wù),當(dāng)任務(wù)完成之后,我們希望將結(jié)果發(fā)送到主線程中進(jìn)行處理,那么就會使用到Handler,基本的思想如下圖所示:

當(dāng)我們在子線程中通過
Handler放入在主線程中的循環(huán)隊(duì)列時(shí),那么主線程就會收到消息,之后,我們就可以在主線程中進(jìn)行后續(xù)的處理,例如下面這樣:
public class MainActivity extends AppCompatActivity {
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
Log.d("Handler", "HandleMessageThreadId=" + Thread.currentThread().getId());
}
};
private void startThread() {
new Thread() {
@Override
public void run() {
super.run();
Log.d("Handler", "ThreadId=" + Thread.currentThread().getId());
mHandler.sendEmptyMessage(0);
}
}.start();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d("Handler", "MainThreadId=" + Thread.currentThread().getId());
startThread();
}
}
打印出上面的線程Id,可以看到最后handleMessage收到消息的時(shí)候是在主線程當(dāng)中:

2.2 實(shí)現(xiàn)延時(shí)操作
除了線程間的通信之外,我們還可以通過Handler提供的sendXXXDelay方法,實(shí)現(xiàn)延時(shí)操作,延時(shí)操作有兩種目的:
- 一種是希望讓不那么緊急的任務(wù)延后執(zhí)行,例如在應(yīng)用啟動過程中,我們在
onCreate方法中的任務(wù)不是那個(gè)緊急,那么可以通過Handler發(fā)送一個(gè)延時(shí)消息出去,讓它不占用主線程去渲染布局的資源,從而提高應(yīng)用的啟動速度。 - 另一種就是防抖動操作,我們收到一個(gè)命令,并不是馬上執(zhí)行它,而是通過
Handler的sendxxxDelay方法延時(shí),如果在這段事件內(nèi)又有一個(gè)相同的命令來到了,那么就把之前的消息移除,再放入一個(gè)新的延時(shí)消息。
例如下面的例子,當(dāng)我們點(diǎn)擊一個(gè)按鈕之后,我們不立刻執(zhí)行任務(wù),而是一段時(shí)間之后仍然沒有收到第二次點(diǎn)擊事件采取執(zhí)行任務(wù):
private Handler mDelayHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.d("Handler", "handleMessage");
}
};
private void performClickDelay() {
Log.d("Handler", "performClickDelay");
mDelayHandler.removeMessages(1);
mDelayHandler.sendEmptyMessageDelayed(1, 500);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTv = (TextView) findViewById(R.id.tv);
mTv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
performClickDelay();
}
});
}
我們連續(xù)多次點(diǎn)擊按鈕之后,只收到了一次消息:

2.3 使用HandlerThread在異步線程執(zhí)行耗時(shí)操作
由于Looper其實(shí)是線程的一個(gè)私有變量(ThreadLocal),主線程可以有Looper,子線程同樣也可以有Looper,只不過從子線程中的Looper中的MessageQueue中取出消息之后,是在子線程當(dāng)中處理的,那么我們就可以通過它來執(zhí)行異步操作,其基本思想如下圖所示:

HandlerThread就是基于這一思想來實(shí)現(xiàn)的,關(guān)于HandlerThread的內(nèi)部實(shí)現(xiàn),可以參考之前的這篇文章 Android 異步任務(wù)知識梳理(2) - HandlerThread 源碼解析,它的本質(zhì)就是當(dāng)異步線程啟動之后,會初始化這個(gè)線程私有的Looper,因此,當(dāng)我們通過HandlerThread中的getLooper()方法獲得這個(gè)Looper之后,在通過這個(gè)Looper來創(chuàng)建一個(gè)Handler,那么這個(gè)Handler的handleMessage回調(diào)時(shí)所在的線程就是這個(gè)異步線程。
下面是一個(gè)簡單的事例:
private void useHandlerThread() {
final HandlerThread handlerThread = new HandlerThread("handlerThread");
System.out.println("MainThreadId=" + Thread.currentThread().getId() + ",handlerThreadId=" + handlerThread.getId());
handlerThread.start();
MyHandler handler = new MyHandler(handlerThread.getLooper());
handler.sendEmptyMessage(0);
}
private class MyHandler extends Handler {
MyHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
System.out.println("handleMessageThreadId=" + Thread.currentThread().getId());
}
}
我們打印出handleMessage的執(zhí)行時(shí)的線程ID,可以看到它就是HandlerThread的線程ID,因此我們就可以通過發(fā)送消息的方式,在異步線程執(zhí)行一些耗時(shí)的操作:

2.4 使用AsyncQueryHandler對ContentProvider進(jìn)行操作
如果你的本地?cái)?shù)據(jù)使用ContentProvider封裝的,那么對這些數(shù)據(jù)的增刪改查操作,為了不影響主線程,應(yīng)該在子線程中進(jìn)行操作,而AsyncQueryHandler就是系統(tǒng)為我們提供的很方便的工具類,它通過Handler + HandlerThread的方式,實(shí)現(xiàn)了異步的增刪改查。
它是在HandlerThread的基礎(chǔ)之上擴(kuò)展出來的,其基本思想如下圖所示,這一框架就保證了發(fā)起命令和接收回調(diào)是在同一個(gè)線程,而任務(wù)的執(zhí)行則是在另一個(gè)線程:

具體的實(shí)現(xiàn)原理可以參考這篇文章 Android 異步任務(wù)知識梳理(3) - AsyncQueryHandler 源碼解析。
當(dāng)然AsyncQueryHandler限制了只能操作通過ContentProvider封裝的數(shù)據(jù),我們可以參考它的思想,進(jìn)行擴(kuò)展,實(shí)現(xiàn)對于數(shù)據(jù)庫的增刪改查。
2.5 使用Handler機(jī)制檢測應(yīng)用中的卡頓問題
對于每個(gè)應(yīng)用程序來說,它的入口函數(shù)為ActivityThread中的main()方法:
public static void main(String[] args) {
//...
Looper.prepareMainLooper();
//...
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
而在main()函數(shù)的最后,則構(gòu)建一個(gè)在主線程中的循環(huán)隊(duì)列,之后應(yīng)用程序收到的事件之后,還主線程就會被喚醒,進(jìn)行事件的處理,假如這一處理的事件過長,那么就會發(fā)生ANR,因此,我們就可以通過計(jì)算主線程中對于單次消息的處理時(shí)間,從而間隔地判斷是否存在卡頓問題。
那么要怎么知道主線程中單次消息的處理時(shí)間呢,我們查看Looper的源碼:
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
final long traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycleUnchecked();
}
}
對于消息的處理對應(yīng)這句:
msg.target.dispatchMessage(msg);
而在這句話的前后,則調(diào)用了Printer類打印,那么我們就可以通過這兩次打印的之間的時(shí)長來獲得單次消息的處理時(shí)間。
public class MainLoopMonitor {
private static final String MSG_START = ">>>>> Dispatching";
private static final String MSG_END = "<<<<< Finished";
private static final int TIME = 1000;
private Handler mExecuteHandler;
private Runnable mExecuteRunnable;
private static class Holder {
private static final MainLoopMonitor INSTANCE = new MainLoopMonitor();
}
public static MainLoopMonitor getInstance() {
return Holder.INSTANCE;
}
private MainLoopMonitor() {
HandlerThread monitorThread = new HandlerThread("LooperMonitor");
monitorThread.start();
mExecuteHandler = new Handler(monitorThread.getLooper());
mExecuteRunnable = new ExecutorRunnable();
}
public void startMonitor() {
Looper.getMainLooper().setMessageLogging(new Printer() {
@Override
public void println(String x) {
if (x.startsWith(MSG_START)) {
mExecuteHandler.postDelayed(mExecuteRunnable, TIME);
} else if (x.startsWith(MSG_END)) {
mExecuteHandler.removeCallbacks(mExecuteRunnable);
}
}
});
}
private class ExecutorRunnable implements Runnable {
@Override
public void run() {
StringBuilder sb = new StringBuilder();
StackTraceElement[] stackTrace = Looper.getMainLooper().getThread().getStackTrace();
for (StackTraceElement s : stackTrace) {
sb.append(s).append("\n");
}
System.out.println("MainLooperMonitor:" + sb.toString());
}
}
}
假如我們像下面這樣,在主線程中進(jìn)行了耗時(shí)的操作:
public void anrButton(View view) {
for (int i = 0; i < (1 << 30); i++) {
int b = 6;
}
}
那么就會打印出下面的堆棧信息:

三、Handler 使用注意事項(xiàng)
在介紹完Handler的這些用法,我們再來總結(jié)一下使用Handler是比較容易犯的一些錯(cuò)誤。
3.1 內(nèi)存泄漏
一個(gè)比較常見的錯(cuò)誤就是將定義Handler的子類時(shí)將它作為Activity的內(nèi)部類,而由于內(nèi)部類會默認(rèn)持有外部類的引用,因此,如果這個(gè)內(nèi)部類的實(shí)例在Activity試圖被回收的時(shí)候,沒有被銷毀掉,那么就會導(dǎo)致Activity無法被回收,從而引起內(nèi)存泄漏。
而Handler實(shí)例無法被銷毀掉最常見的情況就是,我們通過它發(fā)送了一個(gè)延時(shí)消息出去,此時(shí)這個(gè)消息會被放入到該線程所對應(yīng)的Looper中的MessageQueue當(dāng)中,而該消息為了能在得到執(zhí)行之后,回調(diào)到對應(yīng)的Handler,因此它會持有這個(gè)Handler的實(shí)例。
從引用鏈的角度來看就是下面的情況:

這個(gè)大家見得很多,就不多舉例子了。
3.2 在子線程中實(shí)例化 new Handler()
在子線程中new Handler()有下面兩個(gè)需要注意的點(diǎn):
(1) 在 new Handler() 之前調(diào)用 Looper.prepare() 方法
另外一個(gè)錯(cuò)誤就是我們在子線程當(dāng)中,直接調(diào)用new Handler(),那么這時(shí)候會拋出異常,例如下面這樣:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
new Thread() {
@Override
public void run() {
mHandler = new MyHandler();
}
}.start();
}
拋出的異常為:

原因是在
Handler的構(gòu)造函數(shù)中,會去檢查當(dāng)前線程的Looper是否已經(jīng)被初始化:
public Handler(Callback callback, boolean async) {
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
如果沒有,那么就會拋出上面代碼當(dāng)中的異常,正確的做法,是需要保證我們在new Handler之前,要保證當(dāng)前線程的Looper對象已經(jīng)被初始化,也就是Looper當(dāng)中的下面這個(gè)方法被調(diào)用:
/** Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
* this looper, before actually starting the loop. Be sure to call
* {@link #loop()} after calling this method, and end it by calling
* {@link #quit()}.
*/
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
(2) 如果希望通過該 Looper 接收消息那么要調(diào)用 Looper.loop() 方法,并且需要放在最后一句調(diào)用
假如我們只調(diào)用了prepare()方法,僅僅只是初始化了一個(gè)線程的私有的變量,此時(shí)是無法通過這個(gè)Looper構(gòu)建的Handler來接收消息的,例如下面這樣:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
new Thread() {
@Override
public void run() {
Looper.prepare();
mHandler = new MyHandler();
}
}.start();
}
public void anrButton(View view) {
mHandler.sendEmptyMessage(0);
}
private static class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
Log.e("TAG", "handleMessageId=" + Thread.currentThread().getId());
}
}
此時(shí)我們應(yīng)當(dāng)調(diào)用loop方法讓這個(gè)循環(huán)隊(duì)列開始工作,并且該調(diào)用一定要位于最后一句,因?yàn)橐坏┱{(diào)用了loop方法,那么它就會進(jìn)入等待 -> 收到消息 -> 喚醒 -> 處理消息 -> 等待的循環(huán)過程,而這一過程只有等到loop方法返回的時(shí)候才會結(jié)束,因此,我們初始化Handler的語句要放在loop方法之前,類似于下面這樣:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
new Thread() {
@Override
public void run() {
Looper.prepare();
mHandler = new MyHandler();
//最后一句再調(diào)用
Looper.loop();
}
}.start();
}
四、小結(jié)
Handler的機(jī)制似乎已經(jīng)成為面試必問的題目,如果大家能在回答完內(nèi)部的實(shí)現(xiàn)原理,再根據(jù)這些實(shí)現(xiàn)原理引申出上面的幾個(gè)應(yīng)用和注意事項(xiàng),可以加分不少哦~
更多文章,歡迎訪問我的 Android 知識梳理系列:
- Android 知識梳理目錄:http://www.itdecent.cn/p/fd82d18994ce
- 個(gè)人主頁:http://lizejun.cn
- 個(gè)人知識總結(jié)目錄:http://lizejun.cn/categories/