從源碼分析Android消息機(jī)制

涉及到的類:
ActvityThread,Handler掖疮,Looper驹马,HandlerActionQuenue,ThreadLocal

ActvityThread

先從ActivtiyThread的main()函數(shù)入口分析

public static void main(String[] args) {
     //內(nèi)部首先Looper.prepare()創(chuàng)建Looper并初始化Looper持有的消息隊列 MessageQueue
//(如果你拋出過這樣的異常Can't create handler inside thread that has not called Looper.prepare()就知道prepare()很重要)
 //創(chuàng)建好后將Looper保存到ThreadLocal中方便Handler直接獲取笆制。
        Looper.prepareMainLooper();
        ActivityThread thread = new ActivityThread();
        thread.attach(false);
        if (sMainThreadHandler == null) {
          //返回的是mH
            sMainThreadHandler = thread.getHandler();
        }
    //--->開啟循環(huán)
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

sMainThreadHandler ===>(即名為H的繼承自Handler的對項绅这,在ActivityThread初始化時,final H mH = new H()初始化)則作用通過binder線程向H發(fā)送消息即可在辆,比如發(fā)送 H.LAUNCH_ACTIVITY 消息就是通知主線程調(diào)用Activity.onCreate() 证薇。當(dāng)然啦不是直接調(diào)用度苔,H收到消息后會進(jìn)行一系列復(fù)雜的函數(shù)調(diào)用最終調(diào)用到Activity.onCreate()。

補(bǔ)充:
ActivityThread 通過 ApplicationThread 和 AMS 進(jìn)行進(jìn)程間通訊浑度,AMS 以進(jìn)程間通信的方式完成 ActivityThread 的請求后會回調(diào) ApplicationThread 中的 Binder 方法寇窑,然后 ApplicationThread 會向 H 發(fā)送消息,H 收到消息后會將 ApplicationThread 中的邏輯切換到 ActivityThread 中去執(zhí)行箩张,即切換到主線程中去執(zhí)行甩骏,這個過程就是主線程的消息循環(huán)模型。

Looper.loop(); ====>從MessageQueue里面取消息并調(diào)用handler的 dispatchMessage(msg) 方法處理消息先慷。如果MessageQueue里沒有消息饮笛,循環(huán)就會阻塞進(jìn)入休眠狀態(tài),等有消息的時候被喚醒處理消息论熙。

此處我們來回答一個問題:
Android中為什么主線程不會因為Looper.loop()里的死循環(huán)卡死福青?
該循環(huán)有阻塞機(jī)制,所以死循環(huán)并不會一直執(zhí)行脓诡。相反的无午,大部分時間是沒有消息的,所以主線程大多數(shù)時候都是處于休眠狀態(tài)祝谚,也就不會消耗太多的CPU資源導(dǎo)致卡死宪迟。
Message msg = queue.next(); // might block if(msg == null) {
// No message indicates that the message queue is quitting.
return;
}
1.阻塞的原理是使用Linux的管道機(jī)制實現(xiàn)的
2.主線程沒有消息處理時阻塞在管道的讀端
3.binder線程會往主線程消息隊列里添加消息,然后往管道寫端寫一個字節(jié)交惯,這樣就能喚醒主線程從管道讀端返回次泽,也就是說looper循環(huán)里queue.next()會調(diào)用返回...

Looper

看下一下Looper.loop內(nèi)部

 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;
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
//省略N行代碼
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            
            final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            final long end;
            try {
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            msg.recycleUnchecked();
        }
    }

1.final Looper me = myLooper()--->獲取主線程的Looper對象
2.MessageQueue queue = me.mQueue--->獲取Looper中的消息隊列
3.for (;;) {} ---->死循環(huán)欠窒,不斷queue中取出message舱馅,處理msg.target.dispatchMessage(msg)

Handler類

先看Handler的構(gòu)造方法

public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
        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;
    }

new Handler()的時候事示,Handler構(gòu)造方法中獲取Looper并且拿到Looper的MessageQueue對象繁莹。然后Handler內(nèi)部就可以直接往MessageQueue里面插入消息了笔横,插入消息即發(fā)送消息常摧,這時候有消息了就會喚醒Looper循環(huán)去處理消息对省。處理消息就是調(diào)用dispatchMessage(msg) 方法惑淳,最終調(diào)用到我們重寫的Handler的handleMessage()方法炬藤。

mLooper = Looper.myLooper(); ====>獲取hander所在線程的Looper御铃,不一定是MainLooper。

調(diào)用了ThreadLocal內(nèi)的方法沈矿,這里存儲了looper上真。后面講ThreadLocal內(nèi)的方法再展開

public static @Nullable Looper myLooper() {
       return sThreadLocal.get();
   }

sendMessage、sendMessageDelayed等等方法羹膳,最終幾乎調(diào)用
sendMessageAtTime(Message msg, long uptimeMillis)睡互。

//獲取消息隊列,插入消息
 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);
    }
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
   //發(fā)送消息的時候把自己封裝到消息里的。
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
//向queue插入一條消息就珠,loop循環(huán)的時候寇壳,取出其中的消息,并根據(jù)target妻怎,分發(fā)給相應(yīng)的hanlder處理
        return queue.enqueueMessage(msg, uptimeMillis);
    }

ThreadLocal

 public T get() {
     //獲取當(dāng)前的thread
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
 //取出它的table數(shù)組壳炎,并找出hreadLocal的reference對象在table數(shù)組中的位置,
//然后table數(shù)組中的下一個位置所存儲的數(shù)據(jù)就是ThreadLocal的值逼侦。
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

相對應(yīng)的還有一個set()

public void set(T value) {
 //獲取當(dāng)前thread
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
   // 內(nèi)部維護(hù)一個table數(shù)組匿辩,存儲Thread的引用
            map.set(this, value);
        else
            createMap(t, value);
    }

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市榛丢,隨后出現(xiàn)的幾起案子铲球,更是在濱河造成了極大的恐慌,老刑警劉巖涕滋,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件睬辐,死亡現(xiàn)場離奇詭異挠阁,居然都是意外死亡宾肺,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進(jìn)店門侵俗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來锨用,“玉大人,你說我怎么就攤上這事隘谣≡鲇担” “怎么了?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵寻歧,是天一觀的道長掌栅。 經(jīng)常有香客問我,道長码泛,這世上最難降的妖魔是什么猾封? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮噪珊,結(jié)果婚禮上晌缘,老公的妹妹穿的比我還像新娘。我一直安慰自己痢站,他們只是感情好磷箕,可當(dāng)我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著阵难,像睡著了一般岳枷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天空繁,我揣著相機(jī)與錄音氢烘,去河邊找鬼。 笑死家厌,一個胖子當(dāng)著我的面吹牛播玖,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播饭于,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼蜀踏,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了掰吕?” 一聲冷哼從身側(cè)響起果覆,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎殖熟,沒想到半個月后局待,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡菱属,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年钳榨,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纽门。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡薛耻,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出赏陵,到底是詐尸還是另有隱情饼齿,我是刑警寧澤,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布蝙搔,位于F島的核電站缕溉,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏吃型。R本人自食惡果不足惜证鸥,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望败玉。 院中可真熱鬧敌土,春花似錦、人聲如沸运翼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽血淌。三九已至矩欠,卻和暖如春财剖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背癌淮。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工躺坟, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人乳蓄。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓咪橙,卻偏偏與公主長得像,于是被迫代替她去往敵國和親虚倒。 傳聞我的和親對象是個殘疾皇子美侦,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,724評論 2 351

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