Handler機制分析

最近翻譯了一片關(guān)于Context泄露的文章,其中提到了Handler的一些知識點。想想當初自己研究這塊的時候也是看著源碼一點點摳出來的。只不過那時候沒有做個總結(jié)馍悟,現(xiàn)在正好借著這個機會把這塊知識點總結(jié)下糠悯,也算是做個備注了。

我們知道Android應(yīng)用啟動的時候會執(zhí)行ActivityThread類的main方法东臀,詳情請參見羅升陽的這篇文章。main方法中會為主線程創(chuàng)建一個Looper對象犀农,這個Looper中維護了一個消息隊列MessageQueen惰赋。Looper會不斷的從消息隊列中取消息,然后交給消息的target對象(即Handler)去處理這個消息呵哨。如果隊列中沒有消息赁濒,那么Looper就會進入等待狀態(tài)直到隊列中有新消息。大概的過程就是這樣孟害,下面我們通過源碼來了解下整個過程拒炎。

首先來看看ActivityThread的main方法,這里只保留了跟本文相關(guān)的主要代碼挨务。

public static void main(String[] args) {

    Looper.prepareMainLooper();

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

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

    Looper.loop();
}

main方法中首先調(diào)用了Looper.prepareMainLooper()來為主線程創(chuàng)建一個Looper對象击你,然后在調(diào)用Looper.loop()方法進入一個無限循環(huán)狀態(tài)不斷的從消息隊列中取消息進行處理。那么谎柄,我們再跟蹤到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();
    }
}

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

prepareMainLooper()首先調(diào)用Looper的prepare()方法創(chuàng)建Looper對象,然后再把這個looper對象賦值給sMainLooper朝巫。同時在創(chuàng)建過程中確保了主線程只有一個Looper對象鸿摇。Looper對象在創(chuàng)建的時候會同時創(chuàng)建一個MessageQueen對象,所有的消息都會存儲在這個隊列中劈猿。Looper也是從這個隊列中取消息來進行處理的拙吉。并且,這個Looper對象的作用域為整個線程揪荣。 這樣筷黔,只要是在該線程內(nèi)發(fā)送的消息,都會發(fā)送到這個Looper對象的消息隊列中進行處理仗颈。

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

main方法最后調(diào)用了Looper的loop()方法讓Looper工作起來佛舱,也就是處理消息隊列中的消息。

public static void loop() {
    final Looper me = myLooper();
    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);
    }
}

loop()方法先拿到當前線程的Looper對象,然后再拿到Looper的MessageQueen對象名眉。最后進入無限循環(huán),不斷的從消息隊列中取出消息凰棉。然后再把消息交給這個消息的target對象進行處理损拢。那么現(xiàn)在的問題就是消息是從哪里來的以及這個target對象又是誰。

一般情況下撒犀,我們會通過handler的sendMessage(msg)方法來發(fā)送一條消息福压。那么我們就跟蹤下這個方法,看看這個消息最終被發(fā)送到哪里去了或舞。

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;
    return enqueueMessage(queue, msg, uptimeMillis);
}

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;//注意:這里把this(即發(fā)送消息的那個Handler對象)賦值給了消息的target對象荆姆。
                    //所以,在Looper.loop()方法中的target就是一個Handler對象
    return queue.enqueueMessage(msg, uptimeMillis);
}

可以看到通過層層調(diào)用映凳,最終調(diào)用了sendMessageAtTime()方法胆筒,該方法又調(diào)用了enqueueMessage()方法將方法放入一個消息隊列中。那么這個消息隊列又跟Looper中的消息隊列有啥關(guān)系呢诈豌?我們來看看Handler是怎么創(chuàng)建的仆救。

public Handler() {
    this(null, false);
}

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

已經(jīng)很清晰明了了。當創(chuàng)建Handler對象的時候矫渔,首先會通過Looper.myLooper()拿到當前線程的Looper對象彤蔽,然后再拿到Looper的MessageQueen對象。也就是說我們的消息是被發(fā)送到當前線程的Looper的消息隊列中了庙洼。這樣Looper.loop()拿到的消息就是我們發(fā)送的消息顿痪,然后再把消息交給發(fā)送這個消息的Hanlder來進行處理。即Looper.loop()中的msg.target.dispatchMessage(msg)油够。那么我們再來看看handler.dispatchMessage(msg)是怎么處理這個消息的蚁袭。

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

我們這里暫時不關(guān)心msg的callback以及handler的mCallback。這里最終會調(diào)用handleMessage(msg)方法叠聋,那我們當然要追蹤進去看看了撕阎。

public void handleMessage(Message msg) {
}

沒錯,是個空方法碌补。其實這里就是我們使用Handler的時候?qū)崿F(xiàn)我們處理消息邏輯的地方虏束。一般我們是這么使用Handler的:

private static Handler mHandler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        //處理邏輯
    }
};

至此,整個Handler機制就串通了:從消息的發(fā)送到消息的處理厦章。

如果我們在主線程中使用Handler镇匀,那我們不用去管Looper。因為袜啃,framew已經(jīng)為我們的主線程創(chuàng)建好了Looper汗侵。但是台夺,如果我們要在其他線程中使用Handler這套機制,那我們就需要自己去創(chuàng)建Looper并調(diào)用Looper.prepare()宰睡,否則就會發(fā)生異常拆魏。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市雪猪,隨后出現(xiàn)的幾起案子栏尚,更是在濱河造成了極大的恐慌,老刑警劉巖只恨,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件译仗,死亡現(xiàn)場離奇詭異,居然都是意外死亡官觅,警方通過查閱死者的電腦和手機纵菌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來休涤,“玉大人咱圆,你說我怎么就攤上這事』蓿” “怎么了闷堡?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長疑故。 經(jīng)常有香客問我杠览,道長,這世上最難降的妖魔是什么纵势? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任踱阿,我火速辦了婚禮,結(jié)果婚禮上钦铁,老公的妹妹穿的比我還像新娘软舌。我一直安慰自己,他們只是感情好牛曹,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布佛点。 她就那樣靜靜地躺著,像睡著了一般黎比。 火紅的嫁衣襯著肌膚如雪超营。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天阅虫,我揣著相機與錄音演闭,去河邊找鬼。 笑死颓帝,一個胖子當著我的面吹牛米碰,可吹牛的內(nèi)容都是我干的窝革。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼吕座,長吁一口氣:“原來是場噩夢啊……” “哼虐译!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起吴趴,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤菱蔬,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后史侣,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡魏身,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年惊橱,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片箭昵。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡税朴,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出家制,到底是詐尸還是另有隱情正林,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布颤殴,位于F島的核電站觅廓,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏涵但。R本人自食惡果不足惜杈绸,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望矮瘟。 院中可真熱鬧瞳脓,春花似錦、人聲如沸澈侠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽哨啃。三九已至烧栋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間棘催,已是汗流浹背劲弦。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留醇坝,地道東北人邑跪。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓次坡,卻偏偏與公主長得像,于是被迫代替她去往敵國和親画畅。 傳聞我的和親對象是個殘疾皇子砸琅,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

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