前言:窮則變,變則通谎僻,通則久。——《周易》
Handler一般用于線程間的通信晴氨,通常項(xiàng)目中的異步實(shí)現(xiàn)都是基于Handler來實(shí)現(xiàn)的,前面在學(xué)習(xí)IntentService的時(shí)候已經(jīng)說過了碉输。今天主要是要理清一下Handler——Looper——MessageQueue之間的業(yè)務(wù)往來籽前。
Handler構(gòu)造方法
public Handler()
public Handler(Callback callback)
public Handler(Looper looper)
public Handler(Looper looper, Callback callback)
@hide public Handler(Callback callback, boolean async)
@hide public Handler(Looper looper, Callback callback, boolean async)
Handler的構(gòu)造方法分為兩種
#使用默認(rèn)的Looper
Handler(Callback callback, boolean async)
#使用自定義的Looper進(jìn)行初始化
Handler(Looper looper, Callback callback, boolean async)
async會為所有的Message和Runnable標(biāo)記setAsynchronous(boolean),不過所有的構(gòu)造函數(shù)都會給這個值傳遞false。
通常情況下敷钾,我們都會使用無參的構(gòu)造函數(shù)枝哄,然后重寫里面的handleMessage實(shí)現(xiàn)方法。我們在使用Handler的時(shí)候阻荒,相信每一位都見到過以下的警告信息
或者
這個Handler類應(yīng)該設(shè)置為靜態(tài)挠锥,否則可能會發(fā)生泄漏(只是可能而已,并不是說那么容易就發(fā)生內(nèi)存泄漏的侨赡,事實(shí)上很多時(shí)候都不會出現(xiàn)這種內(nèi)存泄漏的問題蓖租,不然我們的APP早一百年前就崩潰了)。接下來研究怎么才能去掉這個警告羊壹,我們來仔細(xì)研究Handler的這個使用默認(rèn)Looper的構(gòu)造方法
public Handler(Callback callback, boolean async) {
//是否有內(nèi)存泄漏的可能性
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
//警告 Handler類應(yīng)該是靜態(tài)的蓖宦,否則可能會發(fā)生內(nèi)存泄漏
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
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)我們把這個類定義成匿名,成員油猫,局部類的時(shí)候稠茂,并且這個類沒有static修飾符。那么就會拋出這個警告眨攘。那么去掉這個警告也就很簡單了主慰,添加static。
在這個構(gòu)造函數(shù)中鲫售,通過Looper.myLooper()獲取當(dāng)前線程所關(guān)聯(lián)的Looper對象共螺,同時(shí)也獲取到Looper內(nèi)的MessageQueue對象,至此情竹,消息傳遞的兩個重要要素MessageQueue和Looper就初始化完成了藐不。
//獲取當(dāng)前線程關(guān)聯(lián)的Looper對象
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
另一種形式的構(gòu)造函數(shù)因?yàn)樘^簡單,就沒什么好說的了
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
對于這種構(gòu)造方法,我們來看一下用法即可雏蛮,使用自定義的Looper涎嚼,要確保這個Looper已經(jīng)prepare才行,真的是書讀百遍其義自見挑秉,關(guān)于Looper我想很多人并不是那么熟悉法梯,所以真正懂得Handler的人可以好好鞭策一下那些面試者啊
public class HandlerThread extends Thread {
Handler handler;
@Override
public void run() {
Looper.prepare();
handler = new Handler(){
@Override
public void handleMessage(Message msg) {
// do something
}
};
Looper.loop();
}
}
關(guān)于自定義Looper的Handler,我想有兩個問題犀概,大家是沒有注意的:
1立哑、我們都知道Looper會調(diào)用loop()方法進(jìn)行輪訓(xùn),第一個問題就是在子線程創(chuàng)建的調(diào)用loop會阻塞線程姻灶,所以如果在loop方法后執(zhí)行的語句铛绰,就需要等到loop跳出循環(huán)才繼續(xù)執(zhí)行。
2产喉、第二個問題也是最難的問題捂掰,loop方法會阻塞線程,那么主線程的loop為什么不會阻塞線程呢曾沈?
首先我們的APP運(yùn)行時(shí)會創(chuàng)建一個進(jìn)程这嚣,而所有的線程的資源是共享的,線程的執(zhí)行需要時(shí)間片塞俱,在這里疤苹,我們的loop既然是一個死循環(huán),那么必定是需要一個線程來運(yùn)行才不會阻塞主線程敛腌。我們的主線程一直都處于運(yùn)行之前,不然的話就會出現(xiàn)所謂的ANR了惫皱,
所以結(jié)論一像樊,主線程的loop必然也是創(chuàng)建了一個新的線程來運(yùn)行的。
這個主線程在哪里創(chuàng)建的呢旅敷?
事實(shí)上生棍,會在進(jìn)入死循環(huán)之前便創(chuàng)建了新binder線程,在代碼ActivityThread.main()中:
public static void main(String[] args) {
....
//創(chuàng)建Looper和MessageQueue對象媳谁,用于處理主線程的消息
Looper.prepareMainLooper();
//創(chuàng)建ActivityThread對象
ActivityThread thread = new ActivityThread();
//建立Binder通道 (創(chuàng)建新線程)
thread.attach(false);
Looper.loop(); //消息循環(huán)運(yùn)行
throw new RuntimeException("Main thread loop unexpectedly exited");
}
thread.attach(false)涂滴;便會創(chuàng)建一個Binder線程(具體是指ApplicationThread,Binder的服務(wù)端晴音,用于接收系統(tǒng)服務(wù)AMS發(fā)送來的事件)柔纵,該Binder線程通過Handler將Message發(fā)送給主線程.
關(guān)于IPC部分的,不展開說明锤躁。
對比obtainMessage和obtainMessage(Object...)的重載方法
Handler調(diào)用obtainMessage()以及它的所有重載方法搁料,都會調(diào)用對應(yīng)的Message中的obtain()重載方法,然后返回一個Message對象,之后我們就可以對這個Message設(shè)置一些屬性郭计,通過handler把它發(fā)送出去霸琴。
public final Message obtainMessage()
public final Message obtainMessage(int what)
public final Message obtainMessage(int what, Object obj)
public final Message obtainMessage(int what, int arg1, int arg2)
public final Message obtainMessage(int what, int arg1, int arg2, Object obj)
調(diào)用上述的方法,最終都會調(diào)用到Message類中的靜態(tài)方法昭伸,我們?nèi)∑渲幸粋€比較詳細(xì)的來研究一下即可
public static Message obtain(Handler h, int what,
int arg1, int arg2, Object obj) {
Message m = obtain();
m.target = h;
m.what = what;
m.arg1 = arg1;
m.arg2 = arg2;
m.obj = obj;
return m;
}
可以看到梧乘,Message會先調(diào)用無參的obtain()方法,然后獲取到一個Message對象后庐杨,再根據(jù)所給的參數(shù)對這個Message對象進(jìn)行賦值选调。我們在使用Handler的時(shí)候,肯定都聽說過辑莫,用new Message()所得到的Message對象效率并不高学歧,為什么這樣說呢?因?yàn)镸essage內(nèi)部有一個靜態(tài)的消息鏈(鏈結(jié)構(gòu))各吨,可以從里面重用一些回收的Message(執(zhí)行完的Message)枝笨,這樣就可以減去new Message()帶來的開銷:
public static Message obtain() {
// 同一時(shí)刻,只會有一個線程會訪問sPool
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
如果我們想要獲取一個Message對象揭蜒,那么直接調(diào)用Message.obtain()方法即可横浑。這樣做可以稍微優(yōu)化一點(diǎn)點(diǎn)性能。搞不好可能還真能優(yōu)化屉更,少new一些對象可以降低GC的頻率徙融。
發(fā)送消息
使用Handler發(fā)送消息,有很多種方法瑰谜,大致分成兩類
#Message類型
public final boolean sendMessage(Message msg)
public final boolean sendEmptyMessage(int what)
public final boolean sendEmptyMessageDelayed(int what, long delayMillis)
public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis)
public final boolean sendMessageDelayed(Message msg, long delayMillis)
public boolean sendMessageAtTime(Message msg, long uptimeMillis)
#Runaable類型
public final boolean post(Runnable r)
public final boolean postAtTime(Runnable r, long uptimeMillis)
public final boolean postAtTime(Runnable r, Object token, long uptimeMillis)
public final boolean postDelayed(Runnable r, long delayMillis)
不管是postxxxAtTime或者是sendxxxDelayed欺冀,實(shí)質(zhì)上到最后都是調(diào)用sendMessageAtTime這個方法
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
// 把Message對象添加到消息隊(duì)列中
return enqueueMessage(queue, msg, uptimeMillis);
}
那么對于post方法,會先轉(zhuǎn)化成對應(yīng)sendMessage的方法萨脑,也就是說不管是Runnable對象還是Message對象隐轩,最終添加到MessageQueue的都是Message對象
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
private static Message getPostMessage(Runnable r, Object token) {
Message m = Message.obtain();
m.obj = token;
m.callback = r;
return m;
}
而sendMessageAtTime實(shí)際上做的就是把消息添加到MessageQueue中
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
// 調(diào)用消息隊(duì)列里面的方法,把消息插入到隊(duì)列中
return queue.enqueueMessage(msg, uptimeMillis);
}
至此渤早,我們的Message就添加到MessageQueue中去了职车,當(dāng)然,有可能會添加失敗的鹊杖,如果這個隊(duì)列處于正在退出的狀態(tài)悴灵,那么就會釋放掉這個Message,并且返回false骂蓖。
有兩個比較特殊的發(fā)送消息的方法
public final boolean postAtFrontOfQueue(Runnable r)
public final boolean sendMessageAtFrontOfQueue(Message msg)
很明顯這是一種插隊(duì)方法积瞒,發(fā)送的消息會添加到隊(duì)列的頭部
public final boolean sendMessageAtFrontOfQueue(Message msg) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
// 因?yàn)闀r(shí)間為0,小于其他的Message涯竟,所以會先執(zhí)行這個消息
return enqueueMessage(queue, msg, 0);
}
處理消息
有三種方法可以處理消息的赡鲜,執(zhí)行順序由上往下依次是
1空厌、直接執(zhí)行Runnable的消息
2、實(shí)現(xiàn)Handler內(nèi)的Callback接口银酬,在構(gòu)造Handler的時(shí)候傳進(jìn)去嘲更,如果返回false的話,那么繼續(xù)執(zhí)行第三個方法揩瞪。
3赋朦、重寫Handler內(nèi)的handlerMessage方法
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
// 如果消息的Runnable對象不為空,調(diào)用它的run方法
handleCallback(msg);
} else {
// 消息是一個Message對象
if (mCallback != null) {
// Handler的Callback接口不為空
if (mCallback.handleMessage(msg)) {
//接口返回true的話李破,那么return不再往下執(zhí)行
return;
}
}
//執(zhí)行Handler里面的handleMessage方法
handleMessage(msg);
}
}
還有一些關(guān)于從消息隊(duì)列中移除Message或者Callback宠哄,或者查找隊(duì)列中是否存在Message或Callback的方法就不研究了,在我的認(rèn)識中嗤攻,消息機(jī)制都是執(zhí)行的非趁担快,除非是延時(shí)或者定時(shí)的消息妇菱,否則一般很快就會執(zhí)行完承粤。我們只需要記住
removeCallbacksAndMessages(Object token):移除那些msg.obj == token的對象,如果token是null的話闯团,那么移除所有的Message和Callback辛臊。
public final void removeMessages(int what)
public final void removeMessages(int what, Object object)
public final void removeCallbacksAndMessages(Object token)
Looper
這是一個用于在線程中輪訓(xùn)消息的類,默認(rèn)的線程是沒有消息輪訓(xùn)的房交,為了創(chuàng)建一個Looper輪訓(xùn)消息彻舰,在線程中調(diào)用prepare()初始化looper,調(diào)用loop開始輪訓(xùn)處理消息直到Looper停止候味。Looper基本上都是和Handler一起工作的.
先來看一下Looper的構(gòu)造方法了刃唤,Looper有一個私有的構(gòu)造方法,在構(gòu)造方法內(nèi)保存當(dāng)前線程的信息白群,并且創(chuàng)建一個新的消息隊(duì)列MessageQueue透揣,這個構(gòu)造函數(shù)由Looper的prepare()方法調(diào)用。
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
我們先來看Looper中的一些變量川抡,ThreadLocal是一個和線程關(guān)聯(lián),并且每一個調(diào)用了prepare方法的線程都會建立一種Thread和Looper之間的映射须尚,一個Thread對應(yīng)一個Looper崖堤,而sMainLooper則是保存的是UI線程的Looper對象,我們可以通過這個特性來判斷當(dāng)前線程是否處于主線程
Looper.myLooper() == getMainLooper()
當(dāng)我們調(diào)用prepare的時(shí)候耐床,實(shí)質(zhì)上會調(diào)用的是下面這個方法
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));
}
當(dāng)我們調(diào)用prepare方法的時(shí)候密幔,就會建立Thread和Looper之間的映射關(guān)系,并且這個方法同一個線程中只能調(diào)用一次撩轰,否則會拋出異常胯甩。
對于prepareMainLooper方法昧廷,這個方法會由系統(tǒng)來調(diào)用,在應(yīng)用打開的時(shí)候創(chuàng)建主線程和Looper之間的映射偎箫。這個方法不應(yīng)該由我們自己來調(diào)用木柬,因?yàn)橥瑯又骶€程只能有一個Looper與之關(guān)聯(lián)。
public static void prepareMainLooper() {
//創(chuàng)建一個不允許關(guān)閉的MessageQueue
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
講到這里淹办,我們又不得不提一下loop方法了眉枕,我們都說Looper是負(fù)責(zé)從消息隊(duì)列里面取出消息,然后執(zhí)行的怜森,那么具體是怎么個操作法呢
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 (;;) {
// 1.輪訓(xùn)獲取消息速挑,這是一個阻塞的方法
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 {
//2.處理消息
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);
}
//3.回收消息
msg.recycleUnchecked();
}
}
這就是Looper輪訓(xùn)的工作了,在一個無限循環(huán)中副硅,首先通過調(diào)用MessageQueue的next()方法獲取下一個Message對象姥宝,有可能會阻塞(當(dāng)有消息添加到隊(duì)列的時(shí)候就會獲取該消息繼續(xù)往下執(zhí)行)。獲取到消息后就用消息中的target對象恐疲,調(diào)用它的dispatchMessage(Message msg)方法,可想而知腊满,這個target就是指向我們的handler的,執(zhí)行完成后就會調(diào)用Message的recycleUnchecked()方法釋放資源回收消息流纹。
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
//把這個釋放掉的Message添加到Message的鏈結(jié)構(gòu)里面
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
Looper下還有一些方法
#獲取當(dāng)前線程所關(guān)聯(lián)的Looper對象
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
#獲取Looper對象管理的消息隊(duì)列MessageQueue
public static @NonNull MessageQueue myQueue() {
return myLooper().mQueue;
}
#不安全糜烹,調(diào)用這個方法不會等待Message執(zhí)行完畢
public void quit() {
mQueue.quit(false);
}
#調(diào)用這個方法會等待Message執(zhí)行完畢再退出
public void quitSafely() {
mQueue.quit(true);
}
內(nèi)存泄漏
Handler泄漏從來不是一個陌生的話題,從上面我們可以知道每一個Message都會持有Handler對象的引用漱凝,而一個沒有static修飾的Handler類疮蹦,因?yàn)榉庆o態(tài)內(nèi)部類默認(rèn)會持有外部類的引用,所以假如在一個Activity中有一個Handler茸炒,而這個Activity準(zhǔn)備銷毀的時(shí)候Message持有Handler的引用愕乎,而Handler又持有Activity的引用,那么這個時(shí)候Activity就會因?yàn)闊o法及時(shí)回收而發(fā)生泄漏壁公。
要解決這個問題也非常簡單感论,先寫個模板
public abstract class ModelHandler<T extends Activity> extends Handler {
private final WeakReference<T> reference;
public ModelHandler(T t){
reference = new WeakReference<T>(t);
}
@Override
public void handleMessage(Message msg) {
if (reference.get() == null){
return;
}else {
doSomething(msg);
}
}
public abstract void doSomething(Message msg);
}
接著在我們需要的地方實(shí)現(xiàn)這個模板類
static class NewHandler extends ModelHandler<HandlerActivity>{
public NewHandler(HandlerActivity activity) {
super(activity);
}
@Override
public void doSomething(HandlerActivity activity, Message msg) {
switch (msg.what){
case 1:
activity.dosomething();
break;
default:
break;
}
}
}
//進(jìn)行初始化
NewHandler handler = new NewHandler(this);
除此以外,我還會在Activity的onDestroy處把handler致空紊册,這樣的可以保證關(guān)閉Activity后比肄,不會有新的消息發(fā)送到消息隊(duì)列。當(dāng)然了囊陡,關(guān)于內(nèi)存泄漏的問題可能還有更好的解決方法芳绩,希望大佬們多多指點(diǎn)。
關(guān)于MessageQueue部分的就不講了撞反,我們只需要記住通過next()來獲取消息妥色,并且這是一個阻塞式的方法即可。
Handler消息機(jī)制的流程圖遏片,要知道一切消息的執(zhí)行都離不開Looper和Handler
最后分享一個超厲害的在線畫圖網(wǎng)站 http://www.processon.com 基本上能想到的圖都能在上面畫嘹害,還可以導(dǎo)出圖片撮竿!
最后的最后,賣個關(guān)子笔呀,Handler還可以實(shí)現(xiàn)進(jìn)程間的通信幢踏,但是留到進(jìn)程通信的時(shí)候再說吧。