Android消息處理機制:十分鐘讓你明白消息處理機制

引言

????Android消息機制肯定是最被經(jīng)常提起的一個概念,通過下面的文章希望大家可以理解Message、?Handler、Looper。

1 消息處理流程

? ?在子線程中更新UI晾腔,先使用Handler的sendMessage去發(fā)送Message對象,然后通過Handler的handleMessage()方法中獲得剛才發(fā)送的Message對象列肢,這就是一個消息處理流程华蜒,是常用的一種情況。

new Thread(new Runnable() {

????@Override

????public void run() {

? ? ? ? ? ? Message message = Message.obtain();

? ? ? ????? mHandler.sendMessage(message);

? ????? }

}).start();

mHandler =new Handler(new Handler.Callback() {

????@Override

? ? public boolean handleMessage(Message msg) {

????????return false;

? ? }

});

2 Message

? ? Google官方建議實例化Message時使用obtain方法看彼,而不是直接new Message廊佩。因為Message維護著一個對象池囚聚,使用obtain方法能夠復用之前被回收的Message,下面代碼 if 語句就是復用Message标锄。

public static Message obtain() {

????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的回收是調用了方法recycleUnchecked顽铸,可以看出來對象池有最大數(shù)量(MAX_POOL_SIZE)它的值是50,并且對象池是一個單鏈表料皇,單鏈表的優(yōu)勢就是方便插入谓松、刪除和節(jié)約內存。

private static final int MAX_POOL_SIZE = 50;

void recycleUnchecked() {

? ? 省略掉部分初始化代碼……

? ? synchronized (sPoolSync) {

????????if (sPoolSize < ?MAX_POOL_SIZE) {

????????????next =sPool;

? ? ? ? ? ? sPool =this;

? ? ? ? ? ? sPoolSize++;

? ? ? ????? }

????}

}

3?Handler

? ? 為了防止內存泄漏践剂,將Handler聲明為靜態(tài)類鬼譬。一般情況下,通過Handler的sendMessage方法發(fā)送Message逊脯,最后調用msg.dispatchMessage(msg)回調到handleMessage方法优质。

static class MyHandler extends Handler {

? ? ? ? WeakReference mWeakReference;

? ? ? ? public MyHandler(Activity activity){

? ? ? ? ? ? mWeakReference=new WeakReference(activity);

?????????}

? ? ? ? @Override

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

? ? ? ? ? ? final Activity activity=mWeakReference.get();

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

? ? ? ? ? ? ? ? ? ? //

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? }

? ? }

3.1 Handler構造方法

? ?Handler有七個構造方法,不難看出①②③最后都會調用④军洼。然后通過?Looper.myLooper()獲取到Looper對象巩螃,再通過mLooper.mQueue獲取到MessageQueue對象,最后我們會調用sendMessage方法發(fā)送Message對象到MessageQueue中匕争。

? ① ?public Handler() { this(null, false);}

? ② ?public Handler(Callback callback) {this(callback, false);}

? ③ ?public Handler(boolean async) { this(null, async); }

? ④ ?public Handler(Callback callback, boolean async) {

????????if (FIND_POTENTIAL_LEAKS) {

????????????final Class klass = getClass();

? ? ? ? ????if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&

????????????(klass.getModifiers() & Modifier.STATIC) ==0) {

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

}

? ? 查看myLooper的源碼不難看出避乏,myLooper是從sThreadLocal中獲取到該線程下的Looper。

public static @Nullable Looper myLooper() {

????return sThreadLocal.get();

}

? ? 構造方法⑤⑥最終會調用⑦甘桑,它們和前面四種構造方法不同之處在于可以指定Looper淑际。

⑤ ?public Handler(Looper looper) { this(looper, null, false); }

⑥ ?public Handler(Looper looper, Callback callback) { this(looper, callback, false); }

⑦ ?public Handler(Looper looper, Callback callback, boolean async) {

????????mLooper = looper;

????????mQueue = looper.mQueue;

????????mCallback = callback;

????????mAsynchronous = async;

}

3.2?sendMessage方法

????創(chuàng)建完Handler對象之后,通過sendMessage發(fā)送對象扇住,不難看出最后調用了sendMessageAtTime方法春缕,通過MessageQueue queue = mQueue取得構造Handler時所獲得的MessageQueue對象。

??? 源碼中還有幾個發(fā)送消息的方法就不一一講解了艘蹋。

public final boolean sendMessage(Message msg){

????return sendMessageDelayed(msg, 0);

}

public final boolean sendMessageDelayed(Message msg, long delayMillis){

????if (delayMillis <0) {

????????delayMillis =0;

? ? }

????return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);

}

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;

? ? }

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

}

? ? 通過enqueueMessage方法將Message加入MessageQueue 中锄贼,閱讀源碼發(fā)現(xiàn)Handler方法發(fā)送的Message最后都要到MessageQueue中。

? ? msg.target其實就是Handler自己女阀,它的作用是用來調用自己的dispatchMessage方法宅荤,在Looper的loop方法中會說到。

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

????msg.target =this;

? ? if (mAsynchronous) {

????????msg.setAsynchronous(true);

? ? }

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

}

3.3?dispatchMessage

????調用dispatchMessage方法時會判斷msg是否有callback浸策,而這個callback就是Runnable冯键,通過對post的講解你就會明白是怎么回事。

public void dispatchMessage(Message msg) {

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

????????handleCallback(msg);

? ? ?}else {

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

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

????????????????return;

? ? ? ? ? ? ?}

????????}

????????handleMessage(msg);

? ? }

}

? ? 我們經(jīng)常會用到post庸汗,這個post并非真的創(chuàng)建新線程惫确,?而是將Runnable封裝到Message中。

mHandler.post(new Runnable() {

????@Override

? ? public void run() {

????}

});

public final boolean post(Runnable r, long delayMillis)

{

????return sendMessageDelayed(getPostMessage(r), delayMillis);

}

private static Message ?getPostMessage(Runnable r) {

????Message m = Message.obtain();

? ? m.callback = r;

? ? return m;

}

? ? 閱讀dispatchMessage源碼發(fā)現(xiàn),如果callback不為null就調用handleCallback改化,callback為null則調用handleMessage掩蛤。可以看在出來post中的Runnable最后會在handleCallback中執(zhí)行它的run方法陈肛,并沒有創(chuàng)建新的線程去執(zhí)行run揍鸟。

private static void handleCallback(Message message)?

????message.callback.run();

}

4 Looper

? ? 有了Message和Handler,但是還有在一個問題句旱,3.1中說到的從sThreadLocal中獲取Looper阳藻,這個Looper是誰保存到sThreadLocal中的?其實就是Looper創(chuàng)建時保存的谈撒。

? ? 在Looper這個類中有兩個重要的方法腥泥,分別是prepare和loop方法。prepare負責創(chuàng)建Looper港华,loop負責無限循環(huán)讀取消息道川。

4.1?prepare

? ? Looper的prepare方法可以去實例化一個Looper,通過sThreadLocal.set(new Looper(quitAllowed))這行代碼可以看出來立宜,prepare創(chuàng)建了一個Looper加入到sThreadLocal中冒萄。

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

}

? ? 在實例化Looper時就會創(chuàng)建MessageQueue,所以MessageQueue并不需要我們手動創(chuàng)建橙数。通過上面的 if 語句可以看出尊流,一個線程里只能有一個Looper,否則會拋出"Only one Looper may be created per thread"灯帮,因此MessageQueue在一個線程中也只能有一個崖技。

? ? 通過下面的代碼我們也可以得出另一個結論,在子線程中必須先調用looper.prepare才能使用handler钟哥,否則handler會無法發(fā)送消息迎献,因為沒有MessageQueue。

private Looper(boolean quitAllowed) {

????mQueue =new MessageQueue(quitAllowed);

? ? mThread = Thread.currentThread();

}

4.2 loop

????一般在子線程中是這么使用loop的腻贰。先使用Looper.prepare()創(chuàng)建Looper吁恍,然后發(fā)送消息,最后再使用Looper.loop()無限循環(huán)讀取消息回調到handleMessage播演。

Looper.prepare();

Message message = Message.obtain();

mHandler.sendMessage(message);

Looper.loop();

????loop的源碼比較長冀瓦,只分析關鍵點,完整源碼請自行查看写烤。 ? ?

? ? 首先通過myLooper獲取到Looper對象翼闽。

????從下面的代碼可以看出Looper不能為null不然會拋出"No Looper; Looper.prepare() wasn't called on this thread.",所以loop方法必須在Looper.prepare方法后使用洲炊。

final Looper me =myLooper();

? ? if (me ==null) {

????????throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");

????}

public static @Nullable Looper?myLooper() {

????return sThreadLocal.get();

}

? ? ?loop方法是通過me.mQueue取得MessageQueue的感局。

final MessageQueue queue = me.mQueue;

? ? 那么尼啡,loop是怎么實現(xiàn)無線循環(huán)的呢?關鍵就在于它的for循環(huán)蓝厌。

? ? 然后通過queue.next()去MessageQueue中獲取消息玄叠,接著使用msg.target.dispatchMessage(msg)去分發(fā)消息(msg.target就是前面在講Handler時說到的Handler本身)古徒,至此一個消息處理流程就分析完了拓提。

for (;;) {

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

? ? if (msg ==null) {

????????// No message indicates that the message queue is quitting.

? ? ? ? return;

? ? ?}

????try {

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

????} ? ??

}

五 整體流程總結

圖5-1 流程圖

? ? 首先由Looper.prepare創(chuàng)建Looper,Looper會創(chuàng)建MessageQueue隧膘,因為一個線程只有一個Looper代态,所以一個線程只有一個MessageQueue。

? ? 接著創(chuàng)建Message疹吃,創(chuàng)建Message對象使用obtain方法蹦疑,而不是直接new出來,因為Message內部維護這一個對象池萨驶,可以復用之前已經(jīng)使用過的Message對象歉摧,如果線程池為null會創(chuàng)建一個新的Message(new Message)。

? ? 再接著創(chuàng)建一個Handler腔呜,調用sendMessage發(fā)送Message到MessageQueue(MessageQueue并非真正的隊列叁温,而是一個單鏈表)。

? ? 最后調用Looper.loop循環(huán)讀取消息核畴,并調用Handler的dispatchMessage進行回調處理膝但。回調完之后調用msg.recycleUnchecked方法將msg加入到Message維護的對象池中谤草。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末跟束,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子丑孩,更是在濱河造成了極大的恐慌冀宴,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件温学,死亡現(xiàn)場離奇詭異略贮,居然都是意外死亡,警方通過查閱死者的電腦和手機枫浙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進店門刨肃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人箩帚,你說我怎么就攤上這事真友。” “怎么了紧帕?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵盔然,是天一觀的道長桅打。 經(jīng)常有香客問我,道長愈案,這世上最難降的妖魔是什么挺尾? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮站绪,結果婚禮上遭铺,老公的妹妹穿的比我還像新娘。我一直安慰自己恢准,他們只是感情好魂挂,可當我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著馁筐,像睡著了一般涂召。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上敏沉,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天果正,我揣著相機與錄音,去河邊找鬼盟迟。 笑死秋泳,一個胖子當著我的面吹牛,可吹牛的內容都是我干的队萤。 我是一名探鬼主播轮锥,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼要尔!你這毒婦竟也來了舍杜?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤赵辕,失蹤者是張志新(化名)和其女友劉穎既绩,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體还惠,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡饲握,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蚕键。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片救欧。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖锣光,靈堂內的尸體忽然破棺而出笆怠,到底是詐尸還是另有隱情,我是刑警寧澤誊爹,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布蹬刷,位于F島的核電站瓢捉,受9級特大地震影響,放射性物質發(fā)生泄漏办成。R本人自食惡果不足惜泡态,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望迂卢。 院中可真熱鬧某弦,春花似錦、人聲如沸冷守。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拍摇。三九已至,卻和暖如春馆截,著一層夾襖步出監(jiān)牢的瞬間充活,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工蜡娶, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留混卵,地道東北人。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓窖张,卻偏偏與公主長得像幕随,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子宿接,可洞房花燭夜當晚...
    茶點故事閱讀 43,486評論 2 348