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

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

技術(shù)也是一門學問,只知其一不知其二,你永遠只會停留在基礎(chǔ)。送給自己也是送給大家的一句話:你若不想做,總會找到借口;你若真想做,總會找到方法!

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

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

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

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

為什么需要設(shè)計這樣的一種機制來進行處理,因為我們都知道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回調(diào)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)卻比這復(fù)雜的多。下面我們可以一起進入源碼來看看到底里面是如何實現(xiàn)的。

先來看看handler的構(gòu)造方法,
我們平時只實現(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方法來設(shè)置這個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設(shè)置了一個新的looper,并且這里也進行了一個非空判斷,如果已經(jīng)執(zhí)行過prepare來獲取過一次looper,那么再次調(diào)用的時候就會拋出異常,這樣的做法也是保證了一個線程中只有一個Looper的實例。

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

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

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

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

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

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

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

一開始我就發(fā)現(xiàn)了這個細節(jié),而且我們在new handler的時候大部分的時候都是在主線程中去操作的,那么猜想必然是android系統(tǒng)在應(yī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,并且作為我們應(yīng)用程序的main looper,在我們應(yīng)用程序創(chuàng)建的時候就會創(chuàng)建,所以不需要我們?nèi)ナ謩拥娜フ{(diào)用這個方法。

查看源代碼發(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();      
        ......      
    }

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

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

三.消息循環(huán)

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

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

這里new出了一個MessageQueue對象,也就是我們另一個角色扮演者:消息隊列。所有通過handler發(fā)送的消息都會存儲到這個消息隊列來,因此一個Looper對象對應(yīng)了一個MessageQueue。而在handler的構(gòu)造函數(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);
}

最后調(diào)用的是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,然后又調(diào)用了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ù)時間的順序調(diào)用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),不斷的調(diào)用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方法相對應(yīng),也就是說消息是從這里傳遞過去的。這樣我們從開頭到這兒剛好形成一個邏輯的閉環(huán)。一個消息鏈的發(fā)送和接收處理的完整過程我們再進行總結(jié)下:

1.無論是在android 應(yīng)用程序主線程還是其他線程首先會執(zhí)行Looper.prepare(),創(chuàng)建該線程的Looper對象,記?。阂粋€線程對應(yīng)一個Looper對象;然后在創(chuàng)建Looper的時候創(chuàng)建了一個MessagQueue消息隊列管理消息的入棧和出棧,也是一個線程對應(yīng)一個MessagQueue;

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

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

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

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

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

謝謝。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容