Android Handler 機(jī)制

前言

Handler 在Android 開發(fā)中是使用頻率非常高的遥巴,所以Android 開發(fā)有必要了解Handler 的機(jī)制蘑辑。下面就個(gè)人理解簡(jiǎn)單介紹一下 Handler 原理,在這里我們

需要了解的類

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

首先要說Looper

Looper 類看名便知其意梯影,循環(huán)兢哭。看Looper 類源碼一定要注意這段代碼:

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

該 ThreadLocal 保存不同線程中的Looper,非常重要返帕。


下面我們?cè)诳碙ooper中的一個(gè) prepare 方法桐玻,先看代碼,代碼如下:

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

在這段代碼中看 sThreadLocal.get() 段代碼荆萤,獲取當(dāng)前線程中Looper 實(shí)例镊靴,寫到這我又看到可下面的異常了,那就讓我們看看這個(gè)異常是什么:Only one Looper may be created per thread, 意思是:每個(gè)線程中只可以創(chuàng)建一個(gè)Looper實(shí)例链韭。然后我們?cè)诳纯催@個(gè)方法的功能偏竟,創(chuàng)建自身線程的 Looper 實(shí)例,并將Looper 保存在ThreadLocal 中敞峭。


在看一下 Looper 中 myLooper() 方法踊谋,代碼如下:

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

代碼簡(jiǎn)單,就是獲取所在線程中的Looper 實(shí)例旋讹。


在看一下 Looper 中 prepareMainLooper() 方法殖蚕,代碼如下:

    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

該方法主要是獲取主線程中的Looper 實(shí)例,該方法在 ActivityThread 中main中執(zhí)行(ActivityThread 類是程序的入口)沉迹,即在程序的入口創(chuàng)建一個(gè)主線程的 Looper 實(shí)例睦疫。同時(shí),Looper 中還有 getMainLooper() 方法鞭呕,代碼如下:

    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }

這段代碼是獲取主線程的 Looper 實(shí)例蛤育。


Looper 有一個(gè)非常重要的方法 loop() ,這個(gè)方法非常重要琅拌,主要是負(fù)責(zé)從 MessageQueue 中取出消息交給 Handler 在該線程中執(zhí)行缨伊,寫到這里,可能會(huì)有些蒙进宝,MessageQueue 在什么時(shí)候被實(shí)例的刻坊,看下面這段代碼就知道了:

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

在Looper 的一個(gè)構(gòu)造方法中MessageQueue被實(shí)例。
下面就要介紹一下 loop() 方法党晋,不多說谭胚,先看看源碼長(zhǎng)什么樣:

    public static void loop() {
        //獲取該線程中的Looper 實(shí)例
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        //獲取該線程中與Looper 相關(guān)聯(lián)的 MessageQueue 對(duì)象
        final MessageQueue queue = me.mQueue;

        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        for (;;) {
            // 從MessageQueue 中取出 Message 徐块,如果沒有信息將會(huì)阻塞到這里
            Message msg = queue.next(); // might block
            if (msg == null) {
                return;
            }

            final Printer logging = me.mLogging;
            final long traceTag = me.mTraceTag;
            if (traceTag != 0) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            try {
                // 調(diào)用 Handler 將 Message 交個(gè) Handler 處理
                msg.target.dispatchMessage(msg);
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            final long newIdent = Binder.clearCallingIdentity();
            msg.recycleUnchecked();
        }
    }

通過上面的這段代碼,我們可以知道 loop() 方法是如何循環(huán)消息的灾而,上面的帶有些沒有用的就刪掉了胡控,在看的時(shí)候也不需要每行都要讀懂,只要把注釋的部分看懂就行旁趟。

現(xiàn)在看一下 Message

Message 類主要是存儲(chǔ)信息的類昼激,通過 Message 中的一些變量就可以看出,下面我們來看一下 Message 中的一個(gè)變量锡搜。
注意:Message 類實(shí)現(xiàn)了 Parcelable 接口進(jìn)行序列化處理橙困,為 Bundle 中進(jìn)行數(shù)據(jù)傳輸

  1. Handler target
    我們可以主要到這個(gè)在Message 中定義了一個(gè) Handler 變量,如果第一次看代碼可能會(huì)有些不知為何耕餐,那我們?cè)趤砘仡櫼幌?Looper 中剛剛我們提高過的一段代碼:
    public static void loop() {
    
       ........
       
        for (;;) {
            // 從MessageQueue 中取出 Message 凡傅,如果沒有信息將會(huì)阻塞到這里
            Message msg = queue.next(); // might block
           
            
            // 調(diào)用 Handler 將 Message 交個(gè) Handler 處理
            msg.target.dispatchMessage(msg);
            
        }
        
        ........
    }

上面代碼是 loop() 方法,無(wú)關(guān)代碼已經(jīng)刪掉肠缔,通過這兩行代碼可以知道夏跷,在循環(huán)信息的時(shí)候,通過 MessageQuery 中取出 Message 消息明未,然后獲取Message 中與該線程綁定的 Handler槽华,再通過取出的該線程中的Handler 消息。

然后我們看一下 Handler

了解Handler 首先一定要看一下Handler 的構(gòu)造方法亚隅,Handler 的構(gòu)造方法有很多硼莽,在這里,我們只看這個(gè) Handler(Callback callback, boolean async) 構(gòu)造方法煮纵。

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

在這些代碼中讓我關(guān)注的代碼只有兩行懂鸵,mLooper = Looper.myLooper();這行代碼很簡(jiǎn)單,獲取該線程的Looper 實(shí)例行疏。再看看 mQueue = mLooper.mQueue; 看到這行代碼就更清楚了吧匆光,獲取該線程中 Looper 中的 MessageQuery 實(shí)例。


然在看一下 sendMessageAtTime(Message msg, long uptimeMillis) 和 enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) 兩個(gè)方法:


    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) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

看到上面這兩個(gè)方法代碼也不多酿联,從代碼中可以看到终息,MessageQueue queue = mQueue;和queue.enqueueMessage(msg, uptimeMillis); 是向 MessageQueue 中添加 Message 消息。下面要重點(diǎn)說一下 msg.target = this; 這行代碼贞让,這行代碼和前面介紹 Message 中變量相關(guān)聯(lián)周崭,這行代碼是讓 Message 持有這個(gè)Handler 實(shí)例,在Looper 循環(huán)消息時(shí)可以獲取到該實(shí)例調(diào)用 Handler 中的 dispatchMessage 方法(下面會(huì)介紹 dispatchMessage)喳张,從而調(diào)用 handleMessage续镇。


那么現(xiàn)在我們就來看一下 Handler 中的 dispatchMessage(Message msg) 方法:

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

不用解釋了吧, handleMessage(msg);

最后我們看一下 MessageQueue

在這個(gè)類中我們只看一個(gè)方法 enqueueMessage(Message msg, long when) 方法:

    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 {
                // 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;
    }

這個(gè)方法代碼很多销部,我么刪一些沒用的簡(jiǎn)化一下:

    boolean enqueueMessage(Message msg, long when) {

        synchronized (this) {
            if (mQuitting) {
                msg.recycle();
                return false;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                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;
            }
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

這個(gè)方法主要是將 Message 存入到 MessageQueue 中摸航,供Looper 循環(huán)使用制跟。


說了這么多,就會(huì)有人想問酱虎,既然 Looper 是 Handler 的核心雨膨,那么MainLooper 在什么時(shí)候被創(chuàng)建,下面我們?cè)诳匆幌?ActivityThread 類读串,一個(gè) Android 程序的入口就在 ActivityThread 類中聊记,所以,我們要看一下該類中 main 方法中的實(shí)現(xiàn):

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

        Looper.loop();
        
        ......

    }
}

可以看到在main 函數(shù)中的兩行代碼創(chuàng)建了 Looper 實(shí)例和啟動(dòng) Looepr 中的loop() 循環(huán)爹土。

總結(jié)

在程序啟動(dòng)時(shí)甥雕,ActivityThread 類中啟動(dòng)應(yīng)用,創(chuàng)建UI Thread 的 Looper胀茵, 在創(chuàng)建 UI Thread Looper 后又創(chuàng)建基于該線程的 MessageQuery 與該線程的Looper 進(jìn)行關(guān)聯(lián),所以在activity 中創(chuàng)建的 Handler 是基于 UI Thread 的挟阻,所以 Looper 和 MessageQuery 早就已經(jīng)創(chuàng)建好了琼娘。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市附鸽,隨后出現(xiàn)的幾起案子脱拼,更是在濱河造成了極大的恐慌,老刑警劉巖坷备,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件熄浓,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡省撑,警方通過查閱死者的電腦和手機(jī)赌蔑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來竟秫,“玉大人娃惯,你說我怎么就攤上這事》拾埽” “怎么了趾浅?”我有些...
    開封第一講書人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)馒稍。 經(jīng)常有香客問我皿哨,道長(zhǎng),這世上最難降的妖魔是什么纽谒? 我笑而不...
    開封第一講書人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任证膨,我火速辦了婚禮,結(jié)果婚禮上佛舱,老公的妹妹穿的比我還像新娘椎例。我一直安慰自己挨决,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開白布订歪。 她就那樣靜靜地躺著脖祈,像睡著了一般。 火紅的嫁衣襯著肌膚如雪刷晋。 梳的紋絲不亂的頭發(fā)上盖高,一...
    開封第一講書人閱讀 49,772評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音眼虱,去河邊找鬼喻奥。 笑死,一個(gè)胖子當(dāng)著我的面吹牛捏悬,可吹牛的內(nèi)容都是我干的撞蚕。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼过牙,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼甥厦!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起寇钉,我...
    開封第一講書人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤刀疙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后扫倡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谦秧,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年撵溃,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了疚鲤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡征懈,死狀恐怖石咬,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情卖哎,我是刑警寧澤鬼悠,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站亏娜,受9級(jí)特大地震影響焕窝,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜维贺,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一它掂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦虐秋、人聲如沸榕茧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)用押。三九已至,卻和暖如春靶剑,著一層夾襖步出監(jiān)牢的瞬間蜻拨,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工桩引, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留缎讼,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓坑匠,卻偏偏與公主長(zhǎng)得像血崭,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子笛辟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

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