Handler深刻(分析源碼,手寫一套Handler)

博客:android

https://www.jianshu.com/u/e9cb5fd3aaa8git

聲明:本文由做者 zho007 受權發佈,未經原做者容許請勿轉載面試

 看到上面藍色字了嗎,點下吧數組

640?wx_fmt=png&wxfrom=5&wx_lazy=1

前言

640?wx_fmt=png&wxfrom=5&wx_lazy=1

在安卓當中提供了異步消息處理機制的兩種方式來解決線程之間的通訊,一種是是AsynchTask,另一種就是如今咱們主要分析的Handler。架構

Handler是Android類庫提供的用於接受、傳遞和處理消息或Runnable對象的處理類,它結合Message、MessageQueue和Looper類以及當前線程實現了一個消息循環機制,用於實現任務的異步加載和處理。異步

簡單使用分析

總所周知,安卓中子線程是不能更新UI的,若是在子線程更新,那麼程序就會崩潰,那麼這時候咱們就使用到了handler,子線程操做完成通知主線程更新UI。咱們先看下handler機制的分析圖,和架構圖:ide

0?wx_fmt=jpeg
0?wx_fmt=jpeg

  • Looper有一個MessageQueue消息隊列;oop

  • MessageQueue有一組待處理的Message;測試

  • Message中有一個用於處理消息的Handler;ui

  • Handler中有Looper和MessageQueue。

一個Handler對應一個Looper對象,一個Looper對應一個MessageQueue對象,使用Handler生成Message,所生成的Message對象的Target屬性,就是該Handler對象。而一個Handler能夠生成多個Message,因此說,Handler和Message是一對多的關係。

Android中主線程向子線程發送消息

1. 建立Handler

在安卓的ui線程中建立一個Handler

Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        Log.e("TAG", "當前線程: " + Thread.currentThread().getName() + " 收到消息:" + msg.obj);
        }
    };

2. 開啓子線程向主線程的handler發送消息

bt.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Message obtain = Message.obtain();
                obtain.obj = Thread.currentThread().getName() + ": 發送消息";
                handler.sendMessage(obtain);
            }
        }).start();
    }
});

3. 結果分析

結果:

當前線程: main  消息:Thread-4: 發送消息

結果說明咱們在主線程中建立handler,而後點擊按鈕子線程向主線程發送消息成功

分析:

咱們按照剛纔的流程圖來 分析一遍。

首先咱們在主線程建立了一個Handler,那麼在主線程中也會對應有一個Looper對象在輪詢消息,一個Looper對象有一個MessageQueue,因而主線程中也有一個MessageQueue。咱們的流程就是主線程中looper對象在一直輪詢消息,若是消息隊列中沒有任何消息的話,那麼當前線程暫時阻塞,直到子線程中獲取handler對象發送消息,這時候,handler會對主線程中的MessageQueue中添加消息,當消息添加成功時,將阻塞的線程喚醒。因而looper輪詢到有新消息,將新消息返回給handler對象,由於handler對象是在主線程中建立,因此消息將會在主線程中顯示。

這個流程和生產者消費者模型有一點類似,一個線程生成消息,一個線程消費消息。因此在MessageQueue中的添加消息,和消費消息都會有一把鎖。將這兩個方法鎖住;首先避免多個線程同時操做消息列隊,和避免再寫入消息的時候讀取消息,致使消息錯亂的問題。以下圖,MessageQueue源碼中鎖住當前對象:

0?wx_fmt=png

0?wx_fmt=png

  • 也許這裏有些難懂,可是不要緊,咱們繼續向下分析

子線程向主線程發送消息

上面咱們操做了子線程向主線線程發送消息,接下來咱們使用handler主線程向子線程發送消息。

1. 子線程中建立handler對象。

2. 爲當前子線程建立一個looper對象。(這裏咱們使用ThreadLocal來保存Looper副本)

3. 開啓子線程looper輪詢消息

   new Thread(new Runnable() {
            @Override
            public void run() {
                //對當前線程建立一個looper副本
                Looper.prepare();
                handler = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);
                        Log.e("TAG", "當前線程: " + Thread.currentThread().getName() + " 收到消息:" + msg.obj);
                    }
                };
                //開啓輪詢消息
                Looper.loop();
            }
        }).start();

4. 主線程向子線程發送消息

Message obtain = Message.obtain();
obtain.obj = Thread.currentThread().getName() + "線程發送的消息";
handler.sendMessage(obtain);

5. 結果分析

結果以下:

當前線程: Thread-4  收到消息:main線程發送的消息

這時候咱們就成功重主線程發送了一條消息給子線程

分析:

咱們重上面代碼注意到相比子線程發送消息給主線程咱們主線程發送消息給子線程多了兩行代碼:

1.Looper.prepare();

咱們翻閱源碼以下:

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

這裏咱們使用了一個ThreadLocal來保存每個接收線程中的Looper對象副本。因爲子線程是咱們手動開啓的線程,因此咱們要初始化一個looper副本。因爲安卓主線程中,安卓系統自動維護了一個安卓主線程的looper對象副本並讓looper輪詢着消息。

2.Looper.loop();

咱們翻閱源碼以下:

public static void loop() {
     //獲取looper對象
     final Looper me = myLooper();
     if (me == null) {
         throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
     }
     //獲取messageQueue對象
     final MessageQueue queue = me.mQueue;
                   ...............
     //輪詢消息
     for (;;) {
         //輪詢messageQueue中的消息,沒有消息就再這裏阻塞。
         Message msg = queue.next();
         if (msg == null) {
             // No message indicates that the message queue is
             return;
         }
                   ...........
         try {
             //發送消息
             msg.target.dispatchMessage(msg);
                    ............
         } finally {
             if (traceTag != 0) {
                 Trace.traceEnd(traceTag);
             }
         }
                    ............
     }
 }

這裏安卓系統也是在主線程中輪詢着消息。

手寫一套handler。

咱們通過上面的使用和簡單分析了之後,也許仍是有一些懵逼。因此下面咱們本身經過生產者/消費者模型來模仿安卓Handler手寫一套。代碼以下:

Handler

public class Handler {
    private Looper mLooper;
    private MessageQueue mQueue;
    public Handler() {
        //獲取當前線程的looper
        mLooper = Looper.myLooper();
        //獲取當前線程的消息列隊
        mQueue = mLooper.messageQuene;
    }
    /**
     * 發送消息
     * @param message
     */
    public void sendMessage(Message message) {
        message.target = this;
        mQueue.enqueueMessage(message);
    }
    /**
     * 處理消息
     * @param message
     */
    public void handleMessage(Message message) {
    }
    /**
     * 分發消息
     * @param message
     */
    public void dispatchMessage(Message message) {
        handleMessage(message);
    }
}

Looper

public class Looper {
    final MessageQueue messageQuene;
    private static ThreadLocal<Looper> threadLocal = new ThreadLocal<>();
    public Looper() {
        messageQuene = new MessageQueue();
    }
    /**
     * 爲當前線程初始化一個looper副本對象
     */
    public static void prepare() {
        if (threadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        threadLocal.set(new Looper());
        System.out.println("looper初始化");
    }
    /**
     * 獲取當前線程的looper副本對象
     *
     * @return
     */
    public static Looper myLooper() {
        return threadLocal.get();
    }
    /**
     * 輪詢消息
     */
    public static void loop() {
        //獲取當前線程的looper對象
        Looper me = myLooper();
        Message msg;
        //開始輪詢消息
        for (; ; ) {
            //輪詢消息,沒有消息就阻塞
            msg = me.messageQuene.next();
            if (msg == null || msg.target == null) {
                System.out.println("Looper:" + "空消息");
                continue;
            }
            System.out.println("Looper:" + "looper輪詢到了消息,發送消息");
            //輪詢到了消息分發消息
            msg.target.dispatchMessage(msg);
        }
    }
}

Message

public class Message {
    //發送的消息
    public Object obj;
    //目標Handler
    public Handler target;
    @Override
    public String toString() {
        return obj.toString();
    }
}

MessageQueue

要實現生產者/消費者模型,首先的有鎖,這裏使用ReentrantLock 
主要考慮的重寫入,它能夠根據設定的變量來喚醒不一樣類型的鎖,也就是說當咱們隊列有數據時,咱們須要喚醒read鎖;當隊列有空間時,咱們須要喚醒寫鎖。

public class MessageQueue {
    Message[] mItems;
    int mPutIndex;
    //隊列中消息數
    private int mCount;
    private int mTakeIndex;
    //鎖
    Lock mLock;
    //喚醒,沉睡某個線程操做
    Condition getCondition;//可取
    Condition addCondition;//可添加
    public MessageQueue() {
        mItems = new Message[50];
        mLock = new ReentrantLock();
        getCondition = mLock.newCondition();
        addCondition = mLock.newCondition();
    }
    /**
     * 消息隊列取消息 出隊
     *
     * @return
     */
    Message next() {
        Message msg = null;
        try {
            mLock.lock();
            //檢查隊列是否空了
            while (mCount <= 0) {
                //阻塞
                System.out.println("MessageQueue:" + "隊列空了,讀鎖阻塞");
                getCondition.await();
            }
            msg = mItems[mTakeIndex];//可能空
            //消息被處理後,置空數組中該項
            mItems[mTakeIndex] = null;
            //處理越界,index大於數組容量時,取第一個item
            mTakeIndex = (++mTakeIndex >= mItems.length) ? 0 : mTakeIndex;
            mCount--;
            //通知生產者生產
            addCondition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            mLock.unlock();
        }
        return msg;
    }
    /**
     * 添加消息進隊列
     *
     * @param message
     */
    public void enqueueMessage(Message message) {
        try {
            mLock.lock();
            //檢查隊列是否滿了
            while (mCount >= mItems.length) {
                //阻塞
                System.out.println("MessageQueue:" + "隊列空了,寫鎖阻塞");
                addCondition.await();
            }
            mItems[mPutIndex] = message;
            //處理越界,index大於數組容量時,替換第一個item
            mPutIndex = (++mPutIndex >= mItems.length) ? 0 : mPutIndex;
            mCount++;
            //通知消費者消費
            getCondition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            mLock.unlock();
        }
    }
}

測試:

public class Test {
    public static Handler handler;
    public static void main(String[] args) throws InterruptedException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                handler = new Handler() {
                    @Override
                    public void handleMessage(Message message) {
                        super.handleMessage(message);
                        System.out.println("Test:" + Thread.currentThread().getName() + "線程接收到:" + message.obj);
                    }
                };
                Looper.loop();
            }
        }).start();
        //睡0.5s,保證上面的線程中looper初始化好了
        Thread.sleep(500);
        new Thread(() -> {
            Message message = new Message();
            message.obj = Thread.currentThread().getName() + "發送的消息 ";
            handler.sendMessage(message);
        }).start();
        new Thread(() -> {
            Message message = new Message();
            message.obj = Thread.currentThread().getName() + "發送的消息 ";
            handler.sendMessage(message);
        }).start();
    }
}

結果分析

結果:

looper初始化
MessageQueue:隊列空了,讀鎖阻塞
Looper:looper輪詢到了消息,發送消息
Test:Thread-0線程接收到:Thread-1發送的消息 
Looper:looper輪詢到了消息,發送消息
Test:Thread-0線程接收到:Thread-2發送的消息 
MessageQueue:隊列空了,讀鎖阻塞

分析:

到這裏咱們的手寫的一套Handler就完成了。本身手寫一次handler消息處理機制,再回過頭來看看handler是否是很簡單了,不再怕面試中被問到。固然android源碼中的handler處理機制移值到C層處理了.

demo地址:https://gitee.com/zhongjiabao/Demo.git

相關文章
相關標籤/搜索