天天看點

從源碼的角度解析Android異步消息處理機制

轉載于:

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

http://www.cnblogs.com/JczmDeveloper/p/4403129.html

1.前言

  • Android UI是線程不安全的,如果在子線程中嘗試進行UI操作,程式就有可能會崩潰。

    在一個Activity中,當有多個線程更新UI時,并且沒有枷鎖機制,就會出現更新界面混亂,但是如果有枷鎖機制的話,又會導緻性能下降,此時使用Handler機制,通過消息隊列,保證了消息處理的先後順序,進而很好的更新了界面。

  • 解決的方案應該也是早已爛熟于心,即建立一個Message對象,然後借助Handler發送出去,之後在Handler的handleMessage()方法中獲得剛才發送的Message對象,然後在這裡進行UI操作就不會再出現崩潰了,解決多線程并發的問題。

在下面介紹handler機制前,首先得了解以下幾個概念:

  • Message

    消息,了解為線程間通訊的資料單元。例如背景線程在處理資料完畢後需要更新UI,則可發送一條包含更新資訊的Message給UI線程。

  • Message Queue

    消息隊列,用來存放通過Handler釋出的消息,按照先進先出(FIFO)執行。

  • Handler

    Handler是Message的主要處理者,負責将Message添加到消息隊列以及對消息隊列中的Message進行處理。

  • Looper

    循環器,扮演Message Queue和Handler之間橋梁的角色,循環取出Message Queue裡面的Message,并傳遞給相應的Handler進行處理。

  • 線程

    UI thread 通常就是main thread,而Android啟動程式時會替它建立一個Message Queue。

    每一個線程裡可含有一個Looper對象以及一個MessageQueue資料結構。在你的應用程式裡,可以定義Handler的子類别來接收Looper所送出的消息。

2.代碼分析

(1)Handler的作用

  • 傳遞message

    用于接受子線程發送的資料,并用此資料配合主線程更新UI。

sendEmptyMessage(int what);
sendMessage(Message msg);
sendMessageAtTime(Message msg,long uptimeMillis);//uptimeMillis 自系統開機到目前時間的毫秒數再加上延遲時間
sendMessageDelayed(Message msg,long delayMillis);
sendMessageAtFrontOfQueue(Message msg);
           
  • 傳遞Runnable對象
post(Runnable run);
postAtTime(Runnable run,long uptimeMillis);
postDelayed(Runnable run,long delayMillis);
           
  • 傳遞Callback對象

    Callback用于截獲handler發送的消息,如果傳回true,就截獲成功,不再往下傳遞。

    如果傳回false,則繼續執行下面的handleMessage方法。

public Handler m = new Handler(new Handler.Callback(){
    public boolean handleMessage(Message msg) {
        Toast.makeText(context, "handle intercept", Toast.LENGTH_LONG).show();
        return true;
    };
}){
    public void handleMessage(Message msg) {
        Toast.makeText(context, "handleMessage", Toast.LENGTH_LONG).show();
    };
};
           

(2)Handler的原理

  • handler封裝了message的發送
  • Looper内部包含一個Message Queue消息隊列,所有handler發送的消息都走向這個隊列 。

    Looper.loop()方法是一個for死循環,不斷從Message Queue取消息,有消息就處理消息,沒有消息就阻塞

  • Message Queue消息隊列,可以添加消息,處理消息

Handler負責發送message,Looper負責接受Handler發送的message,并直接把message回傳給handler自己,Message Queue就是一個存儲消息的容器。

下圖展示了具體流程:

從源碼的角度解析Android異步消息處理機制

一個線程中隻有一個Looper執行個體,一個MessageQueue執行個體,可以有多個Handler執行個體。

如下圖:

從源碼的角度解析Android異步消息處理機制

(3)子線程中建立Handler

1.自定義子線程并建立Handler
class MyThread extends Thread{
    private Handler m_Handler = null;
    @Override  
    public void run() {  
        Looper.prepare();  
        handler = new Handler(){
                    @Override
                    public void handleMessage(Message msg) {
                        if(==msg.what){
                            super.handleMessage(msg);
                            try {
                                Thread.sleep();
                                Toast.makeText(getApplicationContext(), "currentThread:"+Thread.currentThread().getName(), Toast.LENGTH_LONG).show();
                            } catch (InterruptedException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                            }
                        }
                    }
        };
        m_Handler.sendEmptyMessage();
        Looper.loop();
    }
}

//啟動線程處理耗時操作
new MyThread().start();
           

一般UI主線程中不宜做耗時操作,此時就可以通過子線程中的消息機制來處理耗時操作。

主線程中建立Handler時,不需要調用Looper.prepare()和Looper.loop(),是因為系統已經幫我們自動調用們調用了。

子線程中建立的Handler時,需要先調用Looper.prepare(),否則會導緻 crash:Can’t create handler inside thread that has not called Looper.prepare(),而且之後還需要調用Looper.loop()來循環從消息隊列處理消息。之是以會這樣,我們需要看下Handler源碼:

public Handler() {  
    if (FIND_POTENTIAL_LEAKS) {  
        final Class<? extends Handler> klass = getClass();  
        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&  
                (klass.getModifiers() & Modifier.STATIC) == ) {  
            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;  
}  
           

源碼中調用了Looper.myLooper()方法擷取了一個Looper對象,如果Looper對象為空,則會抛出一個運作時異常,提示的錯誤正是 Can’t create handler inside thread that has not called Looper.prepare()!那什麼時候Looper對象才可能為空呢?這就要看看Looper.myLooper()中的代碼了,如下所示:

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

從sThreadLocal對象中取出Looper。如果sThreadLocal中有Looper存在就傳回Looper,如果沒有Looper存在自然就傳回空了。是以你可以想象得到是在哪裡給sThreadLocal設定Looper了吧,當然是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());  
}  
           

首先判斷sThreadLocal中是否已經存在Looper了,如果還沒有則建立一個新的Looper設定進去。這樣也就完全解釋了為什麼我們要先調用Looper.prepare()方法,才能建立Handler對象。同時也可以看出每個線程中最多隻會有一個Looper對象。

接下來就是發送消息,除了sendMessageAtFrontOfQueue()方法之外,其它的發送消息方法最終都會輾轉調用到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()方法接收兩個參數,其中msg參數就是我們發送的Message對象,而uptimeMillis參數則表示發送消息的時間,它的值等于自系統開機到目前時間的毫秒數再加上延遲時間,如果你調用的不是sendMessageDelayed()方法,延遲時間就為0,然後将這兩個參數都傳遞到MessageQueue的enqueueMessage()方法中。這個MessageQueue又是什麼東西呢?其實從名字上就可以看出了,它是一個消息隊列,用于将所有收到的消息以隊列的形式進行排列,并提供入隊和出隊的方法。這個類是在Looper的構造函數中建立的,是以一個Looper也就對應了一個MessageQueue。

enqueueMessage()方法毫無疑問就是入隊的方法了,我們來看下這個方法的源碼:

final boolean enqueueMessage(Message msg, long when) {  
    if (msg.when != ) {  
        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 ==  || 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;  
}  
           

MessageQueue并沒有使用一個集合把所有的消息都儲存起來,它隻使用了一個mMessages對象表示目前待處理的消息。然後觀察上面的代碼的16~31行我們就可以看出,所謂的入隊其實就是将所有的消息按時間來進行排序,這個時間當然就是我們剛才介紹的uptimeMillis參數。具體的操作方法就根據時間的順序調用msg.next,進而為每一個消息指定它的下一個消息是什麼。當然如果你是通過sendMessageAtFrontOfQueue()方法來發送消息的,它也會調用enqueueMessage()來讓消息入隊,隻不過時間為0,這時會把mMessages指派為新入隊的這條消息,然後将這條消息的next指定為剛才的mMessages,這樣也就完成了添加消息到隊列頭部的操作。

現在入隊操作我們就已經看明白了,那出隊操作是在哪裡進行的呢?這個就需要看一看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();  
        }  
    }  
}  
           

可以看到,這個方法從第4行開始,進入了一個死循環,然後不斷地調用的MessageQueue的next()方法,我想你已經猜到了,這個next()方法就是消息隊列的出隊方法。不過由于這個方法的代碼稍微有點長,我就不貼出來了,它的簡單邏輯就是如果目前MessageQueue中存在mMessages(即待處理消息),就将這個消息出隊,然後讓下一條消息成為mMessages,否則就進入一個阻塞狀态,一直等到有新的消息入隊。繼續看loop()方法的第14行,每當有一個消息出隊,就将它傳遞到msg.target的dispatchMessage()方法中,那這裡msg.target又是什麼呢?其實就是Handler啦,你觀察一下上面sendMessageAtTime()方法的第6行就可以看出來了。接下來當然就要看一看Handler中dispatchMessage()方法的源碼了,如下所示:

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

在第5行進行判斷,如果mCallback不為空,則調用mCallback的handleMessage()方法,否則直接調用Handler的handleMessage()方法,并将消息對象作為參數傳遞過去。這樣我相信大家就都明白了為什麼handleMessage()方法中可以擷取到之前發送的消息了吧!

2.使用HandlerThread子線程

HandlerThread繼承自Thread,它與普通Thread的差別在于内部有個Looper成員變量,這個Looper對象就是對消息隊列以及消息隊列邏輯處理的封裝。當我們需要一個工作線程,且不是用過就廢棄的話,就可以使用它。

private Handler mHandler;
private HandlerThread mHandlerThread;
private void setRunnableToWorker(Runnable r){
    if(null == mHandlerThread){
        mHandlerThread = new HandlerThread("WorkThread");
        mHandlerThread.setPriority(Thread.MIN_PRIORITY);
        mHandlerThread.start();
    }

    if(null == mHandler){
        mHandler = new Handler(mHandlerThread.getLooper());
        mHandler.post(r);
    }
}
           

(4)主線程與子線程通過Handler資訊互動

//方式一:
    private Handler mThreadHandler;//建立工作線程handler
    private HandlerThread mHandlerThread;//工作線程
    private void setRunnableToWorker(Runnable r){
        if(null == mHandlerThread){
            mHandlerThread = new HandlerThread("WorkThread");
            mHandlerThread.setPriority(Thread.MIN_PRIORITY);
            mHandlerThread.start();
        }
        if(null == mThreadHandler){
            mThreadHandler = new Handler(mHandlerThread.getLooper());
            mThreadHandler.post(r);
        }
    }
    //建立主線程handler
    private Handler m_Handler = new Handler(){
            public void handleMessage(Message msg) {
                if(msg.what == ){
                    Toast.makeText(getApplicationContext(), "update UI", Toast.LENGTH_LONG).show();
                    tv.setText("hi");

                }
            };
    };
    //onResume()中調用setRunnableToWorker()處理耗時操作,并通過handler更新UI
    setRunnableToWorker(new Runnable() {

                @Override
                public void run() {
                    try {
                        Thread.sleep();
                        m_Handler.sendEmptyMessage();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    Toast.makeText(getApplicationContext(), "update UI", Toast.LENGTH_LONG).show();

                }
    });


//方式二:
    private WorkHandler mWorkHandler;//建立工作線程的Handler
    private class WorkHandler extends Handler{
        public WorkHandler(Looper l){
            super(l);
        }
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if(msg.what == ){
                try {
                    Thread.sleep();
                    m_Handler.sendEmptyMessage();//耗時操作處理完畢,發送消息給主線程更新UI                   
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }
    //建立主線程handler
    private Handler m_Handler = new Handler(){
            public void handleMessage(Message msg) {
                if(msg.what == ){
                    Toast.makeText(getApplicationContext(), "update UI", Toast.LENGTH_LONG).show();
                    tv.setText("hi");

                }
            };
    };
    //建立工作線程
    private HandlerThread mWorkThread;
    //onResume()中
    mWorkThread = new HandlerThread("workThread");
    mWorkThread.start();
    mWorkHandler = new WorkHandler(mWorkThread.getLooper());
    mWorkHandler.sendEmptyMessage();//發送消息給工作線程,讓其處理耗時操作
           

(5)Android中更新UI的方式

1.Handler的post()方法
public final boolean post(Runnable r)  
{  
   return  sendMessageDelayed(getPostMessage(r), );  
} 
//調用了sendMessageDelayed()方法去發送一條消息,并且還使用了getPostMessage()方法将Runnable對象轉換成了一條消息

private final Message getPostMessage(Runnable r) {  
    Message m = Message.obtain();//可以避免重複建立Message對象  
    m.callback = r;  
    return m;  
}  
//在這個方法中将消息的callback字段的值指定為傳入的Runnable對象。咦?這個callback字段看起來有些眼熟啊,喔!在Handler的dispatchMessage()方法中原來有做一個檢查,如果Message的callback等于null才會去調用handleMessage()方法,否則就調用handleCallback()方法。

private final void handleCallback(Message message) {  
    message.callback.run();  
}  
//直接調用了一開始傳入的Runnable對象的run()方法。是以在子線程中通過Handler的post()方法進行UI操作就可以這麼寫:
           
public class MainActivity extends Activity {  

    private Handler handler;  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        handler = new Handler();  
        new Thread(new Runnable() {  
            @Override  
            public void run() {  
                handler.post(new Runnable() {  
                    @Override  
                    public void run() {  
                        // 在這裡進行UI操作  
                    }  
                });  
            }  
        }).start();  
    }  
}  
           
2.Activity的runOnUiThread()方法
public final void runOnUiThread(Runnable action) {  
    if (Thread.currentThread() != mUiThread) {  
        mHandler.post(action);  
    } else {  
        action.run();  
    }  
}  
//如果目前的線程不等于UI線程(主線程),就去調用Handler的post()方法,否則就直接調用Runnable對象的run()方法。
           
3.View的post()方法
public boolean post(Runnable action) {  
    Handler handler;  
    if (mAttachInfo != null) {  
        handler = mAttachInfo.mHandler;  
    } else {  
        ViewRoot.getRunQueue().post(action);  
        return true;  
    }  
    return handler.post(action);  
} 
//最終調用的還是handler的post方法
           
4.Handler的sendMessage方法

見上述分析