Handler,MessageQueue将饺,Message 與Looper

首先來一個整體的印象:

我們將這四個對象看做一條流水線的工作流程贡避,Handler(工人),MessageQueue(傳送帶)予弧,Message(待處理的貨物)刮吧,Looper(傳送帶的發(fā)動機)

工作流程: 工人(Handler)貨物(Message) 放到 傳送帶(MessageQueue)上,發(fā)動機(Looper) 帶動整個傳送帶往前走掖蛤,工人將傳送帶上的貨物取下來打包處理杀捻。

流水線要能正常的工作,工人(Handler)首先要有一條傳送帶蚓庭,傳送帶要有發(fā)動機致讥,要傳送貨物當然還得有貨物(Message),當然發(fā)動機還得啟動(Looper.loop())器赞。
這個對應(yīng)了Handler中的幾個屬性垢袱,來看看Handler的屬性組成:

    final Looper mLooper;//發(fā)動機
    final MessageQueue mQueue;//傳送帶
    final Callback mCallback;//
    final boolean mAsynchronous;

所以一個正常的Handler要正常工作,所必需的的三大要素:

1.構(gòu)造一個MessageQueue港柜,這個消息隊列用來存儲handler的sendMessage(Message msg)傳過來的Message请契。
2.構(gòu)造發(fā)動機唯一的Looper對象(每一條流水線只能有一臺發(fā)動機),這個looper是整個消息傳遞機制動力的來源
3.所有工作完成之后,要啟動發(fā)動機爽锥,即Looper要啟動涌韩,注意looper一經(jīng)啟動就意味著所有的準備工作已經(jīng)完成,也就是說所有的準備工作必須在looper啟動之前救恨,因為looper是個死循環(huán)贸辈,在這個循環(huán)啟動之后的代碼如果執(zhí)行了,說明整個系統(tǒng)已經(jīng)出錯了或者退出了肠槽。這也就是一般Looper.looper()是寫在方法最后一句的原因擎淤。

那么問題來了:
1.有人會問,既然looper是個死循環(huán)秸仙,它不會一直運行導(dǎo)致占用太多的系統(tǒng)資源導(dǎo)致系統(tǒng)內(nèi)存溢出崩潰嗎嘴拢?

要回答這個問題首先要知道,線程中默認是沒有Looer這個東西的寂纪,但是如果你需要使用 Handler席吴,那么這個東西還會被創(chuàng)建,那么我們先來看看Looper是在哪里被創(chuàng)建的捞蛋,又是如何與線程產(chǎn)生聯(lián)系的孝冒。那么首先來看Handler的創(chuàng)建,不多說上源碼:

    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();//這里是構(gòu)造我們的發(fā)動機
        if (mLooper == null) {
            throw new RuntimeException(//這里給出了提示拟杉,如果沒有調(diào)用Looper.prepare()方法就運行Handler的話會報錯
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;//構(gòu)造發(fā)動機的同時也構(gòu)造了傳送帶
        mCallback = callback;
        mAsynchronous = async;
    }

Handler有三個構(gòu)造函數(shù)胆建,但最后會調(diào)用上面的揩慕,代碼很簡單,就是簡單的構(gòu)造所需要的組件,下面來看 Looper.myLooper()方法:

    /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
..............................
    // sThreadLocal.get() will return null unless you've called prepare().
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

是不是覺得有點熟悉了雄家,對钞翔,ThreadLocal蜕提!前面講了ThreadLocal的set與get方法可以設(shè)置變量為線程的私有變量全庸。那么我們?nèi)タ纯磗ThreadLocal 的set方法在哪調(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));//這里簡單的new了一個looper對象
    }
..........................
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);//創(chuàng)建了messageQueue對象
        mThread = Thread.currentThread();
    }

這也就是我們前面提到的在創(chuàng)建Handler之前不調(diào)用Looper.prepare()方法會報錯的原因。因為sThreadLocal.get()拿到的是空值默色!這樣looper與當前線程就形成了邏輯上的一對一的關(guān)系

了解了looper的創(chuàng)建我們來看看Android的主線程球凰,Android應(yīng)用的入口在ActivityThread類,我們來看看它的main函數(shù):

 public static void main(String[] args) {
      ..................................

     Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();//準備主循環(huán)

        ActivityThread thread = new ActivityThread();//創(chuàng)建ActivityThread對象腿宰,其中會創(chuàng)建一個H 對象呕诉,其實也就是我們說的handler對象
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
      ...........................
        Looper.loop();//啟動主循環(huán)

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

     final H mH = new H();//這是activityThread中創(chuàng)建的H對象

從上面我們可以知道,looper的創(chuàng)建是在 Handler 創(chuàng)建之前的酗失,當一切準備就緒之后义钉,調(diào)用Looper.loop()應(yīng)用程序就進入了死循環(huán)。

回到上面的問題规肴,既然主線程進入了死循環(huán)捶闸,那么這個死循環(huán)會不會導(dǎo)致應(yīng)用卡死夜畴,即使不會卡死會不會過度的耗費系統(tǒng)資源(CPU)?
答案大家都知道 是肯定不會删壮,但是是為什么呢贪绘?
就我個人的理解哈,設(shè)計成死循環(huán)的目的央碟,是讓主線程能一直運行下去(除非用戶自己退出)税灌,我們也不想我們的應(yīng)用程序運行一段時間就自動退出吧?出于這個考慮亿虽,死循環(huán)是最好的選擇菱涤。其實binder內(nèi)部也是采用的死循環(huán)。再回過頭來看Looper.looper方法:

public static void loop() {
        final Looper me = myLooper();//獲取當前線程的looper
        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 (;;) {//死循環(huán) 一個個去取出消息
            Message msg = queue.next(); // might block不斷去取messageQueue中的消息洛勉,這個地方有可能阻塞
            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 {
                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);
            }

            msg.recycleUnchecked();
        }
    }

上面就是整個looper的工作過程,首先拿到當前線程的looper收毫,然后通過這個looper拿到當前的messageQueue攻走,然后進入死循環(huán),不斷的去取messageQueue中的消息此再,即調(diào)用messageQueue中的next方法昔搂,這個方法有可能會阻塞,也就是主線程休眠输拇。如果next方法取到的值為 null 摘符,那么表示消息隊列在退出了,也就是整個線程在退出了

這里的next方法的結(jié)果只會有三種狀態(tài)
1.返回正常值
2.阻塞淳附,表示隊列中沒有消息议慰,主線程進入休眠
3.返回null蠢古,表示線程在退出

上面說了奴曙,在隊列中沒有消息時(實際判斷沒這么簡單),主線程會進入休眠草讶,因此也不至于過度的耗費系統(tǒng)資源洽糟。

簡單說就是在主線程的MessageQueue沒有消息時,便阻塞在loop的queue.next()中的nativePollOnce()方法里堕战,此時主線程會釋放CPU資源進入休眠狀態(tài)坤溃,直到下個消息到達或者有事務(wù)發(fā)生,通過往pipe管道寫端寫入數(shù)據(jù)來喚醒主線程工作嘱丢。所以說薪介,主線程大多數(shù)時候都是處于休眠狀態(tài),并不會消耗大量CPU資源越驻。

2.沒看見哪里有相關(guān)代碼為這個死循環(huán)準備了一個新線程去運轉(zhuǎn)汁政?

這個我們其實在最初接觸android的時候就知道道偷,當一個安卓應(yīng)用啟動的時候,记劈,每個App進程中至少會有兩個binder線程 ApplicationThread(簡稱AT)和ActivityManagerProxy(簡稱AMP)勺鸦,除了圖中畫的線程,其中還有很多線程目木,比如signal catcher線程等换途。

事實上,會在進入死循環(huán)之前便創(chuàng)建了新binder線程刽射,在代碼ActivityThread.main()中:

        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);//便會創(chuàng)建一個Binder線程

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

thread.attach(false)军拟;便會創(chuàng)建一個Binder線程(具體是指ApplicationThread,Binder的服務(wù)端誓禁,用于接收系統(tǒng)服務(wù)AMS發(fā)送來的事件)吻谋,該Binder線程通過Handler將Message發(fā)送給主線程

3. Activity的生命周期是怎么實現(xiàn)在死循環(huán)體外能夠執(zhí)行起來的?

前面說到了ActivityThread中H對象现横,ActivityThread的內(nèi)部類H繼承于Handler漓拾,通過handler消息機制,簡單說Handler機制用于同一個進程的線程間通信戒祠,而Binder是用于進程中通信骇两!

Activity的生命周期都是依靠主線程的Looper.loop,當收到不同Message時則采用相應(yīng)措施:在H.handleMessage(msg)方法中姜盈,根據(jù)接收到不同的msg低千,執(zhí)行相應(yīng)的生命周期。 比如收到msg=H.LAUNCH_ACTIVITY馏颂,則調(diào)用ActivityThread.handleLaunchActivity()方法示血,最終會通過反射機制,創(chuàng)建Activity實例救拉,然后再執(zhí)行Activity.onCreate()等方法难审; 再比如收到msg=H.PAUSE_ACTIVITY,則調(diào)用ActivityThread.handlePauseActivity()方法亿絮,最終會執(zhí)行Activity.onPause()等方法告喊。 上述過程,我只挑核心邏輯講派昧,真正該過程遠比這復(fù)雜黔姜。

主線程的消息又是哪來的呢?當然是App進程中的其他線程通過Handler發(fā)送給主線程蒂萎,請看接下來的內(nèi)容:

Activity的生命周期通信.jpg

結(jié)合圖說說Activity生命周期秆吵,比如暫停Activity,流程如下:

1.線程1的AMS中調(diào)用線程2的ATP五慈;(由于同一個進程的線程間資源共享纳寂,可以相互直接調(diào)用实苞,但需要注意多線程并發(fā)問題)
2.線程2通過binder傳輸?shù)紸pp進程的線程4;
3.線程4通過handler消息機制烈疚,將暫停Activity的消息發(fā)送給主線程黔牵;
4.主線程在looper.loop()中循環(huán)遍歷消息,當收到暫停Activity的消息時爷肝,便將消息分發(fā)給ActivityThread.H.handleMessage()方法猾浦,再經(jīng)過方法的調(diào)用,最后便會調(diào)用到Activity.onPause()灯抛,當onPause()處理完后金赦,繼續(xù)循環(huán)loop下去。

總結(jié)來說就是:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末对嚼,一起剝皮案震驚了整個濱河市夹抗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌纵竖,老刑警劉巖漠烧,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異靡砌,居然都是意外死亡已脓,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門通殃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來度液,“玉大人,你說我怎么就攤上這事画舌《榈#” “怎么了?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵曲聂,是天一觀的道長霹购。 經(jīng)常有香客問我,道長句葵,這世上最難降的妖魔是什么厕鹃? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任兢仰,我火速辦了婚禮乍丈,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘把将。我一直安慰自己轻专,他們只是感情好,可當我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布察蹲。 她就那樣靜靜地躺著请垛,像睡著了一般催训。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上宗收,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天漫拭,我揣著相機與錄音,去河邊找鬼混稽。 笑死采驻,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的匈勋。 我是一名探鬼主播礼旅,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼洽洁!你這毒婦竟也來了痘系?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤饿自,失蹤者是張志新(化名)和其女友劉穎汰翠,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體昭雌,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡奴璃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了城豁。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片苟穆。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖唱星,靈堂內(nèi)的尸體忽然破棺而出雳旅,到底是詐尸還是另有隱情,我是刑警寧澤间聊,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布攒盈,位于F島的核電站,受9級特大地震影響哎榴,放射性物質(zhì)發(fā)生泄漏型豁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一尚蝌、第九天 我趴在偏房一處隱蔽的房頂上張望迎变。 院中可真熱鬧,春花似錦飘言、人聲如沸衣形。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽谆吴。三九已至倒源,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間句狼,已是汗流浹背笋熬。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留腻菇,地道東北人突诬。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像芜繁,于是被迫代替她去往敵國和親旺隙。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,828評論 2 345

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