天天看點

Android必備知識點之消息傳遞機制Handler

1.最常見的使用場景

Android中常用Handler使用場景?

并不能在子線程中通路UI控件,否則會觸發程式異常,這時候需要通過Handler将更新UI的操作切換到主線程進行

系統為什麼不允許在子線程中通路UI呢?

UI控件不是線程安全的,多線程并發通路的時候可能會導緻UI處于不可預期的狀态,如果對UI控件的通路加線程鎖,會降低UI線程的通路效率,另外就是邏輯變的複雜。

簡要概括:

Handler運作需要底層MessageQueue 和 Looper支撐

MessageQueue:消息隊列,内部存儲一組消息,一對隊列的形式對外提供插入和删除的工作。内部存儲結構不是隊列,而是單連結清單

Looper:消息循環,MessageQueue隻是消息的存儲單元,不能處理消息,Looper則無限循環查詢消息,如果有新消息,則處理,否則一直等待。

ThreadLocal:并不是線程,它的作用是在每個線程中存儲資料。

Handle建立的時候會才用目前線程的Looper來構造消息循環系統,Handler是怎麼獲得目前線程的呢?是因為使用了ThreadLocal,它可以在不同的線程中存儲并提供資料,通過ThreadLocal可以輕松擷取每個線程的Looper。

ThreadLocal詳解

ThreadLocal是一個線程内部存儲類,通過他可以在指定的線程中存儲資料,資料存儲以後,隻有在指定線程中可以擷取到存儲的資料,對于其他線程則無法擷取到資料。

1.使用場景:

①以線程為作用域并且不同的線程具有不同的資料副本。

②複雜邏輯下的對象傳遞,例如監聽器的傳遞。通過參數傳遞或者靜态全局變量,都有局限性

2.使用

ThreadLocal<T> mtL = new ThreadLocal<T>();
//設定
mtl.set("..");
//擷取
mtl.get();
           

3.源碼解析

public void set(T value) {
    Thread currentThrea = Thread.currentThread();
    Values values = values(currentThread);
    if(values == null) {
        values = initializeValues(currentThrea);
    }
    values.put(this,value);
}
           

通過valuse方法擷取目前線程中的ThreadLocal資料,沒有則初始化,然後再講ThreadLocal的值進行存儲,數組形式的存儲,這裡數組排列順序:所有的線程有一個排序,這個排序,存儲資料是目前線程排序下角标加一。具體就不深入了~

Looper詳解
new Thread(new Runnable() {
        @Override
        public void run() {
            //為目前線程建立looper
            Looper.prepare();
            Handler handler = new Handler();
            //開啟消息循環
            Looper.loop();
        }
    }).start();
           

①Looper.quit()和Looper.quitSafely()的差別

quit會直接退出Looper,而quitSafely隻是設定一個退出标記,然後把消息隊列中的已有的消息處理完畢才安全的退出。

解析:

①Looper.prepare()

此方法會調用Looper(boolea quitAllowed)建立Looper,

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

持有目前線程和MessageQueue;

②Looper.loop()

代碼過于長就不粘貼了,大概解析一下

....
for(;;){
    Message msg = mQueue.next();
    if(msg == null) {
        return;
    }
    ...
    msg.target.dispatchMessage(msg);
    ...
}
           

loop方法是個死循環,唯一跳出的方式就是MessageQueue的next方法傳回null,即當調用Looper.quit()和Looper.quitSafely(),next為空,消息隊列退出。也就是說,如果Looper必須退出嗎,否則一直循環。

處理消息:msg.target.dispatchMessage(msg);其中msg.target是發送這條消息的Handler的對象,也就是說,消息最終交給dispatchMessage方法處理

Handler的工作原理

handler.sendMessage(Message msg); –> sendMessageDelayed(Message msg,long 0); –> sendMessageAtTime(Message msg, long delaymillis) –> enqueueMessage(MessageQueue queue, Message msg, long delaymillis)

Handler發送消息是向消息隊列中插入一條消息,MessageQueue的next方法就會傳回這條消息給Looper,Looper收到消息後就開始處理了,最終消息由Looper交由Handler處理,即Handler的dispatchMessage方法會被調用,這時Handler就進入處理消息階段了,調用handleCallBack(msg)

抽象方法

/**
 * Handle system messages here.
 */
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}
           
Handler和Handler.Callback的handleMessage差別:

如果直接用Handler的handleMessage黃色警報,換成靜态的就好了,原因:MessageQueue中的消息隊列會一直持有對handler的引用,而作為内部類的handler會一直持有外部類的引用,就會導緻外部類不能被GC回收。當我們發延時很長的msg時就容易出現洩漏。是以此處應該設定為static,然後Handler就會跟随類而不是跟随類的對象加載,也就不再持有外部類的對象引用。

handler.post(new Runnable(){
    @Override
    public void run() {
         mTestTV.setText("This is post");//更新UI
    }});
           

和handler.sendMessage(msg);作用相當

繼續閱讀