Android消息機(jī)制Handler學(xué)習(xí)記錄

注:本來只是想整理成Handler部分的面試題及答案拿來背誦的,哪知一看源碼就沒停下來,變成了大篇文章...本文根據(jù)源碼對(duì)Handler進(jìn)行分析,但由于能力有限,仍然和市面上的大多Handler消息機(jī)制的文章相似——即,僅探討表層邏輯,對(duì)于其中的Binder、native、epoll之類的并不涉及。所以如果你已經(jīng)看多了表層解析,就可以右上角啦~。

而且有個(gè)大問題:我把自己當(dāng)作一個(gè)旁觀者再讀了一遍這篇文章發(fā)現(xiàn)自己講的并不清楚,所以各位還是自己去看一遍源碼比較好。至少我自己感覺在看了這一遍源碼之后,印象就很深刻了,估計(jì)能記很久。

Android的消息機(jī)制主要是指Handler的運(yùn)行機(jī)制。

1. 什么是Handler


HandlerAndroid消息機(jī)制的上層接口。我們可以使用Handler將一個(gè)任務(wù)切換到某個(gè)指定的線程中去執(zhí)行。在Android?中,Handler主要是為了解決在子線程中無法訪問UI的矛盾。

2. 為什么子線程無法訪問UI


因?yàn)?b>Android的UI是線程不安全的,UI負(fù)責(zé)與用戶的交互,如果多線程中并發(fā)訪問UI會(huì)導(dǎo)致UI處于不可控的狀態(tài)。其次也不能選擇使用加鎖處理,首先因?yàn)榧渔i會(huì)阻塞某些線程的執(zhí)行,降低UI訪問的效率,其次加鎖會(huì)讓UI訪問的邏輯變得復(fù)雜。

//該方法用于檢查當(dāng)前是否是UI線程

void checkThread() {

? if (mThread != Thread.currentThread()) {

? ? ? throw new CalledFromWrongThreadException(

? ? ? ? ? "Only the original thread that created a view hierarchy can touch its views.");

? }}

3. 為什么使用Handler


UI線程直接與用戶交互,如果在UI線程處理耗時(shí)操作,用戶將無法繼續(xù)執(zhí)行其他UI操作,用戶體驗(yàn)很差。其次Android規(guī)定,如果任意一個(gè)Acitivity沒有響應(yīng)5秒鐘以上就會(huì)彈出ANR窗口。因此我們可以使用Handler將耗時(shí)操作放到子線程中去執(zhí)行以避免上述情況。

4. 怎么使用Handler


首先

private Handler handler = new Handler(){

? @Override? public void handleMessage(Message msg) { ?

}};

然后再子線程中處理完耗時(shí)操作后構(gòu)建一個(gè)Message對(duì)象,然后使用handler.post(Runnable r)或者handler.sendMessage(Message msg)等方法傳入Message即可。post()方法最后也是使用sendMessage()方法,因?yàn)?b>Runnable對(duì)象會(huì)被轉(zhuǎn)化成一個(gè)Message

這里有一點(diǎn)需要注意的是,由于我們是使用Message來進(jìn)行消息傳遞,所以如果每次都new?一個(gè)Message就很可能會(huì)產(chǎn)生大量用了一次就不再用的Message對(duì)象,消耗有限的內(nèi)存資源。針對(duì)這種情況,HandlerobtainMessage()方法內(nèi)部采用了享元模式。我們應(yīng)該優(yōu)先使用HandlerobtainMessage()方法構(gòu)建Message對(duì)象。

5. Handler中的享元模式


享元模式適用于可能存在大量重復(fù)對(duì)象的場景。具體的定義忘了。一般經(jīng)典的享元模式會(huì)使用Map作為對(duì)象容器。不過在Message中,是通過一個(gè)單鏈表來實(shí)現(xiàn)的,具體的說就是sPool,而sPool就是一個(gè)Message

那為啥sPool這個(gè)Message對(duì)象會(huì)是一個(gè)單鏈表呢?見如下代碼:

//Message.java中

Message next; //這不就是自定義單鏈表的中next指向下一個(gè)節(jié)點(diǎn)嘛

調(diào)用obtainMessage()最終會(huì)調(diào)用Messageobtain()方法,obtain()方法先會(huì)判斷sPool如果為空,就返回一個(gè)新的Message對(duì)象,不為空就返回sPool,然后sPool指向其next

6. Handler的內(nèi)存泄露問題及其處理方法


成因:

Handler的內(nèi)存泄露問題大致是這樣形成的:就是主線程中創(chuàng)建的Handler會(huì)和主線程Looper的消息隊(duì)列MessageQueue相關(guān)聯(lián),于是MessageQueue中的每個(gè)Message都會(huì)持有一個(gè)Handler的引用。而由于非靜態(tài)內(nèi)部類和匿名類都會(huì)隱式的持有它們所屬外部類的引用,所以就導(dǎo)致了Message持有Handler引用,Handler持有其外部類的引用的引用鏈。在Message被處理前,這條引用鏈會(huì)阻止垃圾回收器的回收,于是發(fā)生內(nèi)存泄露。

驗(yàn)證:

可以使用?handler.postDelayed()方法進(jìn)行驗(yàn)證。

解決:

解決方法:1. 使用內(nèi)部靜態(tài)類構(gòu)造Handlr,因?yàn)閮?nèi)部靜態(tài)類不會(huì)持有外部類的引用。但是這樣的話就無法操控外部Activity的對(duì)象,于是還需要增加一個(gè)隊(duì)Activity的弱引用。2. 在Activity退出的時(shí)候調(diào)用Looper的quit和quitSafely方法,以及使用對(duì)應(yīng)的handler.removeCallback方法,這些方法會(huì)最后會(huì)執(zhí)行 msg.target = null 操作,讓msg不再持有handler引用。

7. 講解一下Handler的運(yùn)行機(jī)制


private Handler myHandler = null;

new Thread("handler線程"){

@Override

public void run() {

? ? Looper.prepare();

? ? myHandler = new MyHandler();

? ? Looper.loop();? ?

? ? }

}.start();

private class MyHandler extends Handler{

? ? @Override

? ? public void handleMessage(Message msg) {

? ? ? ? super.handleMessage(msg);

? ? }

}

Handler運(yùn)行機(jī)制的主要參與者是HandlerMessageQueueLooper以及MessageThreadLocal?這5個(gè)角色。

(感覺有些復(fù)雜)

這5個(gè)角色關(guān)系有點(diǎn)復(fù)雜,

-----------------------------ThreadLocal-------------------------------------

先講相對(duì)簡單的ThreadLocalThreadLocal是在Looper中使用的,ThreadLocal的作用是我們可以通過它把數(shù)據(jù)存儲(chǔ)到指定的線程,并且只有在指定的線程才能獲取數(shù)據(jù)。

而其中把數(shù)據(jù)儲(chǔ)存到指定的線程這個(gè)操作在Looper.prepare()中進(jìn)行,方法調(diào)用的最后會(huì)調(diào)用ThreadLocalset方法傳入一個(gè)Looper對(duì)象(Looper對(duì)象包含了MessageQueue和當(dāng)前線程)。

//Looper.java中

//全局唯一一份的ThreadLocal,將會(huì)在每個(gè)ThreadLocalMap中作為key

static final ThreadLocal sThreadLocal = new ThreadLocal<~>();

private static void prepare(boolean quitAllowed) {

? ? if (sThreadLocal.get()!= null) {

? ? ? ? throw new RuntimeException("Only one Looper may be created per thread");

? ? }

? ? //這個(gè)sThreadLocal是static final 的.

? ? sThreadLocal.set(new Looper(quitAllowed));

}

private Looper(boolean quitAllowed) {

? ? mQueue = new MessageQueue(quitAllowed);

? ? mThread = Thread.currentThread();

}

然后我們可以看到ThreadLocal中的set(T value)中的createMap()方法中傳入的是Threadnew?Looper(),但并不是用Thread作為key呦,如下。

//ThreadLocal.java中

public void set(T value) {

? ? Thread t = Thread.currentThread();

? ? ThreadLocalMap map = getMap(t);

? ? if (map != null)

? ? ? ? map.set(this, value);

? ? else{

? ? ? ? createMap(t, value);

? ? }

}

void createMap(Thread t, T firstValue) {

? ? t.threadLocals = new ThreadLocalMap(this, firstValue);

}

//Thread.java中

ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocalMap(this , firstValue)中,this就是那個(gè)全局唯一份的sThrealLocal。所以我們很清楚了,在每個(gè)線程中Looper.prapare()的時(shí)候都會(huì)給當(dāng)前線程傳入一個(gè)ThreadLocalMap,這個(gè)Mapkey其實(shí)不重要,因?yàn)樗芯€程中的ThreadLocal都指向同一個(gè)對(duì)象,就是Looper.java中的static final ThreadLocal sThreadLocal…因?yàn)?b>key不變,所以如果多次調(diào)用Looper.prepare()的話,是會(huì)覆蓋的。

上面講了如何通過ThreadLocal把數(shù)據(jù)存儲(chǔ)到指定線程,至于后部分的只有在指定的線程才能獲取數(shù)據(jù)也就一目了然,通過當(dāng)前線程t.threadLocals獲取線程中的變量副本,然后取出值即可。在源碼中的體現(xiàn)如下:

//Looper.java中

public static @Nullable Looper myLooper() {

? ? return sThreadLocal.get();//注意這個(gè)myLooper()。返回了一個(gè)Looper對(duì)象。

}

//ThreadLocal.java中

public T get() {

? ? Thread t = Thread.currentThread();

? ? ThreadLocalMap map = getMap(t);

? ? if (map != null) {

? ? ? ? ThreadLocalMap.Entry e = map.getEntry(this);

? ? ? ? if (e != null) {? ? ? ?

? ? ? ? ? ? @SuppressWarnings("unchecked")

? ? ? ? ? ? //下面這個(gè)value。。。之前存進(jìn)去的那個(gè)Looper對(duì)象了

? ? ? ? ? ? T result = (T)e.value;? ? ? ? ?

? ? ? ? ? ? return result;

? ? ? ? }

? ? }

? ? return setInitialValue();//這個(gè)就是在map中沒有的時(shí)候返回個(gè)null

}

ThreadLocalMap getMap(Thread t) {

? ? return t.threadLocals;

}

簡單的講,每個(gè)線程都有個(gè)存儲(chǔ)變量的副本,互相隔離。

那么這個(gè)特性對(duì)我們的Handler機(jī)制有什么影響呢?從上面可以了解到每個(gè)Thread其實(shí)都對(duì)應(yīng)了一個(gè)Looper,而每個(gè)Looper都對(duì)有一個(gè)MessageQueue

具體有什么影響需要先看一看Handler的構(gòu)造方法才能看出來。

-----------------------------Handler--------------------------------

先來看一個(gè)錯(cuò)誤示例:

new Thread(){

? ? public void run(){

? ? ? ? Handler handler =new Handler();

? ? };

}.start();//正確的方法是handler前面用Looper.prepare(),后面用Looper.loop()

上述代碼會(huì)拋出“Can’t create handler inside thread that has not called Looper.prepare()”異常,這是因?yàn)樵?b>Handler構(gòu)造方法中所有規(guī)定,如下:

//Hnadler中

public Handler(Callback callback, boolean async) {

? ? …省略代碼

? ? mLooper = Looper.myLooper(); //最終調(diào)用sThreadLocal.get()

? ? 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;

}

//Looper中

public static @Nullable Looper myLooper() {

? ? return sThreadLocal.get();//如果沒有獲取到Map,則返回null

}

ThreadLocal一節(jié)的分析中可以看到sThreadLocal.get()方法在沒有獲取到ThreadLocalMap的情況下,會(huì)返回一個(gè)null

因?yàn)槠湎乱徊?i>?mQueue?=?mLooper.mQueue;需要將Looper中的MessageQueue拿出來進(jìn)行操作,所以必然之前就需要有一個(gè)Looper對(duì)象,所以就必然要先Looper.prepare()啦。

至于在主線程中我們?yōu)樯犊梢灾苯?b>new一個(gè)Handler呢?因?yàn)橄到y(tǒng)已經(jīng)幫我們做好了這一步,代碼如下:

//ActivityThread中

public static void main(String[] args) {

? ? …代碼省略

? ? Looper.prepareMainLooper();

? ? …代碼省略

? ? ActivityThread thread = new ActivityThread();

? ? thread.attach(false);

? ? if (sMainThreadHandler == null) {

? ? ? ? sMainThreadHandler = thread.getHandler();

? ? }

? ? …代碼省略

? ? Looper.loop();

? ? throw new RuntimeException("Main thread loop unexpectedly exited");

}

扯遠(yuǎn)了咳咳。

從前面的Handler的構(gòu)造方法中就可以看到,Handler其實(shí)使用的是Looper對(duì)象中的MessageQueue,而Looper對(duì)象又是和Thread對(duì)應(yīng)的。

那么我們就知道了,Handler的各種發(fā)消息的操作send() , post()?等,最后都會(huì)把數(shù)據(jù)Message傳遞到“那個(gè)線程(假定先命名為Thread_1)”中。

知道這個(gè)我們就可以知道為啥handler可以在不同的線程中給自己的線程的線程發(fā)消息啦。

對(duì)于使用者來說,使用Handler的步驟并不復(fù)雜,定義一個(gè)Handler,然后調(diào)用handler.sendMessage()發(fā)送消息,然后在handleMessage()中對(duì)消息進(jìn)行處理即可。

那么我們就從發(fā)送消息開始一步步了解Handler的消息機(jī)制。

Handler的發(fā)消息有很多種發(fā)送消息的接口,如下

postAtTime(Runnable r, Object token, long uptimeMillis)

postAtTime(Runnable r, long uptimeMillis)

post(Runnable r)

postDelayed(Runnable r, long delayMillis)

sendMessageAtFrontOfQueue(Message msg)

sendMessageAtTime(Message msg, long uptimeMillis)

sendMessageDelayed(Message msg, long delayMillis)

sendEmptyMessageDelayed(int what, long delayMillis)

sendMessage(Message msg)

sendEmptyMessage(int what)

postAtFrontOfQueue(Runnable r)

……等方法

但是這么多方法,最后都是調(diào)用了enqueueMessage()方法

//Handler.java中

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {

? ? msg.target = this; //這個(gè)地方要注意,在這里msg持有了handler引用

? ? if (mAsynchronous) {

? ? ? ? msg.setAsynchronous(true);

? ? }

? ? return queue.enqueueMessage(msg, uptimeMillis);

}

最后的queue還有印象么,就是之前說的,從Looper對(duì)象中存儲(chǔ)的那個(gè)MessageQueue

所以最后調(diào)用了MessageQueue.java中的enqueueMessage(Message msg, long when)方法,如下:

//MessageQueue.java

boolean enqueueMessage(Message msg, long when) {

? ? if (msg.target == null) {

? ? ? ? throw new IllegalArgumentException("Message must have a target.");

? ? }

? ? …省略代碼

? ? synchronized (this) {

? ? ? ? if (mQuitting) {?

? ? ? ? ? ? …省略代碼

? ? ? ? ? ? msg.recycle();

? ? ? ? ? ? return false;

? ? ? ? }

? ? ? …省略代碼

? ? ? ? msg.when = when;

? ? ? ? Message p = mMessages;

? ? ? ? if (p == null || when == 0 || when < p.when) {

? ? ? ? ? ? msg.next = p;

? ? ? ? ? ? mMessages = msg;

? ? ? ? } else {

? ? ? ? ? ? …省略代碼

? ? ? ? ? ? Message prev;

? ? ? ? ? ? for (;;) {

? ? ? ? ? ? ? ? prev = p;

? ? ? ? ? ? ? ? p = p.next;

? ? ? ? ? ? ? ? if (p == null || when < p.when) {

? ? ? ? ? ? ? ? ? ? break;

? ? ? ? ? ? ? ? }

? ? ? ? ? ? …省略代碼

? ? ? ? ? ? }

? ? ? ? ? ? msg.next = p; // invariant: p == prev.next

? ? ? ? ? ? prev.next = msg;

? ? ? ? }

? ? …省略代碼

? ? }

? ? return true;

}

上述的代碼不扣細(xì)節(jié)的話,就一件事,把消息插入mMessages中。

sMessages明明是Message為何能插入呢,因?yàn)樵?.5的享元模式中就介紹了Message可以構(gòu)成單鏈表。所以雖然喊MessageQueue.java是消息隊(duì)列,但是其實(shí)它自身并不是什么鏈表隊(duì)列結(jié)構(gòu),它的作用是對(duì)Message這個(gè)單鏈表進(jìn)行各種操作。

所以Handler發(fā)送消息的最終結(jié)果,就是把消息插入到了Message的單鏈表中。而且這個(gè)Message持有Handler的引用。

那么現(xiàn)在消息插入到消息鏈表中了,怎么把msg拿出來呢?這就到了我們的LooperMessageQueue環(huán)節(jié)了。

還記得之前的Looper.prapare() – new Handler() – Looper.loop()?三部曲嗎。拿消息的操作就在Looper.loop()中,當(dāng)然也在MessageQueue中,具體的流程如下。

-------------------Looper和MessageQueue和Message----------------------

話不多說,上代碼:

//Looper.java中

publi 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;

? ? Binder.clearCallingIdentity();

? ? final long ident = Binder.clearCallingIdentity();

? ? …省略代碼

? ? for (;;) {

? ? ? ? Message msg = queue.next(); // might block

? ? ? ? if (msg == null) {

? ? ? ? ? ? return;

? ? ? ? }

? ? ? ? …省略代碼

? ? ? ? try {

? ? ? ? ? ? msg.target.dispatchMessage(msg);

? ? ? ? } finally {

? ? ? ? ? …省略代碼

? ? ? ? }

? ? ? ? …省略代碼

? ? ? ? final long newIdent = Binder.clearCallingIdentity();

? ? ? ? …省略代碼

? ? }

}

我留下了幾行Binder的代碼,我看不懂,但是覺得這個(gè)和Binder有關(guān)的代碼肯定有很大的深意o(╯□╰ )o ,去除掉了其他我不懂的代碼之后,剩下的代碼的邏輯很清晰:先是獲取到當(dāng)前線程的MessageQueue queue = me.mQueue,然后用一個(gè)死循環(huán)不斷使用queue.next()方法得到msg。如果有msg,就調(diào)用handler.disptachMessage(msg)進(jìn)行消息分發(fā)。要問Handler在哪兒?就是msg.target哇。

這里先不看disptachMessage(msg)的過程,我先看看for(;;)這個(gè)死循環(huán)里,只有一處可以返回。就是當(dāng)queue.next()返回null的時(shí)候,并且所有需要處理的msg都是從queue.next()拿來的。所以看樣子,我們的Looper.loop()中最主要的工作是由queue.next()來完成的。

讓我們深入queue.next()看看:

Message next() {

? ? …省略代碼

? ? for (;;) {

? ? ? ? if (nextPollTimeoutMillis != 0) {

? ? ? ? ? ? Binder.flushPendingCommands();

? ? ? ? }

? ? ? ? nativePollOnce(ptr, nextPollTimeoutMillis);//這里甚至有個(gè)native方法!!!肯定有深意

? ? ? ? synchronized (this) {

? ? ? ? ? ? Message prevMsg = null;

? ? ? ? ? ? Message msg = mMessages;

? ? ? ? ? ? if (msg != null && msg.target == null) {

? ? ? ? ? ? ? ? do {

? ? ? ? ? ? ? ? ? ? prevMsg = msg;

? ? ? ? ? ? ? ? ? ? msg = msg.next;

? ? ? ? ? ? ? ? } while (msg != null && !msg.isAsynchronous());

? ? ? ? ? ? }

? ? ? ? ? ? if (msg != null) {

? ? ? ? ? ? ? ? mBlocked = false;

? ? ? ? ? ? ? ? if (prevMsg != null) {

? ? ? ? ? ? ? ? ? ? prevMsg.next = msg.next;

? ? ? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? ? ? mMessages = msg.next;

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? msg.next = null;

? ? ? ? ? ? ? ? return msg;

? ? ? ? ? ? }

? ? ? ? ? ? if (mQuitting) {

? ? ? ? ? ? ? ? dispose();

? ? ? ? ? ? ? ? return null;

? ? ? ? ? ? }

? ? ? }

}

這里面我省略了一堆我看不懂的代碼,剩下的代碼邏輯也不難。這里又有一個(gè)for(;;)死循環(huán),這個(gè)死循環(huán)是不斷的mMessages中拿出msg,還記得我們之前見過的mMessages嗎,之前看到mMessages是插入節(jié)點(diǎn),這次見到就是要從中取出節(jié)點(diǎn)了,取出的操作就是各種.next

這里重點(diǎn)關(guān)注兩個(gè)return的地方,記住只有兩個(gè)return。第一個(gè)是返回msg,第二是mQuitting == true?的時(shí)候返回null

那么返回null會(huì)怎么樣呢?還記得Looper.loop()中當(dāng)msg == null的時(shí)候會(huì)發(fā)生什么嗎,當(dāng)msg = null的時(shí)候,Looper.loop()退出for(;;)死循環(huán)。既然退出死循環(huán)了,loop()方法也就執(zhí)行完畢,也就意味著這個(gè)Looper不再獲取新消息,handler再發(fā)什么消息都沒有用了。

甚至我們可以看到當(dāng)mQuittiong == true的時(shí)候,handler試圖enqueueMessage()?發(fā)送的消息直接就被回收了:

//MessageQueue.java中

boolean enqueueMessage(Message msg, long when) {

? …省略代碼

? ? synchronized (this) {

? ? ? ? if (mQuitting) {

? ? ? ? ? ? IllegalStateException e = new IllegalStateException(

? ? ? ? ? ? ? ? ? ? msg.target + " sending message to a Handler on a dead thread");

? ? ? ? ? ? msg.recycle();

? ? ? ? ? ? return false;

? ? ? ? }

? ? …省略代碼

}

上面的msg.recycle()和享元模式有關(guān),大概就是這個(gè)msg直接給你抹除所有信息然后插入到sPool中。當(dāng)然sPool這個(gè)單鏈表也不是給你無限插入的,最大數(shù)量是MAX_POOL_SIZE = 50。可以自己看下源碼哈。

從上面看出mQuitting的威力巨大,那么哪兒設(shè)置這個(gè)mQuitting的值的呢,見源碼:

//MessageQueue.java中

void quit(boolean safe) {

? ? …省略代碼

? ? synchronized (this) {

? ? ? ? if (mQuitting) {

? ? ? ? ? ? return;

? ? ? ? }

? ? ? ? mQuitting = true;

? ? ? ? …省略代碼

? ? }

}

而這個(gè)quit( boolean safe )則是在Looper中被調(diào)用,如下:

//Looper.java中

public void quit() {

? ? mQueue.quit(false);

}

public void quitSafely() {

? ? mQueue.quit(true);

}

怎么樣,是不是感覺很簡單。這兩者的區(qū)別在于如何處理那些延遲發(fā)送的msg,看最后調(diào)用的方法名就清楚了:removeAllMessagesLocked()removeAllFutureMessagesLocked()

兩個(gè)方法里都用到了msg.recycleUnchecked(),也就是說這些msg對(duì)象最后會(huì)被回收進(jìn)sPool里。所以理解為啥官方建議使用handler.obtainMessage()方法了吧——因?yàn)楹芏嗟胤降?b>msg都會(huì)被回收進(jìn)sPool。而且直接獲取現(xiàn)有msg肯定也比每次新new一個(gè)message資源消耗少,效率更高。

上面講了MessageQueue.next()返回null的特殊情況,那么如果正常返回了一個(gè)msg呢?正常的話則會(huì)調(diào)用Looper.loop()?中的msg.target.dispatchMessage(msg)方法,這個(gè)方法調(diào)用的是Handler中的dispatchMessage()方法,如下

//Handler.java中

public void dispatchMessage(Message msg) {

? ? if (msg.callback != null) {

? ? ? ? handleCallback(msg);

? ? } else {

? ? ? ? if (mCallback != null) {

? ? ? ? ? ? if (mCallback.handleMessage(msg)) {

? ? ? ? ? ? ? ? return;

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? handleMessage(msg);

? ? }

}

(假裝看不到handleCallback(msg)^_^因?yàn)榭粗悬c(diǎn)復(fù)雜啊)最后都是調(diào)用了handleMessage(msg),就是我們最最最最最最最最開始的——

private Handler handler = new Handler(){

//重寫handleMessage方法

? ? @Override

? ? public void handleMessage(Message msg) {

? ? ? ? super.handleMessage(msg);

? ? }

};

——這里面的handleMessage(msg)了,然后就可以拿msg去玩耍啦。

還記得這handleMessage()最初哪兒調(diào)用的嗎?是msg.target.dispatchMessage(msg),而這個(gè)msg最終的源頭又是存在于t.sThreadLocal這個(gè)ThreadLocalMap里的,所以在handleMessage(msg){}方法也就是執(zhí)行在t這個(gè)線程中啦。

于是就達(dá)成了——handler這個(gè)在外行走可以在不同的線程發(fā)送消息,最后消息都是傳送到hanler最初創(chuàng)建的Thread里進(jìn)行處理——這個(gè)功能。

1.8 總結(jié)

總結(jié)幾個(gè)要點(diǎn):

1.?ThreadLocal是個(gè)好東西,它的存在讓Thread、Handler、MessageQueue綁定了起來,于是讓handler可以做到不管在哪個(gè)線程發(fā)消息,最終消息都會(huì)傳送到原來的Thread里。ThreadLocalMap也是功不可沒。

2.?LooperMessageQueue共同協(xié)作來讓整個(gè)消息隊(duì)列動(dòng)起來,不斷的取出新消息

3.?Message更多是用來構(gòu)造成單鏈表,不管是MessageQueue中的sMessages還是自己的享元模式中的sPool。所以最好使用handler.obtainMessage()來獲取Message對(duì)象。

4. 如果上述文章哪兒有問題或者哪兒看不懂請(qǐng)輕輕的噴,最好是留個(gè)言我可以改善改善O(∩_∩)O

于是,一圈分析結(jié)束~ ~ ~ ~ ~ ~ ~啦啦啦啦啦。

秋豆麻袋,那這個(gè)msg.callback的運(yùn)行機(jī)制呢?還有那些看著名字奇奇怪怪的方法呢?還有為什么UI線程死循環(huán)竟然不阻塞呢?還有….你不是說那個(gè)內(nèi)存泄露有問題嗎?你的最終結(jié)論呢?

O(╯□╰)o

Msg.callback實(shí)在不想看了,內(nèi)存泄露問題等我再問問大神,那些奇奇怪怪名字的方法如果書上木有我也研究不出啥。

但是至于為什么UI線程死循環(huán)不阻塞這個(gè)問題,問的好!

看我再秀(Baidu)一波!

其實(shí)我在源碼中看到了一堆Binder、native、epoll字眼,所以我一直懷疑不阻塞這個(gè)東西應(yīng)該和Binder機(jī)制甚至和Linux的底層機(jī)制管道epoll有關(guān)。

...

算了直接放鏈接點(diǎn)我你可以發(fā)現(xiàn)新大陸

epoll模型

當(dāng)沒有消息的時(shí)候會(huì)epoll.wait,等待句柄寫的時(shí)候再喚醒,這個(gè)時(shí)候其實(shí)是阻塞的。

所有的ui操作都通過handler來發(fā)消息操作。

比如屏幕刷新16ms一個(gè)消息,你的各種點(diǎn)擊事件,所以就會(huì)有句柄寫操作,喚醒上文的wait操作,所以不會(huì)被卡死了。

這部分留待以后如果有機(jī)會(huì)研究Linux再詳細(xì)看吧。和Binder機(jī)制一樣,如果要研究的話,就太深入了。

本文參考《Android開發(fā)藝術(shù)探索》 + 《Android源碼設(shè)計(jì)模式解析與實(shí)戰(zhàn)》+ AOSP以及感謝1Offer童鞋。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,908評(píng)論 6 541
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,324評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,018評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,675評(píng)論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,417評(píng)論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,783評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,779評(píng)論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,960評(píng)論 0 290
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,522評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,267評(píng)論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,471評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,009評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,698評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,099評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,386評(píng)論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,204評(píng)論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,436評(píng)論 2 378

推薦閱讀更多精彩內(nèi)容