Handler運(yùn)行機(jī)制解析

Handler簡單介紹與使用

說起Handler堪簿,大多數(shù)Android開發(fā)者會想到:在子線程中更新UI,這確實(shí)是Handler的主要用途之一。分析Handler的運(yùn)行機(jī)制托猩,就從最簡單的使用開始吧:

private Handler mHandler;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            Log.i("thread",Thread.currentThread().getName());
            Bundle data = msg.getData();
            Log.i("message",data.getString("msg"));
            return true;
        }
    });

    new Thread(new Runnable() {
        @Override
        public void run() {
            Message message = Message.obtain();
            Bundle bundle = new Bundle();
            bundle.putString("msg","This is in the child thread message");
            message.setData(bundle);
            mHandler.sendMessage(message);
        }
    }).start();
}

上面的代碼應(yīng)該沒什么好解釋的碧磅,首先在onCreate方法中第三行創(chuàng)建了Handler對象碘箍,然后在子線程中創(chuàng)建出一個Message對象,并往Message對象中set了一個Bundle鲸郊,最終調(diào)用Handler的sendMessage方法丰榴。Handler的handleMessage方法會被調(diào)用,這個handleMessage方法是回調(diào)在主線程的秆撮,我們也就成功實(shí)現(xiàn)了將子線程的消息發(fā)送給主線程處理四濒,因此控制臺輸出如下:

I/thread: main
I/message: This is in the child thread message

對于使用而言,知道這么用就行了,但本文是對Handler運(yùn)行機(jī)制的總結(jié)盗蟆,因此會繼續(xù)深入戈二。

先來看Handler的構(gòu)造器:

public Handler(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;
    mCallback = callback;
    mAsynchronous = async;
}

從這里我們至少能得到以下信息:Handler與Looper,MessageQueue有關(guān)喳资。在構(gòu)造方法中第一行就是獲取Looper觉吭,跟進(jìn)到myLooper方法中:return sThreadLocal.get();實(shí)際上是獲取當(dāng)前線程的Looper,最后將當(dāng)前線程的Looper對象引用賦給了自己的成員變量mLooper仆邓,如果獲取到的Looper是null鲜滩,就會拋出異常。接著看mLooper.mQueue节值,這個mQueue實(shí)際上就是當(dāng)前線程的Looper中的MessageQueue徙硅,在Handler的構(gòu)造器中,將當(dāng)前線程Looper中的MessageQueue對象引用賦給了自己的成員變量mQueue搞疗。

為何在子線程中創(chuàng)建Handler會報異常嗓蘑?

還記得剛才Handler的構(gòu)造器中需要獲取當(dāng)前線程的Looper嗎?如果獲取不到贴汪,就會拋出異常脐往,我們實(shí)驗(yàn)一下:

new Thread(new Runnable() {
    @Override
    public void run() {
        mHandler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                return false;
            }
        });
    }
}).start();

果然GG:

java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

這是因?yàn)樽泳€程中,默認(rèn)是沒有Looper的扳埂,所以當(dāng)然會報錯业簿。那按照它提供的方法,在創(chuàng)建Handler前阳懂,調(diào)用一下Looper.prepare方法梅尤,就不會有問題了。那這個prepare方法究竟做了什么?實(shí)際上猜都能猜出來岩调,肯定是給當(dāng)前線程創(chuàng)建了一個Looper:

public static void prepare() {
    prepare(true);
}

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.set(new Looper(quitAllowed));這里就往當(dāng)前線程中設(shè)置了Looper巷燥。子線程有了Looper,Looper中有MessageQueue号枕,滿足了創(chuàng)建Handler的條件缰揪。雖然這樣寫,創(chuàng)建Handler是沒有問題了葱淳,但是此時Handler是無法處理Message的钝腺,還必須要調(diào)用下Looper.loop方法,讓Looper開始循環(huán)取出MessageQueue中的消息交給Handler赞厕。那為何主線程不需要調(diào)用prepare方法就能直接創(chuàng)建Handler了呢艳狐?因?yàn)樵贏ctivityThread中,已經(jīng)幫我們創(chuàng)建好了Looper皿桑,所以無需我們手動再去創(chuàng)建毫目。因此蔬啡,如果我們想在子線程中創(chuàng)建Handler,并正常使用镀虐,應(yīng)該這樣寫:

new Thread(new Runnable() {
    @Override
    public void run() {
        Looper.prepare();
        mHandler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                return false;
            }
        });
        Looper.loop();
    }
}).start();

Handler的工作流程

其實(shí)說了這么多箱蟆,依然不知道Handler、Looper粉私、MessageQueue三者間到底是什么關(guān)系顽腾,是如何協(xié)作工作的近零。在這里诺核,就先簡單說一下它們?nèi)唛g的關(guān)系,后面再具體分析每一個久信。當(dāng)創(chuàng)建了Handler以后窖杀,Handler就獲取到了當(dāng)前線程的Looper和MessageQueue。MessageQueue顧名思義就是消息隊(duì)列的意思裙士,維護(hù)著當(dāng)前線程未處理的消息列表入客。雖然叫隊(duì)列,但是它內(nèi)部的數(shù)據(jù)結(jié)構(gòu)是單鏈表腿椎,這樣做的優(yōu)點(diǎn)就是增加和刪除速度更快桌硫,因?yàn)閷τ贛essageQueue來說,最主要的操作就是新增啃炸、讀取和刪除铆隘。當(dāng)調(diào)用Handler的send系列方法時,會向MessageQueue中放入這個Message南用,放入了Message后膀钠,怎么才能交給Handler處理呢?答案是Looper裹虫。Looper的作用簡單理解就是一個死循環(huán)肿嘲,不斷的從MessageQueue中取出Message交給Handler,Handler就會根據(jù)dispatchMessage的分發(fā)對消息進(jìn)行處理筑公。注意Looper所在的線程是創(chuàng)建Looper的線程雳窟,我們在主線程中創(chuàng)建Handler使用的是主線程的Looper,因此handleMessage的調(diào)用是在主線程匣屡,而如果我們在子線程通過Looper.prepare創(chuàng)建了Looper封救,這個Looper是屬于子線程的,自然Looper中的MessageQueue也是在子線程中的耸采。這個時候我們在子線程創(chuàng)建Handler兴泥,這個Handler獲取到的Looper就是這個子線程的Looper,那么Looper循環(huán)MessageQueue時取出的Message在交給handleMessage處理時虾宇,也是處于子線程的搓彻。不要以為只要用了Handler,它的handleMessage就一定是回調(diào)在主線程,這個是和Handler所使用的Looper有關(guān)的旭贬。

MessageQueue

前面提到過MessageQueue是Android中的消息隊(duì)列怔接,雖然叫隊(duì)列,但是內(nèi)部實(shí)現(xiàn)并不是隊(duì)列稀轨,而是單鏈表扼脐。當(dāng)我們調(diào)用Handler的sendMessage方法的時候,其實(shí)最終都是向MessageQueue中插入了一個Message:

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

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

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

最終奋刽,調(diào)用的是enqueueMessage方法瓦侮,這個方法里面,就會去調(diào)用當(dāng)前線程的Looper中的MessageQueue的enqueueMessage方法佣谐,這個方法就是往MessageQueue中插入一個Message肚吏。光插入了消息,如何才能取出來交給Handler處理呢狭魂?這就是Looper的事情了罚攀。

Looper

Looper是與線程所關(guān)聯(lián)的,一個線程只能有一個Looper雌澄,主線程初始化時斋泄,默認(rèn)就會創(chuàng)建Looper,這也是為何能直接在主線程中創(chuàng)建使用Handler的原因镐牺。主線程創(chuàng)建Looper是在ActivityThread.java中的main方法中創(chuàng)建的炫掐,這也是Android應(yīng)用程序的入口:

//省略部分代碼
public static void main(String[] args) {
    //創(chuàng)建Looper
    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"));
    }
    
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    //開始消息循環(huán)
    Looper.loop();

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

我們知道,要讓Looper開始消息循環(huán)任柜,需要調(diào)用Looper.loop方法卒废,這也是為什么我們在子線程中創(chuàng)建Handler后,還要調(diào)用Looper.loop的原因宙地,如果不調(diào)用摔认,loop方法是不執(zhí)行的,也就不會有循環(huán)讀取消息隊(duì)列中的消息交給Handler處理了宅粥。重點(diǎn)看下Looper的loop方法:

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;
    ...
    for (;;) {
        //取出Message
        Message msg = queue.next(); 
        if (msg == null) {
            return;
        }
        ...
        msg.target.dispatchMessage(msg);
        ...
        msg.recycleUnchecked();
    }
}

首先獲取了當(dāng)前線程的Looper和MessageQueue参袱,然后開起了死循環(huán),不停的從MessageQueue中取出Message(通過MessageQueue的next方法)秽梅。如果取出的Message不為null抹蚀,就會執(zhí)行msg.target.dispatchMessage(msg); target是什么呢?我們再回過頭看看Handler將Message放進(jìn)MessageQueue的代碼:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

target就是你正在使用的那個Handler的對象企垦。因此當(dāng)Looper從MessageQueue中取出一條Message后环壤,就會調(diào)用對應(yīng)的Handler對象中的dispatchMessage方法。如果MessageQueue中已經(jīng)沒有任何消息了钞诡,就會一直阻塞在MessageQueue的next方法郑现,直到有新消息到來湃崩。

Handler

既然Looper從MessageQueue中取出Message后,調(diào)用了Handler的dispatchMessage方法接箫,我們就來看下dispatchMessage方法中攒读,做了什么事情?

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

感覺有點(diǎn)疑問辛友,msg.callback薄扁,這個callback是什么?查看Message類的成員變量發(fā)現(xiàn):Runnable callback废累,什么時候用到呢邓梅?我們回想一下Handler中有post這個方法,用法很簡單九默,例如我們要在子線程中更新UI震放,也可以這樣做:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    final Handler handler = new Handler();
    new Thread(new Runnable() {
        @Override
        public void run() {
            handler.post(new Runnable() {
                @Override
                public void run() {
                    Log.i("thread", Thread.currentThread().getName());
                }
            });
        }
    }).start();
}

這個Runnable中的run方法就是回調(diào)在主線程的,其實(shí)原理都是一模一樣的驼修,我們看Handler的post方法

public final boolean post(Runnable r) {
    return  sendMessageDelayed(getPostMessage(r), 0);
}

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

原來,和我們調(diào)用send系列方法一樣诈铛,最終都是將Message放進(jìn)MessageQueue中乙各!只不過send系列方法是你顯示傳入一個Message,而post是你傳入一個Runnable幢竹,它內(nèi)部調(diào)用getPostMessage方法給你隱式構(gòu)建一個Message耳峦,并且把構(gòu)建的Message對象的callbak字段賦值為傳過來的Runnable。因此回到Handler的dispatchMessage方法焕毫,如果我們使用post方式蹲坷,則msg.callback就不為null,就會執(zhí)行handleCallback:

private static void handleCallback(Message message) {
    message.callback.run();
}

這樣就調(diào)用了Runnable中的run方法邑飒。而如果我們使用的send方式循签,那么msg.callback就是null,就會執(zhí)行下面的:else語句塊:

if (mCallback != null) {
    if (mCallback.handleMessage(msg)) {
        return;
    }
}
handleMessage(msg);

這里的mCallback還有印象嗎疙咸?回想下我們之前看的Handler的構(gòu)造器中有這么一句:

public Handler(Callback callback) {
    this(callback, false);
}

public Handler(Callback callback, boolean async) {
    ...
    mCallback = callback;
    ...
}

這個mCallback就是我們之前在創(chuàng)建Handler時傳的匿名內(nèi)部類县匠,因此我們匿名內(nèi)部類中的handleMessage方法就會被回調(diào)。注意看最后一行撒轮,還有一個handleMessage乞旦,這個在什么情況下會用呢?比如你這樣創(chuàng)建Handler時:

Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
    }
};

這樣創(chuàng)建Handler题山,mCallback就為null兰粉,因此回調(diào)的就是Handler的handleMessage了。
通過查看dispatchMessage源碼,我們現(xiàn)在可以說:優(yōu)先級最高的是handleCallback笑诅,其次是Callback中的handleMessage,這個回調(diào)中的返回值決定了是否還要繼續(xù)回調(diào)Handler中的handleMessage衷笋,優(yōu)先級最低的就是Handler中的handleMessage客峭。

總結(jié)

要使用Handler豫领,就必須要有Looper,有了Looper舔琅,就自然有了MessageQueue等恐。主線程中,自帶了Looper备蚓,因此不需要手動創(chuàng)建Looper就可以正常使用Handler课蔬。而子線程中,默認(rèn)是沒有Looper的郊尝,因此要想在子線程中使用Handler二跋,就必須要手動為當(dāng)前線程創(chuàng)建Looper。創(chuàng)建的方法是Looper.prepare流昏,這樣當(dāng)前線程就擁有了Lopper和MessageQueue扎即,再創(chuàng)建Handler,就能與這個線程的Looper和MessageQueue關(guān)聯(lián)了况凉。但是要想實(shí)現(xiàn)消息循環(huán)谚鄙,還需要調(diào)用Looper.loop。
當(dāng)你使用Handler的send系列方法或是post系列方法時刁绒,本質(zhì)上都是將Message放入與當(dāng)前Handler所關(guān)聯(lián)的消息隊(duì)列中闷营,通過Looper循環(huán)取出Message再交給Handler處理。同樣的知市,Activity中的runOnUiThread也就沒什么好神奇的了傻盟,只要你搞懂了Handler的原理,一看代碼就懂了:

public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);
    } else {
        action.run();
    }
}

好了嫂丙,到這里就總結(jié)完成了娘赴。如果文中對知識的理解有錯誤,歡迎指正奢入,共同學(xué)習(xí)進(jìn)步筝闹。謝謝!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末腥光,一起剝皮案震驚了整個濱河市关顷,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌武福,老刑警劉巖议双,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異捉片,居然都是意外死亡平痰,警方通過查閱死者的電腦和手機(jī)汞舱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來宗雇,“玉大人昂芜,你說我怎么就攤上這事∨馄眩” “怎么了泌神?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長舞虱。 經(jīng)常有香客問我欢际,道長,這世上最難降的妖魔是什么矾兜? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任损趋,我火速辦了婚禮,結(jié)果婚禮上椅寺,老公的妹妹穿的比我還像新娘浑槽。我一直安慰自己,他們只是感情好配并,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布括荡。 她就那樣靜靜地躺著,像睡著了一般溉旋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上嫉髓,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天观腊,我揣著相機(jī)與錄音,去河邊找鬼算行。 笑死梧油,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的州邢。 我是一名探鬼主播儡陨,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼量淌!你這毒婦竟也來了骗村?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤呀枢,失蹤者是張志新(化名)和其女友劉穎胚股,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體裙秋,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡琅拌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年缨伊,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片进宝。...
    茶點(diǎn)故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡刻坊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出党晋,到底是詐尸還是另有隱情谭胚,我是刑警寧澤,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布隶校,位于F島的核電站漏益,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏深胳。R本人自食惡果不足惜绰疤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望舞终。 院中可真熱鬧轻庆,春花似錦、人聲如沸敛劝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽夸盟。三九已至蛾方,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間上陕,已是汗流浹背桩砰。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留释簿,地道東北人亚隅。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像庶溶,于是被迫代替她去往敵國和親煮纵。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評論 2 348

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