Android Handler機(jī)制

作為一個(gè)Android程序員,我想大家都知道在做一些比較耗時(shí)的操作的時(shí)候都不會(huì)放在主線程,比如網(wǎng)絡(luò)請(qǐng)求逼泣、數(shù)據(jù)庫(kù)操作等(尤其是網(wǎng)絡(luò)請(qǐng)求撮胧,在Android4.0之后強(qiáng)制不能放在主線程中執(zhí)行一罩,否則拋出android.os.NetworkOnMainThreadException異常)廊驼,而更新UI的操作的都是由UI線程處理犹芹。那么有這樣一個(gè)需求熙涤,我在一個(gè)新的線程中獲取到數(shù)據(jù)阁苞,然后設(shè)置到界面上困檩,這就涉及到了兩個(gè)線程,用于更新界面的UI線程不知道什么時(shí)候數(shù)據(jù)獲取完成可以更新界面了那槽,那怎么辦呢悼沿?Google就給我們提供了一個(gè)解決方案,也就是我們今天主要說(shuō)明的內(nèi)容——Android中的Handler消息機(jī)制骚灸。

首先來(lái)一個(gè)簡(jiǎn)單使用:

private static final int HANDLER_TEST_WHAT = 0x01; // 定義測(cè)試what

// 創(chuàng)建Handler對(duì)象糟趾,并重寫(xiě)handleMessage()方法處理消息
private Handler handler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        int what = msg.what; // 獲取消息的what
        // 通過(guò)what對(duì)消息進(jìn)行判斷,不同消息不同的處理
        if(what == HANDLER_TEST_WHAT){
        // 處理消息
        }
    }
};

// 創(chuàng)建一個(gè)子線程發(fā)送消息
new Thread(){
    @Override
    public void run() {
    Message message = Message.obtain(); // 創(chuàng)建一個(gè)消息對(duì)象甚牲,使用obtain()重用回收的消息對(duì)象
    message.what = HANDLER_TEST_WHAT;   // 設(shè)置Message的 what
    handler.sendMessage(message);       // 發(fā)送一個(gè)消息
    }
}.start();

上面就是一個(gè)簡(jiǎn)單的例子义郑,在子線程發(fā)送一個(gè)消息,然后在主線程就可以對(duì)消息進(jìn)行處理丈钙,當(dāng)然包括更新UI或者其他操作非驮。

下面我們就從源碼的角度查看與分析一下Handler消息機(jī)制,他是怎樣將一個(gè)子線程的數(shù)據(jù)傳遞到主線程中的雏赦。

介紹Handler機(jī)制之前劫笙,首先了解幾個(gè)概念:

  • Message:消息,理解為線程間通訊的數(shù)據(jù)單元星岗。例如后臺(tái)線程在處理數(shù)據(jù)完畢后需要更新UI填大,則可發(fā)送一條包含更新信息的Message給UI線程。
  • MessageQueue:消息隊(duì)列俏橘,用來(lái)存放通過(guò)Handler發(fā)布的消息并按時(shí)間進(jìn)行排序允华,按照先進(jìn)先出執(zhí)行。
  • Handler:Handler是Message的主要處理者敷矫,負(fù)責(zé)將Message添加到消息隊(duì)列以及對(duì)消息隊(duì)列中的Message進(jìn)行處理例获。
  • Looper:輪詢器,扮演MessageQueue和Handler之間橋梁的角色曹仗,循環(huán)取出MessageQueue里面的Message榨汤,并交付給相應(yīng)的Handler進(jìn)行處理。

消息機(jī)制分析

從上面的示例中怎茫,我們先創(chuàng)建了一個(gè)Handler對(duì)象并且重寫(xiě)了 handleMessage() 方法用來(lái)處理接收到的消息收壕,而消息的發(fā)送是通過(guò) Handler 的sendMessage() 方法。也可以說(shuō)轨蛤,Handler機(jī)制是由 sendMessage() 方法開(kāi)始蜜宪,到 handleMessage() 方法結(jié)束,現(xiàn)在我們就來(lái)一步一步看看數(shù)據(jù)是怎樣傳遞的祥山,線程又是怎樣切換的圃验。

首先看看創(chuàng)建 Handler

最終調(diào)用的是 帶兩個(gè)參數(shù) 的構(gòu)造方法,如下:

    public Handler(@Nullable Callback callback, boolean async) {
        
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
    }

說(shuō)明:

  1. Looper.myLooper():獲取當(dāng)前線程的 Looper 對(duì)象缝呕,也就是線程需要是一個(gè) Looper 線程澳窑,主線程就是一個(gè) Looper 線程斧散,主線程為什么是一個(gè)Looper線程以及怎樣將一個(gè)線程變?yōu)?Looper 線程,在《Android Handler分析 (三) Looper詳解和Handler其他知識(shí)》中有說(shuō)到摊聋。

     public static @Nullable Looper myLooper() {
         return sThreadLocal.get();
     }
    
  2. mLooper.mQueue:就是一個(gè) MessageQueue 對(duì)象鸡捐,他是在 Looper 構(gòu)造方法中初始化的

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

我們創(chuàng)建了 Handler 對(duì)象之后,然后通過(guò) Handler 的 sendMessage() 方法發(fā)送一個(gè) Message麻裁,接下來(lái)看看 sendMessage() 方法做了寫(xiě)什么事

Handler#sendMessage() 方法

    public final boolean sendMessage(@NonNull Message msg) {
        return sendMessageDelayed(msg, 0);
    }

    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

    public boolean sendMessageAtTime(@NonNull 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(@NonNull MessageQueue queue, @NonNull Message msg, long uptimeMillis) {
        // 注意這行代碼箍镜,將 Message 的 target 變量賦值成 this,也就是當(dāng)前 Handler 對(duì)象
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

說(shuō)明:

  1. sendMessage() 方法調(diào)用了一些 Message 類自身的發(fā)送方法煎源,主要是處理消息發(fā)送時(shí)間(立馬發(fā)送還是延遲發(fā)送等)
  2. 最終調(diào)用 enqueueMessage() 方法色迂,在這個(gè)方法中又調(diào)用了 queue.enqueueMessage(msg, uptimeMillis) ,queue 就是 MessageQueue手销,所以查看一下 MessageQueue 中的 enqueueMessage() 方法
  3. 重點(diǎn)注意enqueueMessage() 方法中的 msg.target = this 代碼脚草,將 Message 的 target 變量賦值成 this,也就是當(dāng)前 Handler對(duì)象

MessageQueue#enqueueMessage() 方法

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) {
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            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è)方法所做的事情就是將 Message 對(duì)象根據(jù)發(fā)送時(shí)間添加到隊(duì)列中原献,方法的具體分析在 《Android Handler分析 (二) MessageQueue詳解》中。

Message 消息對(duì)象添加到 MessageQueue 中之后埂淮,在哪里取出來(lái)姑隅,并且傳遞到 handleMessage() 方法呢?

這里我們就有必要先說(shuō)一下 Looper 線程了倔撞,一個(gè)類變?yōu)長(zhǎng)ooper線程并且能處理消息讲仰,主要有兩步:

  1. 調(diào)用 Looper.prepare() 方法
  2. 調(diào)用 Looper.loop() 方法開(kāi)始輪詢

至于這兩個(gè)方法具內(nèi)容是什么我們這里就不說(shuō)了,在《Android Handler分析 (三) Looper詳解和Handler其他知識(shí)》 中有分析痪蝇,只需要知道 loop() 方法最重要的作用就是不斷的循環(huán)調(diào)用 MessageQueue 的 next() 方法去獲取 Message 鄙陡,然后進(jìn)行處理,而 MessageQueue 的 next() 方法的消息分析在 《Android Handler分析 (二) MessageQueue詳解》中躏啰,它的主要作用就是按照順序從隊(duì)列里面取出 Message趁矾。

所以我們直接看 MessageQueue.next() 取到消息之后 Looper.loop() 方法是怎樣處理的,核心代碼如下:

public static void loop() {
    final Looper me = myLooper();
    final MessageQueue queue = me.mQueue;
    Message msg = queue.next();
    try {
        msg.target.dispatchMessage(msg);
    } catch (Exception exception) {
    } finally {
    }
}

說(shuō)明:

通過(guò)獲取的 Message 對(duì)象的變量 target, 調(diào)用它的 dispatchMessage() 方法给僵,并且將 msg 對(duì)象傳遞給 dispatchMessage() 方法毫捣。在上面,我們已經(jīng)知道了 target 變量表示的就是 Handler帝际,所以這里調(diào)用的就是 Handler 的 dispatchMessage() 方法了蔓同。

Handler#dispatchMessage() 方法

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

dispatchMessage() 方法中調(diào)用了 handleMessage(msg) 方法并傳遞 Message,這個(gè)方法我們?cè)趧?chuàng)建 Handler 時(shí)重寫(xiě)了蹲诀。所以就走到了我們自己處理消息的部分斑粱。因?yàn)槲覀兊倪@個(gè)Handler是在主線程創(chuàng)建的并且綁定的也是主線程的Looper,所以這個(gè)方法就在主線程當(dāng)中執(zhí)行了脯爪。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末则北,一起剝皮案震驚了整個(gè)濱河市矿微,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌咒锻,老刑警劉巖冷冗,帶你破解...
    沈念sama閱讀 223,002評(píng)論 6 519
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異惑艇,居然都是意外死亡蒿辙,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,357評(píng)論 3 400
  • 文/潘曉璐 我一進(jìn)店門(mén)滨巴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)思灌,“玉大人,你說(shuō)我怎么就攤上這事恭取√┏ィ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,787評(píng)論 0 365
  • 文/不壞的土叔 我叫張陵蜈垮,是天一觀的道長(zhǎng)耗跛。 經(jīng)常有香客問(wèn)我,道長(zhǎng)攒发,這世上最難降的妖魔是什么调塌? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,237評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮惠猿,結(jié)果婚禮上羔砾,老公的妹妹穿的比我還像新娘。我一直安慰自己偶妖,他們只是感情好姜凄,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,237評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著趾访,像睡著了一般态秧。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上腹缩,一...
    開(kāi)封第一講書(shū)人閱讀 52,821評(píng)論 1 314
  • 那天屿聋,我揣著相機(jī)與錄音,去河邊找鬼藏鹊。 笑死润讥,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的盘寡。 我是一名探鬼主播楚殿,決...
    沈念sama閱讀 41,236評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了脆粥?” 一聲冷哼從身側(cè)響起砌溺,我...
    開(kāi)封第一講書(shū)人閱讀 40,196評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎变隔,沒(méi)想到半個(gè)月后规伐,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,716評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡匣缘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,794評(píng)論 3 343
  • 正文 我和宋清朗相戀三年猖闪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肌厨。...
    茶點(diǎn)故事閱讀 40,928評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡培慌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出柑爸,到底是詐尸還是另有隱情吵护,我是刑警寧澤,帶...
    沈念sama閱讀 36,583評(píng)論 5 351
  • 正文 年R本政府宣布表鳍,位于F島的核電站馅而,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏譬圣。R本人自食惡果不足惜用爪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,264評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望胁镐。 院中可真熱鬧,春花似錦诸衔、人聲如沸盯漂。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,755評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)就缆。三九已至,卻和暖如春谒亦,著一層夾襖步出監(jiān)牢的瞬間竭宰,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,869評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工份招, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留切揭,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,378評(píng)論 3 379
  • 正文 我出身青樓锁摔,卻偏偏與公主長(zhǎng)得像廓旬,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子谐腰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,937評(píng)論 2 361

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