Android異步消息處理機(jī)制深度解析

對于Android的異步消息處理機(jī)制,大家一定都不陌生,異步消息處理機(jī)制一個(gè)常見的應(yīng)用場景就是在子線程中更新UI,我們都知道,Android的UI是線程不安全的,如果在子線程中直接更新UI,便會(huì)導(dǎo)致程序崩潰。對于該問題,常見的解決方法是,在UI線程新建一個(gè)Handler并覆寫其handleMessage方法,在子線程中獲取Message對象,通過Message對象的arg,obj字段以及setData()方法攜帶一些數(shù)據(jù),之后利用UI線程的Handler將消息發(fā)送出去,最后便可以在handleMessage方法中獲取到剛剛發(fā)送的消息并進(jìn)行相應(yīng)的處理了,示例代碼如下:

public class MainActivity extends ActionBarActivity {

    private Handler handler1=new Handler(){
        @Override
        public void handleMessage(Message msg){
            //to do sth
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new Thread(new Runnable(){
            @Override
            public void run() {
                Message msg=Message.obtain();
                msg.what=1;
                Bundle bundle = new Bundle();  
                bundle.putString("info", "info");  
                msg.setData(bundle);  
                handler1.sendMessage(msg);
            }
        }).start();
    }

}

這種方法相信大家都已經(jīng)用得很熟了,注意,此時(shí)我們是在UI線程創(chuàng)建Handler的,那么現(xiàn)在我們嘗試在子線程創(chuàng)建Handler,看看與之前的UI線程創(chuàng)建Handler有何區(qū)別,示例代碼如下:

public class MainActivity extends ActionBarActivity {

    private Handler handler2=null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new Thread(new Runnable(){
            @Override
            public void run() {
                handler2=new Handler();
            }
        }).start(); 
    }

}

結(jié)果,系統(tǒng)竟然報(bào)錯(cuò)了,錯(cuò)誤提示信息為Can't create handler inside thread that has not called Looper.prepare() 。意思是,不能在一個(gè)沒有調(diào)用Looper.prepare()的線程內(nèi)創(chuàng)建Handler。那么我們依據(jù)系統(tǒng)的意思,在創(chuàng)建Handler之前調(diào)用Looper.prepare()試試看:

new Thread(new Runnable(){
    @Override
    public void run() {
        Looper.prepare();
        handler2=new Handler();
    }
}).start(); 

果不其然,這回終于不報(bào)錯(cuò)了。但是,僅僅解決問題是不夠的,我們更應(yīng)該去探究問題背后所隱藏的原理,只有這樣我們的能力才會(huì)有一個(gè)質(zhì)的提升,所以下面,我將帶領(lǐng)大家從源代碼級別深入地探究Android的異步消息處理機(jī)制

OK,話不多說,讓我們趕快進(jìn)入這美妙的探索之旅吧~~~

前面已經(jīng)講到,在子線程中創(chuàng)建Handler時(shí),如果事先不調(diào)用一下Looper.prepare(),系統(tǒng)便會(huì)報(bào)錯(cuò)。那么我們?yōu)槭裁匆欢ㄒ热フ{(diào)一下Looper.prepare()呢?看來,只有Handler的構(gòu)造函數(shù)以及Looper.prepare()的源代碼能夠告訴我們答案了,我們先從Handler的構(gòu)造函數(shù)看起,Handler的構(gòu)造函數(shù)源代碼如下:

public Handler() {  
    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 = null;  
} 

我們從mLooper = Looper.myLooper()這行代碼看起,如果拿到的mLooper對象為空的話,便會(huì)拋出一個(gè)運(yùn)行時(shí)異常,提示信息正是剛剛的“Can't create handler inside thread that has not called Looper.prepare()”,那么mLooper對象何時(shí)為空呢,這就要去看Looper.myLooper()中的代碼了,Looper.myLooper()的代碼如下:

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

顯然,這個(gè)方法是從sThreadLocal對象中拿出當(dāng)前的Looper對象,如果拿不到的話則返回null。現(xiàn)在我們可以大膽猜測下在什么地方給sThreadLocal對象設(shè)置Looper的了,沒錯(cuò),就是在Looper.prepare()方法中,我們趕緊去看一下Looper.prepare()方法的源碼:

public static final void prepare() {  
    if (sThreadLocal.get() != null) {  
        throw new RuntimeException("Only one Looper may be created per thread");  
    }  
    sThreadLocal.set(new Looper());  
}  

在Looper.prepare()中,會(huì)先去嘗試獲取sThreadLocal中的Looper對象,如果當(dāng)前能夠獲取到Looper對象,則拋出運(yùn)行時(shí)異?!癘nly one Looper may be created per thread”,這也說明了每個(gè)線程最多只能有一個(gè)Looper對象。如果獲取不到Looper對象,則給sThreadLocal設(shè)置Looper對象。
說到這里,很多朋友可能都會(huì)有疑惑:為什么主線程中沒有調(diào)用Looper.prepare(),卻依然能夠正常地創(chuàng)建Handler呢?
這是因?yàn)槌绦蛟趩?dòng)時(shí),系統(tǒng)已經(jīng)自動(dòng)幫我們調(diào)用了Looper.prepare()了,具體可以參照ActivityThread中的main()方法,這里就不去詳述了,感興趣的朋友可以去自行查閱。
我們繼續(xù)去分析Handler中的源代碼:

  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 = null; 

可以看到,在利用Looper.myLooper()獲取到Looper對象并賦值給Handler的成員變量mLooper之后,又將 mLooper的mQueue賦值給Handler的成員變量mQueue,由此可見,Handler中擁有著Looper和MessageQueue兩個(gè)成員變量,Handler的構(gòu)造函數(shù)的主要目的就是初始化這兩個(gè)成員變量,同時(shí),一個(gè)Looper對應(yīng)著一個(gè)MessageQueue。
分析完Handler的構(gòu)造函數(shù)以及Looper.prepare()的源代碼,我們再來研究一下消息的發(fā)送流程,先溫習(xí)一下消息發(fā)送的代碼:

Message msg=Message.obtain();
msg.what=1;
Bundle bundle = new Bundle();  
bundle.putString("info", "info");  
msg.setData(bundle);  
handler1.sendMessage(msg);

看到這里,我們不禁要問:Handler到底將消息發(fā)到哪里?為什么之后在handleMessage中又可以收到之前發(fā)送的消息?
我們知道,Handler有很多發(fā)送Message的方法,其中,除了sendMessageAtFrontOfQueue方法,其他方法都會(huì)輾轉(zhuǎn)調(diào)用到sendMessageAtTime方法,sendMessageAtTime方法的源代碼如下:

public boolean sendMessageAtTime(Message msg, long uptimeMillis)  
{  
    boolean sent = false;  
    MessageQueue queue = mQueue;  
    if (queue != null) {  
        msg.target = this;  
        sent = queue.enqueueMessage(msg, uptimeMillis);  
    }  
    else {  
        RuntimeException e = new RuntimeException(  
            this + " sendMessageAtTime() called with no mQueue");  
        Log.w("Looper", e.getMessage(), e);  
    }  
    return sent;  
}  

可以看到,sendMessageAtTime方法有兩個(gè)參數(shù),第一個(gè)參數(shù)為msg,代表著我們發(fā)送的消息,第二個(gè)參數(shù)為uptimeMillis,代表著發(fā)送消息的時(shí)間,其值為自系統(tǒng)開機(jī)到當(dāng)前時(shí)間的毫秒數(shù)加上延遲時(shí)間,如果不是調(diào)用的sendMessageDelayed方法,則延遲時(shí)間為0.之后,將當(dāng)前Handler的MessageQueue對象(即mQueue)取出,判斷MessageQueue對象是否為空,若不為空,則將當(dāng)前消息的target屬性指向當(dāng)前發(fā)送消息的Handler對象(即this),最后,調(diào)用我們MessageQueue對象的enqueueMessage方法讓消息進(jìn)入消息隊(duì)列中。
這里要稍微解釋下MessageQueue,顧名思義,MessageQueue就是一個(gè)消息隊(duì)列,其內(nèi)部會(huì)以一個(gè)隊(duì)列的形式存儲(chǔ)我們發(fā)送的消息,并提供了消息的入隊(duì)和出隊(duì)方法。
接下來就要分析較為關(guān)鍵的enqueueMessage方法了,enqueueMessage方法會(huì)將消息放入消息隊(duì)列中,并按照消息發(fā)送的時(shí)間對消息進(jìn)行排序,enqueueMessage方法的源代碼如下:

final boolean enqueueMessage(Message msg, long when) {  
    if (msg.when != 0) {  
        throw new AndroidRuntimeException(msg + " This message is already in use.");  
    }  
    if (msg.target == null && !mQuitAllowed) {  
        throw new RuntimeException("Main thread not allowed to quit");  
    }  
    synchronized (this) {  
        if (mQuiting) {  
            RuntimeException e = new RuntimeException(msg.target + " sending message to a Handler on a dead thread");  
            Log.w("MessageQueue", e.getMessage(), e);  
            return false;  
        } else if (msg.target == null) {  
            mQuiting = true;  
        }  
        msg.when = when;  
        Message p = mMessages;  
        if (p == null || when == 0 || when < p.when) {  
            msg.next = p;  
            mMessages = msg;  
            this.notify();  
        } else {  
            Message prev = null;  
            while (p != null && p.when <= when) {  
                prev = p;  
                p = p.next;  
            }  
            msg.next = prev.next;  
            prev.next = msg;  
            this.notify();  
        }  
    }  
    return true;  
}  

enqueueMessage方法有兩個(gè)參數(shù),第一個(gè)是入隊(duì)的消息,第二個(gè)是該消息發(fā)送的時(shí)間。前面提到的sendMessageAtFrontOfQueue方法也會(huì)調(diào)用到enqueueMessage方法,但傳入的消息發(fā)送時(shí)間為0。在enqueueMessage方法的內(nèi)部,會(huì)將當(dāng)前入隊(duì)消息的when字段設(shè)置為傳入的消息發(fā)送時(shí)間,取出當(dāng)前的隊(duì)頭消息并賦給變量p,之后判斷如果當(dāng)前隊(duì)頭消息為空或消息發(fā)送時(shí)間為0或消息發(fā)送時(shí)間小于隊(duì)頭消息的時(shí)間,則將當(dāng)前入隊(duì)消息的next指針指向隊(duì)頭消息,之后將隊(duì)頭消息重新賦值為入隊(duì)消息,從而完成了將入隊(duì)消息插入到消息隊(duì)列頭部的操作。如果不滿足上述的三種情況,則按照消息發(fā)送時(shí)間的先后順序?qū)ふ胰腙?duì)消息在隊(duì)列中的插入位置,之后將入隊(duì)消息插入即可。
上面是對消息的入隊(duì)操作的分析,那么出隊(duì)操作在哪里呢?我們就要去分析一下Looper.loop()的源碼了:

public static final void loop() {  
    Looper me = myLooper();  
    MessageQueue queue = me.mQueue;  
    while (true) {  
        Message msg = queue.next(); // might block  
        if (msg != null) {  
            if (msg.target == null) {  
                return;  
            }  
            if (me.mLogging!= null) me.mLogging.println(  
                    ">>>>> Dispatching to " + msg.target + " "  
                    + msg.callback + ": " + msg.what  
                    );  
            msg.target.dispatchMessage(msg);  
            if (me.mLogging!= null) me.mLogging.println(  
                    "<<<<< Finished to    " + msg.target + " "  
                    + msg.callback);  
            msg.recycle();  
        }  
    }  
}  

在Looper.loop()中,會(huì)進(jìn)入一個(gè)死循環(huán),不斷調(diào)用當(dāng)前MessageQueue的next()方法取出下一條待處理的消息,如果當(dāng)前沒有待處理的消息則阻塞。之后,當(dāng)消息不為空且消息的target字段不為空的話,調(diào)用消息的target字段的dispatchMessage方法,注意,此時(shí)消息的target字段就是當(dāng)初發(fā)送這條消息的Handler對象。
接下來,我們進(jìn)入到Handler的dispatchMessage方法的源代碼中:

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

在Handler的dispatchMessage方法中,首先會(huì)去判斷消息的callback字段是否為空,若不為空,則調(diào)用handleCallback方法對消息進(jìn)行處理,若為空,則再去判斷Handler的mCallback字段是否為空(Handler的無參構(gòu)造函數(shù)中mCallback被設(shè)置為空),若不為空,則調(diào)用mCallback的handleMessage方法,若mCallback字段為空,則直接調(diào)用 handleMessage方法。
至此,我們已經(jīng)從源代碼級別回答了上面提出的兩個(gè)問題:Handler到底將消息發(fā)到哪里?為什么之后在handleMessage中又可以收到之前發(fā)送的消息?相信大家一定都有很深的理解了吧_。
下面介紹一個(gè)Android異步消息處理線程的最標(biāo)準(zhǔn)的寫法,此寫法引自Android官方文檔:

class LooperThread extends Thread {  
      public Handler mHandler;  
      public void run() {  
          Looper.prepare();  
          mHandler = new Handler() {  
              public void handleMessage(Message msg) {  
                  // process incoming messages here  
              }  
          };  
          Looper.loop();  
      }  
  }  

現(xiàn)在,我們來思考一個(gè)非常關(guān)鍵的問題:handleMessage方法何時(shí)運(yùn)行在主線程中,何時(shí)運(yùn)行在子線程中?
有不少朋友可能會(huì)說,handleMessage方法的定義位于主線程中,其就會(huì)在主線程中執(zhí)行,handleMessage方法的定義位于子線程中,其就會(huì)在子線程中執(zhí)行。
事實(shí)真的是這樣嗎?
其實(shí)不然,我先舉個(gè)反例。HandlerThread就是一個(gè)典型的反例。我們在主線程中定義Handler并覆寫其handleMessage方法,在定義Handler時(shí),我們傳入一個(gè)已經(jīng)啟動(dòng)的HandlerThread對象的Looper作為參數(shù),那么,我們此時(shí)的handleMessage方法是運(yùn)行在子線程中的,但此時(shí)我們handleMessage方法的定義是位于主線程中的。
這是怎么回事呢?明明handleMessage方法的定義是位于主線程中的,怎么會(huì)運(yùn)行在子線程里面呢?看來還得再次分析一下我們的源碼,不過這一次,我們需要對源碼進(jìn)行逆向分析。
首先,我們知道,handleMessage方法是在dispatchMessage方法中被調(diào)用的,而dispatchMessage方法又是在Looper.loop()中調(diào)用的,也就是說,如果Looper.loop()運(yùn)行在主線程,handleMessage方法也會(huì)運(yùn)行在主線程,如果Looper.loop()運(yùn)行在子線程,handleMessage方法也會(huì)運(yùn)行在子線程。那么我們的Looper.loop()到底是運(yùn)行在主線程,還是在子線程呢?其實(shí),這就要看我們定義的Handler用的是哪個(gè)線程的Looper了,如果我們定義的Handler用的是主線程的Looper,那么它使用的MessageQueue自然也是主線程Looper對應(yīng)的MessageQueue,通過該Handler發(fā)送的消息會(huì)進(jìn)入該MessageQueue中,之后會(huì)在主線程的Looper對應(yīng)的Looper.loop()方法中不斷取出該MessageQueue中的消息進(jìn)行處理,注意,此時(shí)我們主線程的Looper對應(yīng)的Looper.loop()方法也是運(yùn)行在主線程中的,所以此時(shí)我們的handleMessage方法也是運(yùn)行在主線程中的。定義的Handler用的是子線程的Looper的分析過程同上。總結(jié)一下,如果創(chuàng)建Handler時(shí)用的是主線程的Looper,則相應(yīng)的handleMessage方法會(huì)運(yùn)行在主線程中,如果創(chuàng)建Handler時(shí)用的是子線程的Looper,則相應(yīng)的handleMessage方法會(huì)運(yùn)行在子線程中。
看到這里,相信大家已經(jīng)對Android的異步消息處理機(jī)制有了一個(gè)非常深入的理解了,如果對文中內(nèi)容有疑惑的話請?jiān)诓┛拖路搅粞?,我?huì)盡可能地解答,謝謝大家,望大家多多支持!

參考:http://blog.csdn.net/guolin_blog/article/details/9991569

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

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

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