Android Handler原理分析

前言

(該文半年前寫于CSDN算谈,回頭看看篮迎,覺得寫的不太好惫撰,稍微修改一下)

平時開發(fā)app時,Handler簡直已經(jīng)被用爛了顶籽,它的主要工作就是負(fù)責(zé)子線程和主線程之間的通信葵腹。我相信你已經(jīng)對Handler的使用熟能生巧了高每,但是你真的了解它嗎?

深入源碼

Handler的使用

先來回想一下我們平時都是怎么使用Handler的礁蔗。

step1:初始化一個Handler對象觉义,重寫其handleMessage()方法:

Handler mHandler=new Handler() {
    @Override
    public void handleMessage(Message msg){
        //todo
    }
};

step2:發(fā)送消息通知Handler處理:

Message msg=mHandler.obtainMessage();
msg.what=0;
mHandler.sendMessage(msg);

我們經(jīng)常會在主線程中進(jìn)行step1操作,而在子線程中通過setp2來與主線程通信浴井,如執(zhí)行UI操作晒骇。那么Handler究竟是如何做到線程之間通信的呢?故事要從一個叫Looper的家伙說起磺浙。

Looper是什么

在Android中洪囤,對于每一個線程,都可以創(chuàng)建一個Looper對象(最多一個!)和多個Handler撕氧。Looper就像一個消息泵瘤缩,源源不斷的從消息池中拿到消息,交給Handler處理伦泥。

我們先來簡單的看一下Looper類剥啤,Looper類中有有四個我們必須要了解的變量:

private static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper;  // guarded by Looper.class

final MessageQueue mQueue;
final Thread mThread;

上面定義了兩個靜態(tài)變量:一個ThreadLocal類型的變量sThreadLocal,一個Looper類型的變量sMainLooper不脯。還有兩個final變量:一個隊(duì)列類型的mQueue府怯,一個線程類型的mThread。這四個變量至關(guān)重要防楷。

一個線程想要使用Handler牺丙,就必須得創(chuàng)建一個Looper對象。那么創(chuàng)建一個Looper對象需要做什么呢复局?很簡單冲簿,一行代碼足以粟判。

Looper.prepare();

在Looper.prepare()中,Looper類會創(chuàng)建一個新的Looper對象,并放入全局的sThreadLocal中峦剔。

sThreadLocal.set(new Looper(quitAllowed));  

我們再繼續(xù)深入看看new Looper(quitAllowed)档礁,很簡單,也就兩行代碼:

mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();

原來只是初始化mQueue和mThread這兩個變量羊异。

但是僅僅創(chuàng)建了Looper還不行事秀,還必須開啟消息循環(huán),要不然要Looper有何用野舶。開啟消息循環(huán)同樣很簡單:

Looper.Loop();

現(xià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 msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        ......
        msg.target.dispatchMessage(msg);
        ......
        msg.recycleUnchecked();
    }
}

這段代碼看起來一堆,其實(shí)主要工作也就在于那個for循環(huán)平道。不過還是從第一行代碼先看起吧睹欲。

第一行調(diào)用了函數(shù)myLooper(),這個函數(shù)的作用在于從sThreadLocal中取出當(dāng)前線程的Looper對象,因?yàn)閟ThreadLocal為ThreadLocal類型一屋,所以它會保證在多線程情景下窘疮,每個線程的數(shù)據(jù)互不干擾,只能取出自己的Looper對象冀墨。

接下來取出Looper對象中的mQueue變量闸衫。

final MessageQueue queue = me.mQueue;

再來看看for循環(huán),大家可以發(fā)現(xiàn)這是一個無限循環(huán)诽嘉,沒有終止條件蔚出。正是這個for循環(huán),開啟了我們的消息機(jī)制的循環(huán)虫腋,源源不斷的將消息給發(fā)送出去:

Message msg = queue.next();
msg.target.dispatchMessage(msg);

Handler與Looper的綁定

說了這么多骄酗,大家對Looper應(yīng)該稍微有點(diǎn)了解了,但是上述的代碼里似乎沒有涉及到我們使用的Handler啊悦冀,那Looper是如何與Handler進(jìn)行綁定的呢趋翻?又是怎么拿到我們的消息并進(jìn)行分發(fā)的呢?這就要看看Handler的源碼了盒蟆。

先要看一看Handler的構(gòu)造函數(shù):

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

這里主要有兩步操作:首先是獲取當(dāng)前線程的Looper對象踏烙,賦值給本地變量,接著將Looper中的消息隊(duì)列mQueue賦值給Handler的mQueue历等。通過這兩步宙帝,Handler就與當(dāng)前線程的Looper對象綁定了。

再回到Handler的日常使用:

handler.sendEmptyMessage(int)

我們直接深入到這個方法的最底層:sendMessageAtTime(Message msg,long uptimeMills);

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

先是將消息隊(duì)列mQueue賦值給局部變量queue(注意注意募闲!這個mQueue指向的可是Looper的mQueue,忘記了請看前述Hander的構(gòu)造函數(shù))愿待。再直接看最后一句浩螺, enqueueMessage(queue, msg, uptimeMillis);這里應(yīng)該是將我們發(fā)送的消息入隊(duì)了靴患。再向下挖:

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

果然,這里是將msg放入了Looper的mQueue中了要出。好了鸳君,消息放到隊(duì)列中去了,有進(jìn)就有出啊患蹂,你個送信的總得把信送出去吧或颊。還記得Loop.loop()方法嗎,我們前面說該方法開起來消息機(jī)制的循環(huán)传于。loop()的for循環(huán)中最終調(diào)用了 msg.target.dispatchMessage(msg); 而msg.target, 指向的就是消息的收信人囱挑,也就是Handler,那么我們又回到了Handler的源碼:

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

哈哈,看見了啥沼溜?handleMessage(msg) 啊平挑。我們平時new Hander時咋寫的記得吧。

Handler mHandler=new Handler() {
    @Override
    public void handleMessage(Message msg){
        //todo
    }
};

我們重寫了handleMessage方法系草,處理我們接收到的消息通熄,OK,消息終于送達(dá)到目的地了找都!

主線程的Looper

最后在說一點(diǎn)唇辨,我們平時開發(fā)過程中,并沒有在Activity中去初始化Looper能耻,那為什么可以使用Handler呢赏枚?其實(shí)Activity所在的主線程照樣也創(chuàng)建了Looper對象,替你干了活而已嚎京,雖然它是主線程嗡贺,也得照樣按照規(guī)則辦事啊。

Activity所在的主線程是ActivityThread鞍帝,其實(shí)這樣說并不準(zhǔn)確诫睬,因?yàn)锳ctivityThread并不是一個線程,它只是主線程的一個入口:ActivityThread中的void main(String[] args)帕涌。

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

我擦摄凡,你看它已經(jīng)默默做好了一切!
細(xì)心的你應(yīng)該發(fā)現(xiàn)了蚓曼,這里調(diào)用的是Looper.prepareMainLooper()亲澡,而不是之前所說的Looper.prepare()。是啊纫版,它可是主線程床绪,總得有點(diǎn)不一樣的地方吧,豈能完全平起平坐。

我們來挖一挖這個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();
    }
}

它首先也調(diào)用了prepare(false)方法膀斋,只不過又多做了一件事:sMainLooper = myLooper();

這時你要問了,這個變量有啥用氨匝拧仰担?大家還記得這個變量是靜態(tài)的吧,這樣在子線程中绩社,你就可以通過getMainLooper()來獲得主線程的Looper對象了摔蓝。而對于其他的子線程,因?yàn)樗鼈兊腖ooper對象只存儲在sThreadLocal中愉耙,所以只能夠取出自己的Looper對象了贮尉。

舉個例子,子線程想要在主線程中執(zhí)行一段代碼劲阎,就可以按照如下操作:

new Handler(getMainLooper()).post(new Runnable() {
        @Override
        public void run() {
            //todo
        }
    })

總結(jié)

以上就是Handler與Looper的哀怨情仇绘盟。總得來說悯仙,Looper就像是一個郵局龄毡,Handler通過sendMessage()將信件交給Looper,放到郵局的倉庫mQueue里锡垄,郵局Looper再不斷的從倉庫中取出信沦零,交還給信對應(yīng)的Handler,收信人調(diào)用handleMessage()來讀信货岭。

(轉(zhuǎn)載請注明ID:半棧工程師路操,歡迎訪問個人博客https://halfstackdeveloper.github.io/)

歡迎關(guān)注我的知乎專欄:https://zhuanlan.zhihu.com/halfstack

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市千贯,隨后出現(xiàn)的幾起案子屯仗,更是在濱河造成了極大的恐慌,老刑警劉巖搔谴,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件魁袜,死亡現(xiàn)場離奇詭異,居然都是意外死亡敦第,警方通過查閱死者的電腦和手機(jī)峰弹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來芜果,“玉大人鞠呈,你說我怎么就攤上這事∮壹兀” “怎么了蚁吝?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵旱爆,是天一觀的道長。 經(jīng)常有香客問我灭将,道長疼鸟,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任庙曙,我火速辦了婚禮,結(jié)果婚禮上浩淘,老公的妹妹穿的比我還像新娘捌朴。我一直安慰自己,他們只是感情好张抄,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布砂蔽。 她就那樣靜靜地躺著,像睡著了一般署惯。 火紅的嫁衣襯著肌膚如雪左驾。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天极谊,我揣著相機(jī)與錄音诡右,去河邊找鬼。 笑死轻猖,一個胖子當(dāng)著我的面吹牛帆吻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播咙边,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼猜煮,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了败许?” 一聲冷哼從身側(cè)響起王带,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎市殷,沒想到半個月后愕撰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡被丧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年盟戏,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片甥桂。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡柿究,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出黄选,到底是詐尸還是另有隱情蝇摸,我是刑警寧澤婶肩,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站貌夕,受9級特大地震影響律歼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜啡专,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一险毁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧们童,春花似錦畔况、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至齐板,卻和暖如春吵瞻,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背甘磨。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工橡羞, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人宽档。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓尉姨,卻偏偏與公主長得像,于是被迫代替她去往敵國和親吗冤。 傳聞我的和親對象是個殘疾皇子又厉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345

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