Android-異步消息處理機制(Handler,Looper,Message)

相信大部分開發(fā)android的人使用Handler在子線程上去進行ui的操作這種模式已經(jīng)滾瓜爛熟了篮洁,但是當你不去深入研究它的原理帽芽,和理清它與Looper和Message之間的關系時删掀,遇到問題和bug的時候你就會無從下手,手忙腳亂导街。

技術也是一門學問披泪,只知其一不知其二,你永遠只會停留在基礎搬瑰。送給自己也是送給大家的一句話:你若不想做款票,總會找到借口;你若真想做泽论,總會找到方法艾少!

開始進入正題,什么是異步消息處理機制翼悴?
就應用程序而已缚够,android系統(tǒng)中java的應用程序和其他系統(tǒng)上相同,都是靠消息驅動來工作的,它們的大致工作原理是:

*有一個消息隊列潮瓶,可以往這個消息隊列中不斷投遞消息(進)

*有一個消息循環(huán)陶冷,可以不斷的從這個消息隊列中取出消息钙姊,進行處理毯辅。(出)

事件源把待處理的消息加入到消息隊列中(默認是加至隊尾,但是也會遇到插隊的情況出現(xiàn)煞额,而且不是插中間思恐,直接插入至隊列頭,這種是屬于大哥級別的消息)膊毁,處理線程從消息隊列頭不斷取出消息分發(fā)給對應的target進行處理胀莹。這種實現(xiàn)模式在android上主要就是靠我們的Handler,Looper來實現(xiàn)婚温。

為什么需要設計這樣的一種機制來進行處理描焰,因為我們都知道Android UI線程是不安全的,如果嘗試在非ui線程上去進行ui的更新栅螟,這個時候程序是有可能會崩潰的荆秦。那么android推薦的處理方式是:在你的當前activity也就是ui線程上創(chuàng)建一個Handler,并實現(xiàn)它的callback:handleMessage,
當你需要在子線程上去進行ui更新的時候力图,創(chuàng)建一個Message步绸,通過handler發(fā)送出去,在handleMessage中接收到這個message對象進行ui更新吃媒。

一.Handler

我們平時在開發(fā)的過程中都會選擇直接去new一個Handler,如下代碼:

private Handler mHandler=new Handler(new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {
        return false;
    }
});

然后在線程中通過Message來組裝需要傳遞到ui線程的數(shù)據(jù)瓤介,讓handler來進行發(fā)送,代碼如下:

new Thread(new Runnable() {
    @Override
    public void run() {
        //發(fā)送消息
        Message msg = Message.obtain();
        msg.what = 111;
        Bundle bundle=new Bundle();
        bundle.putString("huan","hello");
        msg.setData(bundle);
        mHandler.sendMessage(msg);
    }
}).start();

這樣就可以在主線程的handler回調callback中接收到這個消息并進行處理:

@Override
public boolean handleMessage(Message msg) {
    if(msg.what==111){
        //取出數(shù)據(jù)
        String str = msg.getData().getString("huan");
        //...進行ui更新
    }
    return false;
}

這樣一套基本的異步更新ui的流程就走完了赘那。但是當寫完這些代碼的時候有沒有想過為什么這樣子做可以實現(xiàn)刑桑?表面上看我們緊緊就是new 了一個handler和一個messsage作為載體就達到了這樣的效果,但是實際底層的實現(xiàn)卻比這復雜的多募舟。下面我們可以一起進入源碼來看看到底里面是如何實現(xiàn)的祠斧。

先來看看handler的構造方法,
我們平時只實現(xiàn)了callback這一個參數(shù),點進去一看發(fā)現(xiàn)其實是:

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

下面的 才是最終的一個實現(xiàn):

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

可以看到其實當我們在new一個handler的時候胃珍,它里面進行了一個很重要的操作:

mLooper = Looper.myLooper();

我們最重要的Looper入場了,Looper.myLooper()方法獲取了一個Looper對象梁肿,如果Looper對象為空,則會拋出一個運行時異常觅彰,所以想要一探究盡吩蔑,我們得去看看這個Looper這個類,到底扮演著什么角色填抬。

二.Looper

備注:至于Looper這個類到底有什么用烛芬,這里先不做解釋,我們先跟著源碼一步一步走下就會揭開它的面紗

當進入到Looper這個類的時候,查看myLooper()這個方法時發(fā)現(xiàn)就緊緊一行代碼:

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

通過sThreadLocal這個對象來獲取到looper赘娄,那既然是通過get方法來獲取到這個looper仆潮,那必然有set方法來設置這個looper,但是我們自己只是new了一個handler遣臼,并沒有對looper做任何的處理和操作性置,那必然是android系統(tǒng)自己在某個地方給我們做了某些操作,接下來繼續(xù)深入查看Looper這個類揍堰,看看sThreadLocal這個對象到底是在哪兒進行申明的:

public final class Looper {
    private static final String TAG = "Looper";

    // sThreadLocal.get() will return null unless you've called prepare().
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

一查找發(fā)現(xiàn)鹏浅,居然在類的開頭進行了這個對象的初始化,并且給出了明確的注釋:如果沒有執(zhí)行Looper的prepare()方法那么通過sThreadLocal.get()返回的就是null屏歹。所以隐砸,現(xiàn)在目的就明確了,跟著指示走下去看看prepare()方法里面到底執(zhí)行了什么操作:

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

很明顯的看到蝙眶,原來是在prepare()方法中對sThreadLocal設置了一個新的looper,并且這里也進行了一個非空判斷季希,如果已經(jīng)執(zhí)行過prepare來獲取過一次looper,那么再次調用的時候就會拋出異常幽纷,這樣的做法也是保證了一個線程中只有一個Looper的實例式塌。

備注下:sThreadLocal是一個ThreadLocal對象,可以在一個線程中存儲變量T

看到這里可能大家可能都會覺得很明了霹崎,清晰了珊搀,我們整理下整個思路流程就是:

  1. new一個handle來作為發(fā)送消息的載體

  2. 在handler的構造函數(shù)中通過Looper.myLooper()來獲取到了這個Looper

  3. 在myLooper()這個方法其實也是通過sThreadLocal這個對象來進行獲取的Looper

  4. 想要通過sThreadLocal這個對象來獲取到looper,必須先執(zhí)行Looper的prepare()方法

那么問題來了:
之前我們不是在new一個handler對象的時候尾菇,發(fā)現(xiàn)其實構造函數(shù)里面就是通過Looper.myLooper()方法來獲取了一個looper境析,但是如果Looper對象為空,則會拋出一個運行時異常派诬;并且我們至始至終都沒用使用過這個looper對象劳淆,更不用說執(zhí)行它的prepare方法了,那么為什么我們這樣任性的使用handler默赂,程序確沒有奔潰沛鸵?

一開始我就發(fā)現(xiàn)了這個細節(jié),而且我們在new handler的時候大部分的時候都是在主線程中去操作的缆八,那么猜想必然是android系統(tǒng)在應用程序啟動的時候曲掰,開啟主線程的時候就為這個線程創(chuàng)建了這了Looper對象,提前為我們執(zhí)行了Looper.prepare()方法奈辰。
這個時候你也不用特意去研究android系統(tǒng)啟動的過程就能很輕松的找到在Looper類的prepare()方法下面就是一個prepareMainLooper()函數(shù)

/**
 * Initialize the current thread as a looper, marking it as an
 * application's main looper. The main looper for your application
 * is created by the Android environment, so you should never need
 * to call this function yourself.  See also: {@link #prepare()}
 */
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栏妖,并且作為我們應用程序的main looper,在我們應用程序創(chuàng)建的時候就會創(chuàng)建奖恰,所以不需要我們去手動的去調用這個方法吊趾。

查看源代碼發(fā)現(xiàn)果然是這樣的:在ActivityThread的main方法中

 public static final void main(String[] args) {      
        ......    
      
        Looper.prepareMainLooper();      
             
        ......      
      
        ActivityThread thread = new ActivityThread();      
        thread.attach(false);      
      
        ......     
        Looper.loop();      
      
        ......     
      
        thread.detach();      
        ......      
    }

這里小結下:我們應用程序在啟動的時候會給我們創(chuàng)建一個main looper宛裕,并始終存在我們應用程序,所以不需要我們手動去調用Looper.prepare()方法论泛,所以在主線程中的任意地方你都可以放肆的創(chuàng)建Handler揩尸,但是注意:如果是在子線程中創(chuàng)建Handler,
務必先調用Looper.prepare()才能創(chuàng)建Handler對象屁奏。

備注:在ActivityThread中不僅實現(xiàn)了Looper.prepareMainLooper()方法我們還看到有個Looper.loop()方法岩榆,這個方法有什么用,先不著急了解了袁,我們留個伏筆朗恳。

三.消息循環(huán)

上面講了hanlder創(chuàng)建的整個過程,以及如何得到looper的過程载绿,但是始終還是不知道這個looper到底有什么用,是如何操作的油航?接下來回到我們之前的步驟中接著走下去崭庸,在sThreadLocal中設置了一個new Looper(),可以進入Looper的構造函數(shù)看看到底實現(xiàn)了什么:

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

這里new出了一個MessageQueue對象,也就是我們另一個角色扮演者:消息隊列谊囚。所有通過handler發(fā)送的消息都會存儲到這個消息隊列來怕享,因此一個Looper對象對應了一個MessageQueue。而在handler的構造函數(shù)中我們看到了:mQueue = mLooper.mQueue 也就是說Handler中的消息隊列變量最終都會指向Looper的消息隊列镰踏。

那就不難理解為什么我們通過

mHandler.sendMessage(msg);

這個代碼就能將消息發(fā)送到Looper的消息隊列中來函筋,handler也為我們提供了一系列函數(shù)來幫助完成創(chuàng)建消息和插入消息隊列的工作:

//從handler中創(chuàng)建一個消息碼是what的消息
public final Message obtainMessage(int what)

//發(fā)送一個只含有消息碼的消息
public final boolean sendEmptyMessage(int what)

//延時發(fā)送一個只含有消息碼的消息
 public final boolean sendEmptyMessageDelayed(int what, long delayMillis)

//發(fā)送一個消息,默認添加至隊列尾
public final boolean sendMessage(Message msg)

//發(fā)送一個消息奠伪,添加至隊列頭跌帐,優(yōu)先級高
public final boolean sendMessageAtFrontOfQueue(Message msg)

接下來再進入sendMessage中看看如何操作的就明了了:

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

最后調用的是sendMessageAtTime這個方法,也就是拿到之前的MessageQueue然后進行了enqueueMessage操作:

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

這里將msg的target指向了自己绊率,也就是handler谨敛,然后又調用了queue.enqueueMessage(msg, uptimeMillis)
進去看看:

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("MessageQueue", 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;
}

大致分析了下,這里主要就是將消息進行了一個時間排序滤否,根據(jù)我們傳入的uptimeMillis.根據(jù)時間的順序調用msg.next.
消息進入隊列已經(jīng)完成脸狸,那么在什么時候進入處理消息,循環(huán)消息藐俺,就是我們之前埋下的伏筆:在ActivityThread中不僅實現(xiàn)了Looper.prepareMainLooper()方法我們還看到有個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;

    // Make sure the identity of this thread is that of the local process,
    // and keep track of what that identity token actually is.
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();

    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }

        // This must be in a local variable, in case a UI event sets the logger
        Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }

        msg.target.dispatchMessage(msg);

        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }

        // Make sure that during the course of dispatching the
        // identity of the thread wasn't corrupted.
        final long newIdent = Binder.clearCallingIdentity();
        if (ident != newIdent) {
            Log.wtf(TAG, "Thread identity changed from 0x"
                    + Long.toHexString(ident) + " to 0x"
                    + Long.toHexString(newIdent) + " while dispatching to "
                    + msg.target.getClass().getName() + " "
                    + msg.callback + " what=" + msg.what);
        }

        msg.recycleUnchecked();
    }
}

我們有時看源碼的時候炊甲,沒必要每一行都去理解它,抓重點就行:這個函數(shù)主要創(chuàng)建了一個死循環(huán)欲芹,不斷的調用Message msg = queue.next();來從消息隊列里面取出消息卿啡,并進行處理:msg.target.dispatchMessage(msg) ,而msg.target剛好是我們之前看handler的時候把自己賦值給了這個target耀石;接下來繼續(xù)看看這個target做了什么事:msg.target.dispatchMessage(msg);

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

在dispatchMessage方法中可以看到如果消息本身有callback牵囤,則直接交給msg的callback處理爸黄,否則mCallback不為null就會執(zhí)行mCallback.handleMessage(msg);看到這兒相信大家已經(jīng)豁然開朗了,這從我們剛開始講的實現(xiàn)callback的handleMessage方法相對應揭鳞,也就是說消息是從這里傳遞過去的炕贵。這樣我們從開頭到這兒剛好形成一個邏輯的閉環(huán)。一個消息鏈的發(fā)送和接收處理的完整過程我們再進行總結下:

1.無論是在android 應用程序主線程還是其他線程首先會執(zhí)行Looper.prepare()野崇,創(chuàng)建該線程的Looper對象称开,記住:一個線程對應一個Looper對象乓梨;然后在創(chuàng)建Looper的時候創(chuàng)建了一個MessagQueue消息隊列管理消息的入棧和出棧鳖轰,也是一個線程對應一個MessagQueue;

2.執(zhí)行Looper.loop方法讓該線程創(chuàng)建一個死循環(huán)扶镀,不斷的調用Message msg = queue.next();來從消息隊列里面取出消息蕴侣,并進行處理:msg.target.dispatchMessage(msg)

3.創(chuàng)建一個Handler來進行消息的發(fā)送和接收處理,并在初始化的時候與該線程的Looper中的MessagQueue相關聯(lián)臭觉;

4.通過handler發(fā)送message的時候昆雀,會將msg的target設置為handler自己,并且將該msg按照時間進行排序至消息隊列

那么Handler,Looper,Message之間的關系就是:
handler負責不斷發(fā)送message到MessageQueue蝠筑;Looper將消息隊列中的消息一個一個取出回調給dispatchMessage方法狞膘;最后消息回到handler所在的線程,通過handler的callback方法進行處理什乙。

好了挽封,到這里差不多把整個handler異步消息機制梳理完畢;后面還會出一篇文章講解下Looper和handler的同步關系臣镣。以及在多線程中如何處理這個消息的傳遞辅愿;handlerThread的用法;

謝謝退疫。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末渠缕,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子褒繁,更是在濱河造成了極大的恐慌亦鳞,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件棒坏,死亡現(xiàn)場離奇詭異燕差,居然都是意外死亡,警方通過查閱死者的電腦和手機坝冕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門徒探,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人喂窟,你說我怎么就攤上這事测暗⊙氪” “怎么了?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵碗啄,是天一觀的道長质和。 經(jīng)常有香客問我,道長稚字,這世上最難降的妖魔是什么饲宿? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮胆描,結果婚禮上瘫想,老公的妹妹穿的比我還像新娘。我一直安慰自己昌讲,他們只是感情好国夜,可當我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著剧蚣,像睡著了一般支竹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鸠按,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天,我揣著相機與錄音饶碘,去河邊找鬼目尖。 笑死,一個胖子當著我的面吹牛扎运,可吹牛的內容都是我干的瑟曲。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼豪治,長吁一口氣:“原來是場噩夢啊……” “哼洞拨!你這毒婦竟也來了?” 一聲冷哼從身側響起负拟,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤烦衣,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后掩浙,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體花吟,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年厨姚,在試婚紗的時候發(fā)現(xiàn)自己被綠了衅澈。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡谬墙,死狀恐怖今布,靈堂內的尸體忽然破棺而出经备,到底是詐尸還是另有隱情,我是刑警寧澤部默,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布侵蒙,位于F島的核電站,受9級特大地震影響甩牺,放射性物質發(fā)生泄漏蘑志。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一贬派、第九天 我趴在偏房一處隱蔽的房頂上張望急但。 院中可真熱鬧,春花似錦搞乏、人聲如沸波桩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽镐躲。三九已至,卻和暖如春侍筛,著一層夾襖步出監(jiān)牢的瞬間萤皂,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工匣椰, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留裆熙,地道東北人。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓禽笑,卻偏偏與公主長得像入录,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子佳镜,可洞房花燭夜當晚...
    茶點故事閱讀 43,472評論 2 348

推薦閱讀更多精彩內容