由Message余境,Handler,MessageQueue和Looper引發(fā)的思考灌诅?

1.首先看一段App應用程序啟動的時候的源碼流程分析圖(網(wǎng)上看到的):

image.png

從上圖我們可以看到:當我們點擊桌面的應用程序的圖標的時候芳来,首先會通過Binder的IPC通信機制,啟動ActivityManagerService的startActivity方法猜拾,如果發(fā)現(xiàn)進程沒有啟動的話即舌,需要通過zygote(受精卵)孵化出一個新的進程,在新的進程中執(zhí)行ActivityThread的main方法挎袜。

2.我們在獲取到Looper.getMainLooper()是通過sMainLooper來獲取的顽聂,那這個sMainLooper是在哪里賦值的?

答:首先我們看到在Looper中有一個方法prepareMainLooper(),該方法會對sMainLooper進行初始化盯仪,那么我們在什么時候會調(diào)用該方法呢紊搪,為什么我們在主線程中創(chuàng)建handler的時候,不需要手動去調(diào)用這個方法全景,而是直接利用handler的構造方法耀石,不需要對looper進行處理就創(chuàng)建呢?
細看framework的源碼爸黄,可以看到整個 Framework 框架只有兩個地方調(diào)用了 prepareMainLooper 方法:

第一處是在 SystemServer.java 中的 ServerThread,這個線程是在 Android 啟動過程中的 main() 方法啟動的:
public static void main(String[] args) {
new SystemServer().run();
}

public SystemServer() {
    mFactoryTestMode = FactoryTest.getMode();
}

private void run() {
        Looper.prepareMainLooper();
        Looper.loop();
}

第二處是在 ActivityThread.java 的 main() 方法中:
public static void main(String[] args) {
......
Looper.prepareMainLooper();

    ActivityThread thread = new ActivityThread();
    thread.attach(false);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    AsyncTask.init();

    Looper.loop();
}

因為systemServer是超級進程娶牌,是在系統(tǒng)啟動的時候,用來初始化各種系統(tǒng)核心服務馆纳,是在不同于APP的進程中的。(從上面的分析來看的話汹桦,Android一個應用開啟對應一個進程鲁驶,一個進程對應有一個主線程。)根據(jù)下圖舞骆,我們可以明白是在App點擊時候的入口钥弯,ActivityThread中對sMainLooper進行賦值的。

3.為什么在子線程創(chuàng)建handler的時候督禽,需要調(diào)用Looper.prepare()方法脆霎?

答:現(xiàn)在我們來看handler的構造方法,對應無Looper形參的構造方法狈惫,都會執(zhí)行下面的構造方法
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> 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;
}

因為在Looper.prepare()會創(chuàng)建一個looper與當前線程綁定睛蛛。如果子線程沒有調(diào)用prepare方法的話,那個mLooper=null,就會出現(xiàn)上面的報錯了忆肾。為什么主線程不需要調(diào)用荸频,從1中我們可以看到,在app進程創(chuàng)建的時候客冈,會調(diào)用Looper.prepareMainLooper(),所以主線程就不用再調(diào)用一次了旭从。

4.當handler發(fā)送一個消息的時候,怎么將消息插到隊列中场仲?

答:如果發(fā)送的是Runnable和悦,不是一個消息,也會在代碼中轉(zhuǎn)成Message類型渠缕,然后最終會執(zhí)行handler的private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)方法:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
將消息插入到消息隊列當中去鸽素。其中的queue是通過looper.mQueue獲取的意味著messageQueue和當前的looper是綁定的,所以一個looper應該是對應一個messageQueue褐健。

5.當Message插入到MessageQueue當中付鹿,系統(tǒng)是怎么通知到對應的handler來執(zhí)行handlerMessage方法呢?

答:如果是在主線程創(chuàng)建Looper的話蚜迅,默認會調(diào)用Looper.loop()方法舵匾,會不停地從消息隊列中取出消息,如果消息隊列為空谁不,則不循環(huán)坐梯,此時釋放cpu,不會占用cpu資源刹帕,如果有消息的話吵血,那就執(zhí)行msg.target.dispatchMessage(msg);實際上就是調(diào)用handler的dispachMessage方法,會調(diào)用handler的handleMessage方法偷溺。

6.在主線程中通過Looper.loop()死循環(huán)蹋辅,為什么不會對主線程造成阻塞呢?

答:因為在該方法中挫掏,Looper在創(chuàng)建的過程中侦另,會創(chuàng)建對應的MessageQueue對象,在構造函數(shù)中尉共,會調(diào)用JNI的方法nativeInit()褒傅,通過這個方法會在c++層創(chuàng)建NativeMessageQueue,同時NativeMessageQueue會創(chuàng)建一個Looper對象袄友,這個Looper對象是通過pipe(管道)設計而成的殿托,如果有消息則喚起讀線程,沒有消息則線程阻塞剧蚣,釋放cpu資源支竹⌒ⅲ回看java層的Looper類的loop方法,Message msg = queue.next()唾戚,進入next方法柳洋,會執(zhí)行nativePollOnce(mPtr, nextPollTimeoutMillis),調(diào)用JNI層的nativePollOnce方法叹坦,會調(diào)用NativeMessageQueue的pollOnce()熊镣,內(nèi)部調(diào)用了c++層Looper對象的pollOnce(),該方法會調(diào)用pollInner()募书,pollInner()方法就是先通過epoll_wait()進入空閑等待狀態(tài)绪囱,等待消息隊列的管道上的消息(IO事件)。如果有消息待處理(即管道上有IO寫事件發(fā)生莹捡,寫事件是EPOLLIN類型)鬼吵,則調(diào)用awoken()將消息讀取出來。所以直到有messageQueue中有消息的時候篮赢,才會喚起齿椅,如果沒有java層沒有消息的時候,線程是會釋放掉cpu資源的启泣;另外所有的ui操作都通過handler來發(fā)消息操作涣脚。

結(jié)論:
1.我們通過handler發(fā)送一個消息,都是通過looper放置對應的messageQueue當中寥茫,然后Looper.loop()會不斷地從messageQueue中獲取消息遣蚀,有消息的話,就通過msg.target(實際就是handler).handlerMessage()纱耻;
2.① 每個Thread只對應一個Looper芭梯;② 每個Looper只對應一個MessageQueue;③ 每個MessageQueue中有N個Message弄喘;④ 每個Message中最多指定一個Handler來處理事件玖喘。一個線程可以擁有多個handler,但是一個handler只能綁定一個線程蘑志。Looper是屬于某一個線程的芒涡,一個looper對應一個MessageQueue。判斷這個handleMessage()方法在哪個線程上執(zhí)行卖漫,就看這個handler的looper對象是在哪個線程,就在這對應的線程上執(zhí)行赠群。

如果有什么不對的羊始,還請指正!

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末查描,一起剝皮案震驚了整個濱河市突委,隨后出現(xiàn)的幾起案子柏卤,更是在濱河造成了極大的恐慌,老刑警劉巖匀油,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缘缚,死亡現(xiàn)場離奇詭異,居然都是意外死亡敌蚜,警方通過查閱死者的電腦和手機桥滨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來弛车,“玉大人齐媒,你說我怎么就攤上這事》柞耍” “怎么了喻括?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長贫奠。 經(jīng)常有香客問我唬血,道長,這世上最難降的妖魔是什么唤崭? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任拷恨,我火速辦了婚禮,結(jié)果婚禮上浩姥,老公的妹妹穿的比我還像新娘挑随。我一直安慰自己,他們只是感情好勒叠,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布兜挨。 她就那樣靜靜地躺著,像睡著了一般眯分。 火紅的嫁衣襯著肌膚如雪拌汇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天弊决,我揣著相機與錄音噪舀,去河邊找鬼。 笑死飘诗,一個胖子當著我的面吹牛与倡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播昆稿,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼纺座,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了溉潭?” 一聲冷哼從身側(cè)響起净响,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤少欺,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后馋贤,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體赞别,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年配乓,在試婚紗的時候發(fā)現(xiàn)自己被綠了仿滔。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡扰付,死狀恐怖堤撵,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情羽莺,我是刑警寧澤实昨,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站盐固,受9級特大地震影響荒给,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜刁卜,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一志电、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蛔趴,春花似錦挑辆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至箫荡,卻和暖如春魁亦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背羔挡。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工洁奈, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人绞灼。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓利术,卻偏偏與公主長得像,于是被迫代替她去往敵國和親低矮。 傳聞我的和親對象是個殘疾皇子印叁,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359

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