1. 程式人生 > >Handler非同步訊息處理機制的原始碼分析

Handler非同步訊息處理機制的原始碼分析

1. Handler非同步訊息處理機制

  • 主執行緒不能進行耗時操作 (會發生ANR異常)
  • 子執行緒不能更新UI的操作 (因為會報異常——>只有產生該控制元件的執行緒才能操作該控制元件。)

    那麼 , 要在子執行緒請求資料,然後將資料傳送到主執行緒中更新UI , 此時需要使用Handler機制.

2. Handler的核心組成部分

  • Handler : 用於傳送訊息和處理訊息

  • Message : 用於攜帶資料和通知

  • MessageQueue : 以單鏈表的形式用於儲存訊息

  • Looper : 死迴圈,如果訊息佇列裡有訊息就將其取出然後交給對應的Handler處理,如果沒有就處於等待狀態

3. Handler的非同步訊息處理流程圖

首先需要在主執行緒中建立一個Handler物件 , 並重寫handleMessage()方法 , 然後當子執行緒中需要進行UI操作時 , 就建立一個Message物件 , 並通過Handler將這條資訊傳送出去 , 之後這條資訊會被新增到MessageQueue的佇列中等待被處理 , 而Looper則會一直嘗試從MessageQueue中取出待處理訊息 , 最後分發會Handler的handlerMessage()方法中.由於Handler是在主執行緒中建立的 , 所以此時handlerMessage()方法中的程式碼也會在主執行緒中執行 , 進行UI操作.

非同步訊息處理執行緒啟動後會進入一個無限的迴圈體之中,每迴圈一次,從其內部的訊息佇列中取出一個訊息,然後回撥相應的訊息處理函式,執行完成一個訊息後則繼續迴圈。若訊息佇列為空,執行緒則會阻塞等待。

4. Handler的簡單使用方法

  • 在主執行緒中建立一個Handler物件 , 重寫裡面的方法
    // 建立一個Handler物件
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            // 處理訊息
} };
  • 在子執行緒中通過mHandler傳送資訊
    // 建立一個Message物件
    Message msg = Message.obtain();
    // 將訊息存入Message物件中
    msg.obj = null;
    // 給訊息新增標記 , 例如 SUCCESS 和 FAILED
    msg.what = 0;
    // 通過mHandler傳送訊息到主執行緒
    mHandler.sendMessage(msg);
  • 在主執行緒中進行操作
    handle.post(new Runnable() {
        @Override
        public void run() {

        }
    });

5. Handler的原始碼分析

(1) 首先 , 我們來看看Message物件的建立 , 一共有3種方式

Message msg = new Message();
Message msg = Message.obtain();
// 傳送訊息
handler.sendMessage(msg);

// handler已經繫結
Message msg = handler.obtainMessage();
// 傳送訊息
msg.sendToTarget();

handler.obtainMessage()方法裡底層實質上也是呼叫了Message的obtain(handler)方法 , 然後通過sendToTarget()傳送出去

    public final Message obtainMessage()
    {
        return Message.obtain(this);
    }

Message繼承了Parcelable介面 , 可實現序列化 , 那麼 , 接下來 , 我們看看Message物件裡的obtain()方法 , 裡面到底做了什麼 :

public final class Message implements Parcelable
    /*package*/ Message next;
    private static final Object sPoolSync = new Object();
    private static Message sPool;
    private static int sPoolSize = 0;
    private static final int MAX_POOL_SIZE = 50;

    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

從中可以看到一個同步塊 , 通過一個new的object物件進行加鎖 , 然後 , 在同步塊裡 , 如果sPool不為null , 那麼將sPool賦值給了m , 最後返回出來 , 如果sPool為null , 那麼就重新new一個Message物件.
這時 , 大家一定奇怪了 , 第11行和第12行的這兩行程式碼又代表著什麼 , 其實 , 這裡訊息池是一個單鏈表結構 , 這兩行程式碼意味著從訊息池中取出Message物件後 , 對sPool的位置進行重置.

然後 , 我們再來看Message的obtain(Handler h)方法 :

    public static Message obtain(Handler h) {
        Message m = obtain();
        m.target = h;

        return m;
    }

其中 , target就是對Handler進行標記 , 比如一個應用中建立了多個Handler物件來發送訊息 , 那麼就可以用target來進行區分.

好 , 我們接下來再看一下Message裡的recycle()方法 , 對訊息進行回收 , 裡面的屬性都設定為預設值 :

    public void recycle() {
        ...
        recycleUnchecked();
    }


    void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

(2) 我們再走進Handler物件中 , 切入主入口 , Handler的例項化

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

實質上是呼叫了它的過載方法 , 傳入兩個引數 , null 和 false , 如下 :

    public Handler(Callback callback, boolean async) {
        // 此處省略一萬行程式碼
        ...

        // 輪詢器,輪詢取訊息
        mLooper = Looper.myLooper();

        // 訊息佇列,存放handler發的訊息
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

關鍵的部分來了 , 第6行和第9行程式碼 , 獲取了輪詢器的物件mLooper , 以及訊息佇列mQueue , 此處表示handler和mLooper共享同一個訊息佇列 . 那我們首先來看mLooper的初始化 , Looper.myLooper()方法 :

    // sThreadLocal.get() will return null unless you've called prepare().
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

其中 , sThreadLocal是代表本地執行緒變數 , 每個執行緒都可以去呼叫它 , 它也知道是哪個執行緒在存放資料 , 那麼這裡的get()方法 , 就是去獲取資料 , 這時 , 你一定奇怪它是什麼時候存的呢?這裡要提到一個ActivityThread類 , 代表主執行緒 , 在應用程式執行之前 , ActivityThread類就已經初始化了 , 我們來看裡面的核心程式碼 :

 //主執行緒
 public static final void main(String[] args) {
    。。。
    //輪詢器的初始化
    Looper.prepareMainLooper();

    。。。
    //輪詢器開始取訊息
    Looper.loop();


}

其中 , prepareMainLooper()裡面實質是呼叫了prepare()方法

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

可以看到 , sThreadLocal.set()方法添加了一個新的Looper物件 , 也就是說 , 在建立Handler之前 , UI執行緒中就已經呼叫了Looper.prepare()和Looper.loop()方法. 在看Looper.loop()方法之前 , 我們先看一下Looper的構造方法 , 因為在主執行緒中已經建立了一個Looper物件

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

從上面的建構函式中可以看到 , Looper在初始化時 , 就預設建立了一個訊息佇列物件MessageQueue.

    MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }

那我們再來看一下Looper.loop()方法 , 輪詢取訊息了 , 不過還沒有傳送訊息 , 這時是取不到訊息的

    public static void loop() {
        // 當前是主執行緒,只有一個Looper物件,同handler一起共享
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        // 與handler共享同一個訊息佇列
        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;
            }

            ...
            msg.target.dispatchMessage(msg);

            ...
            msg.recycleUnchecked();
        }
    }

程式碼比較多 , 其實核心程式碼就幾個 , 其中Message msg = queue.next() , 從系統執行開始就一直無限迴圈取訊息 , 所以當訊息為null時 , 可能會造成阻塞. 那我們來看一下next()方法裡是什麼

    Message next() {
        ...

        // 程式碼太多,但是看到這一行程式碼就足夠了
        nativePollOnce(ptr, nextPollTimeoutMillis);

        ...
    }

這裡就涉及到JNI機制了 , 底層的通訊交給原生代碼了

    private native static long nativeInit();
    private native static void nativeDestroy(long ptr);
    private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
    private native static void nativeWake(long ptr); // 喚醒
    private native static boolean nativeIsPolling(long ptr);
    private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);

這裡擴充套件一下 , inux程序間的通訊,linux管道通訊,管道其實就是個特殊的檔案,該檔案有兩個描述符(操作檔案的控制代碼(引用)),讀的描述符,寫的描述符

通訊的原型
主執行緒拿著讀的描述符等待讀取資料,子執行緒拿著寫的描述符開始寫資料,寫完資料之後通知拿著讀描述符的主執行緒,主執行緒喚醒,開始輪詢取訊息,處理訊息,如果沒有訊息中斷(阻塞)

(3) 傳送訊息
呼叫handle.sendMessage(msg);

    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);
    }
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        // handler傳送訊息,把當前的handler物件繫結到message物件中
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        // 把訊息放到訊息佇列裡,並排序
        return queue.enqueueMessage(msg, uptimeMillis);
    }

感覺發訊息發了好久 , 上面的sendMessageDelayed()方法可以延遲發訊息 , 最終實質上是呼叫了queue.enqueueMessage(msg, uptimeMillis)方法 , 把訊息放到訊息佇列裡,並排序.

    boolean enqueueMessage(Message msg, long when) {
        // this表示同一個容器,訊息佇列
        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, 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) {
                // 喚醒jni
                nativeWake(mPtr);
            }
        }
        return true;
    }

(4) 取訊息
又回到上面的Looper.loop()方法了 , 精簡版

    public static void loop() {
        final Looper me = myLooper();

        final MessageQueue queue = me.mQueue;

        for (;;) {
            // 取訊息
            Message msg = queue.next(); // might block
            if (msg == null) {

                return;
            }
            // 處理資訊
            msg.target.dispatchMessage(msg);

            ...
            msg.recycleUnchecked();
        }
    }

走到這一步了 , 讓我們來看看handler最後是如何處理訊息的 , msg.target.dispatchMessage(msg) , 注意的是 , 該方法是在主執行緒中執行的 , 因為loop()方法就是在主執行緒中執行

    public void dispatchMessage(Message msg) {
        // 傳送資訊時,預設msg.callback為null
        if (msg.callback != null) {
            // 處理runOnUiThread方法
            handleCallback(msg);
        } else {
            // 我們走這裡,msg.callback==null
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            // 主執行緒,方法回撥
            handleMessage(msg);
        }
    }

最後回撥給了主執行緒中的handler的handleMessage()方法 , 這就是Handler非同步訊息處理機制的全過程.

6. runOnUiThread方法的原始碼分析

實質上也是呼叫了Handler的post方法

public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
        // 關鍵點
        mHandler.post(action);
    } else {
        action.run();
    }
}

接下來我們深入看下Handler的post方法的程式碼邏輯

    public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

看到sendMessageDelayed()方法是不是有種很親切的感覺 , 跟上面不同 , 此時傳入的第一個引數msg為getPostMessage(r)

    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

那麼 , 重點來了 , 返回一個新的Message物件 , 只是多添加了一個callback屬性 , callback不再為null , 走到最後的handler.dispatchMessage(msg)方法

    public void dispatchMessage(Message msg) {
        // 此時,msg.callback不為null
        if (msg.callback != null) {
            // 處理runOnUiThread方法
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

此時 , msg.callback不為null , 那麼就走handleCallback(msg)方法

    private static void handleCallback(Message message) {
        message.callback.run();
    }

這裡的message.callback就是傳入的Runnable , 它本身跟執行緒是沒有任何關係的 , 之所以會在主執行緒中執行 , 完全是因為dispatchMessage()方法是在主執行緒中執行的 , 然後呼叫Runnable.run()方法.

歡迎大家交流 , 共同探討!