Handler機制之源碼解析

Handler源碼解析

剛?cè)胄蠥ndroid那會翰意,看過好多遍這方面的不同的博客结榄,每次看完都似懂非懂歇盼,今天我打算自己來分析一下Handler機制及其相關(guān)源碼违寿。

主要涉及以下幾個類:

  1. Handler
  2. MessageQueue
  3. Looper
  4. Message

看一下官方對Handler這個類的解釋:Handler允許你向其發(fā)送Message或者是post Runnable,一個Handler只能綁定一個線程以及線程對應(yīng)的MessageQueue扫倡。當我們創(chuàng)建一個新的Handler的時候谦秧,它就和創(chuàng)造它的時候所在的線程以及線程對應(yīng)的MessageQueue綁定了,它將用來將messages和runnables傳送給MessageQueue撵溃,以及執(zhí)行他們當他們從MessageQueue隊列中被pop出來的時候疚鲤。

Handler有兩種用處:一是用來執(zhí)行同一線程中的messages以及runnables,二是在另一個線程中執(zhí)行缘挑。
發(fā)送消息的方式有很多種集歇,主要是由以下方法來完成的:
一類是post runnable
post
postAtTime
postDelayed
一類是send message
sendEmptyMessage
sendMessage
sendMessageAtTime
sendMessageDelayed

這些方法能夠讓你控制是現(xiàn)在執(zhí)行,還是需要延遲多長時間來執(zhí)行语淘。

當我們開啟一個應(yīng)用時诲宇,即開啟了一個進程际歼,應(yīng)用的主線程就會開啟一個MessageQueue隊列用來管理最重要的一些對象,如Activity和廣播焕窝,你可以創(chuàng)建一個子線程,通過Handler來與主線程或者應(yīng)用來進行通信维贺,當合適的時候它掂,這些runnable或者message會被執(zhí)行。

通過大概翻譯官方注釋溯泣,我們可以發(fā)現(xiàn)Handler主要是用來做線程間通信的虐秋,分析Handler我們主要分析上面提到的兩類方法:1.post runnable 2.send message

我們先分析send 系列方法:

??通過查看源碼,我們可以發(fā)現(xiàn)垃沦,不論是sendMessage客给,還是sendMessageDelayed還是sendEmptyMessage,最終都會調(diào)用sendMessageAtTime方法肢簿。這里面涉及到一個Message對象靶剑,我們稍后來分析,最終會走到enqueueMessage方法池充,enqueueMessage方法會走到MessageQueue里面的enqueueMassage桩引。待會我們一起分析。

我們再來看post runnable系列方法:

??通過查看源碼收夸,我們可以發(fā)現(xiàn)坑匠,最終也是走到sendMessageAtTime方法,然后調(diào)用enqueueMessage卧惜。只不過厘灼,在這個過程中,創(chuàng)建了一個Message實例咽瓷,并將runnable賦給了message的callback设凹。

Handler我們先分析到這里。

下面我們來分析Message這個類:

Message這個類的數(shù)據(jù)結(jié)構(gòu)可以說是一個單鏈表茅姜,它有幾個重要的成員變量:

  1. next 指向鏈表下一item的引用
  2. what 用來區(qū)別是哪一類消息围来,相當于一個type
  3. target 用來保存所屬Handler的引用
  4. callback 用來保存要執(zhí)行的任務(wù)runnable

其他還包括arg1,arg2 匈睁,object等监透,這些就不一一解釋了

Message類還有一個重要方法:obtain。

當我們創(chuàng)建一個Message對象時航唆,我們可以new胀蛮,也可以通過Message.obtain方法來獲取一個實例。官方推薦后者糯钙,

/**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    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();
    }

意思就是說粪狼,這種方式能夠避免重復(fù)創(chuàng)建新對象退腥。從代碼可以看出,只有當Message回收池為空的時候才會去new一個Message再榄。那么這個Message回收池是什么時候建立的呢狡刘,以及什么時候往里面放對象的呢?我們找到recycleUnchecked這個方法,可以發(fā)現(xiàn)這個方法就是將Message的flag置為FLAG_IN_USE困鸥,并且清空其他參數(shù)嗅蔬,并將其加入到緩存池。這個方法是在MessageQueue執(zhí)行enqueueMessage的時候才會調(diào)用疾就。

下面我們來分析MessageQueue這個類:

首先澜术,看它的構(gòu)造,除了傳了一個參數(shù)猬腰,其它的都在native處理了鸟废,我們也看不見什么,感興趣的可以去看一下native層代碼姑荷。

再看一下MessageQueue的數(shù)據(jù)結(jié)構(gòu)盒延,我們可以發(fā)現(xiàn)它其實是一個優(yōu)先級隊列。

隊列的插入以及移除item有兩個重要方法:

第一個是我么上面提到的enqueueMessage方法:這個方法主要是向隊列中插入Message對象:

    boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                //這里是向頭部插入
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                //這里是往中間插入,循環(huán)鏈表觸發(fā)時間,找到對應(yīng)的插入位置
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

第二個就是MessageQueue的next方法,這個方法主要處理的是MessageQueue的出隊操作,即從隊頭開始一個個執(zhí)行移除.代碼太多,就不貼過來了,主要部分就是指針后移:

mBlocked = false;
if (prevMsg != null) {
    prevMsg.next = msg.next;
    } else {
      mMessages = msg.next;
   }
    msg.next = null;
    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
    msg.markInUse();
    return msg;

那么,什么時候才會執(zhí)行next的方法呢?這就得來到Looper這個類了.

Looper這個類,每個線程都只有一個,跟線程綁定.我們看一下他的主要方法:

 public static void loop() {
        final Looper me = myLooper();
      

        for (;;) {
            Message msg = queue.next(); // might block
            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 slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;

            final long traceTag = me.mTraceTag;
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            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);
                }
            }
            if (slowDispatchThresholdMs > 0) {
                final long time = end - start;
                if (time > slowDispatchThresholdMs) {
                    Slog.w(TAG, "Dispatch took " + time + "ms on "
                            + Thread.currentThread().getName() + ", h=" +
                            msg.target + " cb=" + msg.callback + " msg=" + msg.what);
                }
            }

            msg.recycleUnchecked();
        }
    }

從上面的代碼可以發(fā)現(xiàn),loop差不多就是一個死循環(huán),只有當MessageQueue為空的時候,才會退出.Looper當中有兩個重要的地方:

第一個是 Message msg = queue.next(),從隊列中取出一個Message,
第二個是 Message msg = queue.next(),這個方法最終會調(diào)用message對應(yīng)handler的dispatchMessage方法
public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

當msg的callback不為空時,執(zhí)行callback.run,如果為空的話則判斷在創(chuàng)建Handler的時候有沒有給mCallback賦值,如果有,則走mCallback里面的handleMessage方法,如果沒有則走Handler的handleMessage方法.

但是,在實際開發(fā)當中,我們發(fā)現(xiàn)我們并沒有創(chuàng)建Looper,也沒有調(diào)用Looper的loop方法,為什么也能夠執(zhí)行到我們重寫的handleMessage方法呢?

這是因為,我們一般是在主線程即UI線程中創(chuàng)建的Handler,而在主線程中是默認創(chuàng)建了Looper的.看代碼:

 public static void main(String[] args) {
        

        Looper.prepareMainLooper();

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

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

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

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

這段代碼是截取自ActivityThread的main函數(shù),在我們應(yīng)用啟動的時候,就執(zhí)行了這個函數(shù).

至此,我們把Handler機制的重要代碼都分析完了!!

總結(jié)一下:畫個圖,看懂你就贏了

Handler機制.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鼠冕,一起剝皮案震驚了整個濱河市兰英,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌供鸠,老刑警劉巖畦贸,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異楞捂,居然都是意外死亡薄坏,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進店門寨闹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來胶坠,“玉大人,你說我怎么就攤上這事繁堡∩蛏疲” “怎么了?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵椭蹄,是天一觀的道長闻牡。 經(jīng)常有香客問我,道長绳矩,這世上最難降的妖魔是什么罩润? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮翼馆,結(jié)果婚禮上割以,老公的妹妹穿的比我還像新娘金度。我一直安慰自己,他們只是感情好严沥,可當我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布猜极。 她就那樣靜靜地躺著,像睡著了一般消玄。 火紅的嫁衣襯著肌膚如雪跟伏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天莱找,我揣著相機與錄音酬姆,去河邊找鬼嗜桌。 笑死奥溺,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的骨宠。 我是一名探鬼主播浮定,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼层亿!你這毒婦竟也來了桦卒?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤匿又,失蹤者是張志新(化名)和其女友劉穎方灾,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體碌更,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡裕偿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了痛单。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嘿棘。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖旭绒,靈堂內(nèi)的尸體忽然破棺而出鸟妙,到底是詐尸還是另有隱情,我是刑警寧澤挥吵,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布重父,位于F島的核電站,受9級特大地震影響忽匈,放射性物質(zhì)發(fā)生泄漏坪郭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一脉幢、第九天 我趴在偏房一處隱蔽的房頂上張望歪沃。 院中可真熱鬧嗦锐,春花似錦、人聲如沸沪曙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽液走。三九已至碳默,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間缘眶,已是汗流浹背嘱根。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留巷懈,地道東北人该抒。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像顶燕,于是被迫代替她去往敵國和親凑保。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,864評論 2 354

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