動手實現Handler v1.3

keyboard3發表於2017-12-13

Handler是一個訊息的分發物件,進行訊息的傳送和處理。主要用於非同步訊息的通訊。
Handler配合Looper是Android在單執行緒模型實現多執行緒通訊的重要構件
單執行緒模型是指Android程式啟動時會建立一個主執行緒,只有在主執行緒下才能更新UI,其他執行緒執行耗時操作然後交由主執行緒重新整理頁面。

  • 多執行緒通訊實現原理
    應用啟動時,會自動初始化Lopper(如果是子執行緒必須自己建立Looper)
    然後將Looper丟到ThreadLocal中
    開啟lopper.loop()死迴圈讀取訊息
    主執行緒中建立Handlerd物件的初始化函式從ThreadLocal拿到Looper中的messageQueue
    其他執行緒通過Handler物件向MessageQueue新增訊息。
    looper從MessageQueue中獲取訊息,通過訊息所屬的handler物件分發訊息
    如果訊息有回撥Runable物件就直接呼叫執行,否則交由handleMessage()方法處理

  • 多生產者-單消費者
    這裡多執行緒中的一個典型場景就是多生產者對應一個消費者。那麼關鍵是通過什麼保證了執行緒安全
    MessageQueue.javaMessage.java
    生產者:enqueueMessage()
    synchronized (this-MessageQueue)
    message單向連結串列 -先進後出
    消費者:next()
    從單連結串列中取出訊息,空就阻塞直到有訊息進入
    取出message並更新訊息佇列單連結串列的節點,執行緒安全

當消費執行緒消費速度大於生產速度時,便阻塞在loop的queue.next()中的nativePollOnce()方法裡。此時主執行緒會釋放CPU資源進入休眠狀態,直到下個訊息到達或者有事務發生來喚醒主執行緒工作。

  • Android中為什麼主執行緒不會因為Looper.loop()裡的死迴圈卡死? Looper.java

  • 為什麼採用死迴圈
    既然Android中採用了單執行緒模型,而我們知道執行緒執行完畢就會自動結束。而我們的應用需要一直響應使用者操作,不能開啟就自動關閉了。所以採用了死迴圈

  • 系統服務AMS如何與應用的主執行緒進行互動
    在進入loop之前開啟Binder執行緒用於接受系統AMS事件,然後Binder通過系統整合Handler的內部類向主執行緒傳送Message

  • Activity的生命週期是怎麼實現在死迴圈體外能夠執行起來的?
    H的handleMessage()接受不同的生命週期的message,執行不同的生命週期的邏輯

自己實現Handler多執行緒通訊

MyLooper

public class MyLooper {
    /**
     * 當前執行緒的MyLooper變數
     */
    private static ThreadLocal<MyLooper> sThreadLocal = new ThreadLocal<>();
    private MyMessageQueue mQueue = new MyMessageQueue();

    /**
     * 初始化looper物件,並將其存入執行緒中
     */
    public static void prepare() {
        sThreadLocal.set(new MyLooper());
    }

    /**
     * 從當前執行緒獲取looper物件
     * @return
     */
    public static MyLooper getLooper() {
        return sThreadLocal.get();
    }

    public MyMessageQueue getQueue() {
        return mQueue;
    }

    /**
     * 輪詢訊息佇列,分發訊息
     */
    public void loop() {
        for (; ; ) {
            MyMessage message;
            message = mQueue.next();//阻塞直到獲取訊息
            message.target.dispatchMessage(message);//訊息分發
        }
    }
}
複製程式碼

MyHandler

public abstract class MyHandler {

    private final MyMessageQueue myQueue;

    public MyHandler() {
        MyLooper looper = MyLooper.getLooper();
        if (looper == null) {
            throw new IllegalArgumentException("current thread no MyLooper!");
        }
        /**
         * 從當前執行緒中的Looper物件中獲取訊息佇列
         */
        myQueue = looper.getQueue();
    }

    /**
     * 向訊息佇列傳送訊息
     *
     * @param message
     */
    public void sendMessage(MyMessage message) {
        message.target = this;//記錄訊息傳送者
        myQueue.enqueueMessage(message);
    }

    /**
     * 向訊息佇列傳送回撥函式
     *
     * @param callBack
     */
    public void post(Runnable callBack) {
        myQueue.enqueueMessage(getPostMessage(callBack));
    }

    private static MyMessage getPostMessage(Runnable callBack) {
        MyMessage message = new MyMessage();
        message.callback = callBack;
        return message;
    }

    /**
     * 分發訊息
     *
     * @param message
     */
    public void dispatchMessage(MyMessage message) {
        if (message.callback != null) {
            handleCallback(message);
        } else {
            handleMessage(message);
        }
    }

    /**
     * 處理回撥函式
     *
     * @param message
     */
    private static void handleCallback(MyMessage message) {
        message.callback.run();
    }

    /**
     * 處理訊息
     *
     * @param message
     */
    public abstract void handleMessage(MyMessage message);
}

複製程式碼

MyMessageQueue

public class MyMessageQueue {
    public volatile MyMessage mMessages;//保證執行緒可見性

    /**
     * 將訊息加入訊息佇列
     *
     * @param msg
     * @return
     */
    public boolean enqueueMessage(MyMessage msg) {
        if (msg == null) {
            throw new IllegalArgumentException("Message must not null.");
        }
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        synchronized (this) {//執行緒安全
            if (mMessages == null) {
                mMessages = msg;
            } else {
                msg.next = mMessages;//先進後出的單連結串列,新訊息加入到訊息頭
                mMessages = msg;
            }
        }
        return true;
    }
    
    //從訊息列表中獲取訊息
    public MyMessage next() {
        MyMessage prevMsg = null;
        MyMessage msg;
        for (; ; ) {
            /**
             * 我們沒法呼叫Jni的nativeOnePoll自動喚醒執行緒,只能空旋執行緒了
             * 每隔一秒檢查一次訊息佇列中是否有新訊息
             */
            do {
                msg = mMessages;
                if (msg == null) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            } while (msg == null);

            synchronized (this) {//執行緒安全讀取訊息
                if (msg != null) {
                    prevMsg = msg;
                    mMessages = mMessages.next;//單連結串列刪除訊息
                    return prevMsg;
                }
            }
        }
    }
}
複製程式碼

MyMessage

public class MyMessage {
    public MyMessage next;//連結串列節點下一個訊息
    public MyHandler target;//訊息傳送的物件
    public Runnable callback;//回撥函式
    public Object obj;
    public int what;
}
複製程式碼

使用

MyHandler handler;
...
new Thread() {
            @Override
            public void run() {
                MyLooper.prepare();//新增looper到執行緒中
                handler = new MyHandler() {//初始化MyHandler
                    @Override
                    public void handleMessage(final MyMessage message) {
                        if (message.what == 1) {
                            runOnUiThread(new Runnable() {//在主執行緒提示訊息
                                @Override
                                public void run() {
                                    Toast.makeText(MainActivity.this, message.obj.toString(), Toast.LENGTH_SHORT).show();
                                }
                            });
                        }
                    }
                };
                MyLooper.getLooper().loop();//開啟loop輪訓messageQueue
            }
        }.start();
...
        MyMessage message = new MyMessage();
        message.what = 1;
        message.obj = "hello world:" + event.value + "";
        handler.sendMessage(message);//傳送訊息
複製程式碼

相關文章