1. 程式人生 > >自己動手擼一個Handler

自己動手擼一個Handler

一、關於Handler

Handler對於我們Android開發者來說應該是再熟悉不過了,這也是在Android中最重要的訊息機制,特別是在面試筆試時,Handler機制也是最常問到的話題。今天我們就來動手擼一個自己寫的Handler,用java層程式碼方式來實現,進一步來了解Handler線上程通訊過程中的作用。

二、問題

Handler機制也可以理解為執行緒間的訊息機制,如果我們自己來設計Handler實現執行緒間通訊,需要怎麼做呢?我們知道,在Handler機制中,最重要的幾個類:HandlerLooperMessageQueueMessageThreadLocal。那它們在具體實現中又有什麼作用呢?

三、思考

首先,從使用者角度來看,他的操作只有兩步:

  1. 在主執行緒建立Handler例項,並重寫handleMessage方法處理訊息。
  2. 在子執行緒獲取Handler的引用呼叫sendMessage方法傳送訊息,在handleMessage中即可處理該訊息。

那從設計者角度來看,我們要分清HandlerLooperMessageQueueMessageThreadLocal這幾個類都擔當了什麼職責:

  1. Handler 負責傳送和處理訊息
  2. Looper 訊息泵,也就是負責取出訊息交給Handler來處理。
  3. MessageQueue 訊息佇列,負責存取訊息。
  4. Message 具體傳送的訊息。
  5. ThreadLocal 它主要用於做執行緒間的資料隔離用的,這裡它在每個執行緒中存放各自對應的Looper

好了,簡單分析完各個類的作用,那我們開始挽起袖子擼程式碼吧。

四、實現

原理圖

1、 Handler的實現

由於Handler主要負責傳送和處理訊息,那我們主要實現它的sendMessagesendMessagedispatchMessage三個方法,來處理訊息的傳送和接收:

public class Handler {
      //訊息佇列
     MessageQueue mQueue;
      //Looper
Looper mLooper; public 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; } public final void sendMessage(Message msg){ MessageQueue queue = mQueue; if (queue != null) { msg.target = this; queue.enqueueMessage(msg); }else { RuntimeException e = new RuntimeException( this + " sendMessage() called with no mQueue"); throw e; } } /** * Subclasses must implement this to receive messages. */ public void handleMessage(Message msg) { } /** * Handle system messages here. */ public void dispatchMessage(Message msg) { handleMessage(msg); } }

我們在Handler的建構函式中獲取當前執行緒對應的looper,並取出Looper中對應的訊息佇列儲存在成員變數中。sendMessage方法中我們給Messagetarget變數賦值為this,也就是表明了Message是由當前的Handler來負責處理的,之後呼叫enqueueMessage方法將訊息存入訊息佇列中。而dispatchMessage方法我們實現比較簡單,負責呼叫handleMessage來處理訊息。

2、 Looper的實現

Looper主要負責取出訊息交由Handler處理,我們主要來實現prepareloop方法:

public class Looper {

    // sThreadLocal.get() will return null unless you've called prepare().
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    MessageQueue mQueue;

    private Looper() {
        mQueue = new MessageQueue();
    }

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

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

    public static void loop() {

        Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException(
                    "No Looper; Looper.prepare() wasn't called on this thread.");
        }

        MessageQueue queue = me.mQueue;

        for (;;) {
            Message msg = queue.next();
            if (msg == null || msg.target == null)
                continue;
            //轉發給handler
            msg.target.dispatchMessage(msg);
        }
    }
}

Looper的建構函式中我們建立了對應的訊息佇列來存取訊息,並且在prepare方法中存入ThreadLocal當前執行緒的Looperloop方法從當前執行緒的Looper的訊息佇列中取出訊息,最終呼叫msg.target.dispatchMessage(msg)交友之前傳送訊息的Handler來處理訊息。

3、Message的實現

Message的實現比較簡單:

public final class Message {
    //處理該訊息的Handler 
    Handler target;
    public int what;

    public Object obj;

    @Override
    public String toString() {
        return obj.toString();
    }
}

4、MessageQueue訊息佇列的實現

在訊息佇列的實現中我們主要考慮幾個問題:
1. 用什麼資料結構存放訊息,存放資料大小有限制。
2. 當next()方法取出訊息時,訊息佇列沒有訊息,該方法應阻塞。
3. 當enqueueMessage方法存放訊息時,訊息大於存放訊息限制大小,應阻塞。

//訊息佇列
public class MessageQueue {
    //互斥鎖
    Lock lock;
    //條件變數
    Condition mEmptyQueue;
    Condition mFullQueue;
    //訊息
    Message[] mMessages;
    //裝入  和取出訊息的下標
    int putIndex;
    int takeIndex;
    //記錄數   用於判斷是否繼續生產和消費
    int count;
    public MessageQueue(){
        //初始化50個訊息
        mMessages = new Message[50];
        lock = new ReentrantLock();
        //標示
        mEmptyQueue = lock.newCondition();
        mFullQueue = lock.newCondition();
    }
    //生產者 子執行緒
    final void enqueueMessage(Message msg){
        //新增至訊息佇列
        try{
            lock.lock();
            while(count == mMessages.length){
                try {
                    mFullQueue.await();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            mMessages[putIndex] = msg;
            putIndex = (++putIndex == mMessages.length ? 0 : putIndex);
            count++;
            //通知主執行緒繼續執行
            mEmptyQueue.signalAll();
        }finally{
            lock.unlock();
        }

    }
    //消費者  主執行緒
    final Message next(){
        //取出訊息
        Message message = null;
        try{
            lock.lock();
            //取到最後一個
            while (count == 0) {
                try {
                    mEmptyQueue.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
             message = mMessages[takeIndex];
             mMessages[takeIndex] = null;
            takeIndex = (++takeIndex == mMessages.length ? 0 : takeIndex);
            count--;
            //通知子執行緒   
            mFullQueue.signalAll();
        }finally{
            lock.unlock();
        }
        return message;
    }
}

這裡的nextenqueueMessage是典型的生產者、消費者的關係,為防止出現錯亂我們給兩個方法都加上Lock鎖,當enqueueMessage方法存放訊息時如果當前佇列訊息滿了,則呼叫mFullQueue.await();進行等待訊息處理,當向訊息佇列中存放訊息後,也就是說訊息佇列不為空了,呼叫mEmptyQueue.signalAll();通知next()方法來處理訊息。

至此,我們的Handler訊息處理過程已經基本完成了,下面我們測試下看看:

5、測試

public class Test {

    public static void main(String[] args) {
        //初始化Looper
        Looper.prepare();

        final Handler hander = new Handler(){
            public void handleMessage(Message msg) {
                System.out.println(Thread.currentThread().getName() + "--receiver--" + msg.toString());
            };
        };

        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                public void run() {
                    while (true) {
                        Message msg = new Message();
                        msg.what = 0;
                        synchronized (UUID.class) {
                            msg.obj = Thread.currentThread().getName()+"--send---"+UUID.randomUUID().toString();
                        }
                        System.out.println(msg);
                        hander.sendMessage(msg);
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }
        //開始訊息迴圈
        Looper.loop();
    }
}

看下測試結果:

Thread-0--send---e4ed9a81-3477-4f5d-a663-1d30626d93b5
Thread-8--send---4c403131-ec14-406c-b57b-a4943dfa93ac
Thread-7--send---9752a85d-9517-4607-a54f-92c2342b7a28
Thread-6--send---7d4ee443-3ab5-4c4e-aac5-eeafc26b78d9
Thread-9--send---70ba7292-1ff4-404d-974e-dfedb1a3fa71
Thread-4--send---614e07e6-bc39-45be-93b0-6996de7f159e
Thread-2--send---7bfaa831-a31b-457a-82cd-145a9d98d351
Thread-5--send---8ffd7327-6ddb-4088-93e6-1304fc926814
Thread-1--send---f6d5e373-88b0-44e9-ab51-f95808acb068
main--receiver--Thread-0--send---e4ed9a81-3477-4f5d-a663-1d30626d93b5
main--receiver--Thread-8--send---4c403131-ec14-406c-b57b-a4943dfa93ac
main--receiver--Thread-7--send---9752a85d-9517-4607-a54f-92c2342b7a28
main--receiver--Thread-6--send---7d4ee443-3ab5-4c4e-aac5-eeafc26b78d9
main--receiver--Thread-9--send---70ba7292-1ff4-404d-974e-dfedb1a3fa71
main--receiver--Thread-4--send---614e07e6-bc39-45be-93b0-6996de7f159e
main--receiver--Thread-2--send---7bfaa831-a31b-457a-82cd-145a9d98d351
main--receiver--Thread-5--send---8ffd7327-6ddb-4088-93e6-1304fc926814
Thread-3--send---56f8b613-99fa-4ef2-a4b9-c762c4d0cd27
main--receiver--Thread-1--send---f6d5e373-88b0-44e9-ab51-f95808acb068
main--receiver--Thread-3--send---56f8b613-99fa-4ef2-a4b9-c762c4d0cd27

測試成功!!我們自己的Handler也可以正常處理訊息啦~

五、總結

Handler原始碼的實現過程要比我們自己的複雜很多,特別是訊息處理的細節,呼叫了底層C++的程式碼。但實現的整體思路和我們是一樣的,通過動手實踐一次,加深對Handler的理解,對我們認識和處理訊息機制的問題大有裨益。