android消息機制《Android開發藝術探索》筆記

異步消息處理機制

異步消息處理機制概述

1.作用:跨線程間消息傳遞。一般用於開啓子線程執行耗時操作後,需要在執行結束更新UI線程。主線程的UI控件非線程安全,因此android系統不允許直接在子線程更新UI。

2.四元素
異步消息處理機制中主要包括Handler、Looper、MessageQueue、Message。

Handler:消息的處理者與發送者。通過sendmessage可以發送消息;通過handlemessage進行處理信息。

MessageQueue:信息隊列,內部其實是一個單鏈表只負責存儲消息,不負責發送消息。

Looper:消息泵,通過loop方法持續的從消息隊列取出消息,併發送至消息對應的Handler。

Message:消息。用來存儲一定的消息,並在線程中傳遞。具有msg.what字段,以此區分不同Message

3.關係:

1.一個線程只可以有一個Looper,因爲Looper類中有一個靜態變量ThreadLocal,線程每創建一個新looper時,
都會將這個looper set到ThreaLocal中,ThreadLocal可以通過線程來找到對應looper。
如果一個線程有多個looper就無法找到對應的拿一個Looper了。

2.一個線程可以有多個Handler

3.一個Looper中只有一個MessageQueue

4.Handler可以根據線程找到對應的Looper。

5.MessageQueue中存放多個Message,這些Message可以來自多個Handler。

注意:Handler可以在任意線程發送消息,這些消息會發送到Handler所關聯的MessageQueue。Handler在處理時是在它關聯的Looper線程中處理消息的

舉個例子:我們首先創建一個Handler對象handler1,其關聯的Looper就是主線程的looper1,我們在子線程使用handler1發送信息,信息會發送到looper1的messageQueue中。looper1將信息發送給handler1,handler1在主線程執行UI操作
在這裏插入圖片描述

異步消息處理機制的工作流程

1.當Handler對象使用sendMessage發送message後,會調用handler所綁定的looper實例中的MessageQueue的enqueueMessage方法,將message添加進MessageQueue。

2.Looper的loop方法會持續調用MessageQueue的next方法去取出消息。取出消息後,會調用msg對應的Handler的dispatchMessage方法。

3.在dispatchMessage方法中,會調用handlemessage方法,進行UI操作。

在這裏插入圖片描述

異步消息處理機制的具體原理

1.ThreadLocal工作原理

當某些數據以線程爲作用域,且不同線程具有不同的數據副本時,我們使用ThreadLocal。

ThreadLocal和信息機制的關係在於,Looper類中有一個靜態變量,這個靜態變量的類型就是ThreadLocal類。當我們爲線程創建Looper時,其實調用了threadLocal的set方法。Handler可以通過threadlocal對象找到當前線程與之對應的Looper。

ThreadLocal的原理是爲每個線程都創建了一個數組來存儲值。當ThreadLocal使用get函數獲取當前線程的值的時候,首先會根據當前線程找到對應的存儲數組,並通過ThreadLocal的下標在數組中找到存儲的值。

ThreadLocal主要函數有set與get,首先來看set函數。

set函數

可以看到在set函數中,首先通過currentThread()方法得到當前線程。Thread內部有一個Values類專門用於存儲線程中的ThreadLocal值。接下來通過values(currentThread)獲取當前線程對應的Values對象。之後判斷values對象是否爲null,如果爲null就初始化,否則就調用put函數去設置當前ThreadLocal的值。

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

可以看到在put方法中,會將ThreadLocal的值放置於table的refrence+1下標處。

       void put(ThreadLocal<?> key, Object value) {
            cleanUp();

            // Keep track of first tombstone. That's where we want to go back
            // and add an entry if necessary.
            int firstTombstone = -1;

            for (int index = key.hash & mask;; index = next(index)) {
                Object k = table[index];

                if (k == key.reference) {
                    // Replace existing entry.
                    table[index + 1] = value;
                    return;
                }
				...
            }
        }
get函數

get函數的邏輯比較清晰,也是首先獲取當前線程,並找到當前線程對應的Values對象,進而從table數組中取出reference+1位置的結果。

    public T get() {
        // Optimized for the fast path.
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values != null) {
            Object[] table = values.table;
            int index = hash & values.mask;
            if (this.reference == table[index]) {
                return (T) table[index + 1];
            }
        } else {
            values = initializeValues(currentThread);
        }
        return (T) values.getAfterMiss(this);
    }

從ThreadLocal的set和get方法可以看出,他們所操作的對象都是當前線程的localValues對象和table數組,因此在不同線程中訪問同一個ThreadLocal的set和get方法,他們對ThreadLocal所做的讀寫操作僅限於各自內部,這就是爲什麼ThreadLocal可以在多個線程中互不干擾的存儲和修改數據

2.Lopper工作原理

主線程中可以不創建Looper,但是在子線程中需要手動創建Looper,否則直接在子線程創建的Handler找不到匹配的Looper會報錯。

主線程不需要創建Looper是因爲已經自動創建了。

//主線程中不需要自己創建Looper
public static void main(String[] args) {
        ......
        Looper.prepareMainLooper();//爲主線程創建Looper,該方法內部又調用 Looper.prepare()
        ......
        Looper.loop();//開啓消息輪詢
        ......
    }

而在子線程需要調用Looper類的靜態方法prepare方法與loop方法。

//子線程中需要自己創建一個Looper
new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();//爲子線程創建Looper               
                Looper.loop(); //開啓消息輪詢
            }
        }).start();

prepare方法中我們創建新的Looper,並將Looper存儲至ThreadLocal中,以便Handler可以通過ThreadLocal找到當前線程對應的Looper。可以從下述代碼看到Looper類有一個全局變量ThreadLocal,並在prepare方法中執行sThreadLocal.set(new Looper())操作。同時在Looper的構造函數中可以看到會創建一個消息隊列MessageQueue。

public class Looper {
    // 每個線程中的Looper對象其實是一個ThreadLocal,即線程本地存儲(TLS)對象
    private static final ThreadLocal sThreadLocal = new ThreadLocal();
    // Looper內的消息隊列
    final MessageQueue mQueue;
    // 當前線程
    Thread mThread;
    // 。。。其他屬性

    // 每個Looper對象中有它的消息隊列,和它所屬的線程
    private Looper() {
        mQueue = new MessageQueue();
        mRun = true;
        mThread = Thread.currentThread();
    }

    // 我們調用該方法會在調用線程的TLS中創建Looper對象
    public static final void prepare() {
        if (sThreadLocal.get() != null) {
            // 試圖在有Looper的線程中再次創建Looper將拋出異常
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper());
    }
    // 其他方法
}

loop方法是一個阻塞的方法,會持續的訪問MessageQueue的next方法,直到next方法返回message,之後會將調用message對應Handler(msg.target)的dispatchmessage方法,將message發送給Handler去處理。這裏當消息隊列被標記爲退出狀態的時候,他的next方法就會返回null,也就是說,Looper必須退出,否則loop方法就會無限循環下去。

public static void loop() {
        ......
        for (;;) {//死循環
            Message msg = queue.next(); 
            if (msg == null) {
                return;
            }
         ......   
         try {
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
         ......   
    }

2.消息隊列MessageQueue的工作原理

MessageQueue主要有兩個核心函數enqueueMessage方法與next方法,分別對應着讀取與插入操作。MessageQueue的內部是一個單鏈表實現的。

enqueueMessage會向鏈表尾部中插入一個元素,而next函數會從鏈表頭部移出一個元素next方法是一個阻塞方法,當消息隊列中沒有消息就會一直阻塞在這裏,當有消息後,會將消息移出鏈表

3.Handler的工作原理

Handler負責對消息的發送與接收,Handler有兩種創建方式。不過不管是post還是send方式,最終調用的都是sendMessage方法發送廣播。

//第一種:send方式的Handler創建
Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                //如UI操作
                
            }
        };

//第二種:post方式的Handler創建
Handler handler = new Handler();

sendMessage方法會調用Handler對應的MessageQueue的enqueueMessage來將消息添加至消息隊列。此時Looper會通過next方法獲取到這個消息,並調用消息對應Handler的dispatchMessage方法。

Handler發送Message的代碼流程:Handler.sendMessage()->Handler.sendMessageDelayed()->Handler.sendMessageAtTime()->MessageQueue.enqueueMessage()

    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) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

dispatchMessage中,首先,他會檢查Message的callback是否爲null,不爲null就通過handlerCallback來處理消息,Message的callback是一個Runnable對象,實際上就是Handler的post方法所傳遞Runnable參數,handlerCallback的邏輯也很簡單。最終都會調用handleMessage方法去處理message

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

異步消息處理機制的使用

開啓子線程

開啓子線程首先要new一個Thread,並在其內部實現一個Runnable接口,重寫其run函數。一般在android中我們開啓子線程是用子線程來完成耗時操作,比如網絡請求。

new Thread(new Runnable() {
          @Override
          public void run() {
          //耗時操作的邏輯
          }
}).start();

異步消息處理機制

在android中只要在主線程纔可以進行UI操作,但在子線程中,我們可能在獲取網絡數據結束後要更新一些控件的信息,因此Android提供了異步消息處理機制,主要包括Handler(信息的發送者和接收者)Message(信息的載體,可以攜帶少量信息在不同線程交換)MessageQueue(信息隊列),Looper

代碼流程

首先在子線程中創建一個Message對象,併爲它的what字段賦值,賦值後通過sendMessage(message)發送消息。之後在主線程中,創建Handler對象,並實現其內部的handleMessage(Message msg)方法,並根據msg.what去匹配接收到的是哪個Message,並最終更新UI。
在這裏插入圖片描述
圖片來源

一個實例。

 public static final int UPDATE_TEXT =1;
 /*創建Handler實例並重寫handleMessage方法*/
  private Handler handler=new Handler(){
        public  void  handleMessage(Message msg){
            switch (msg.what){
                case UPDATE_TEXT:
                    text.setText("Nice to meet you");
                    break;
                default:
                    break;
            }
        }
    };
/*開啓的子線程*/
new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Message message=new Message();
                        message.what=UPDATE_TEXT;
                        handler.sendMessage(message);
                    }
                }).start();


總結(複習必看)

1.消息機制是android用來實現線程間通信的,最常用的場景就是讓子線程執行一個耗時操作,執行後,需要更新UI控件,這時就需要用消息機制。而UI控件是線程不安全的,所以不能讓UI控件直接在子線程更新。

2.消息機制是指Handler的工作模式。其中包含四個部分,Handler:消息的發出者、執行者;MessageQueue:消息隊列,用來存儲信息;Looper:消息崩,用來取出MessageQueue的信息併發送給消息對應的Handler處理;Message:消息的載體,具有Message.what字段來匹配Message。

3.四個部分的關係

一個線程只能有一個Looper,但可以有多個Handler;MessageQueue中可以存儲多個Message,這些Message可以來自不同的Handler;Looper中只有一個MessageQueue;Handler可以在任意線程發送消息至Handler所綁定的MessageQueue,但Handler只會在所綁定的Looper所在線程對信息進行處理。

4.消息機制工作流程

1)Handler會通過sendMessage方法發出消息並調用所綁定的MessageQueue的enqueueMessage方法將消息放入消息隊列MessageQueue;
2)Looper的loop方法會持續的調用MessagQueue的next方法,此時next方法會返回message,之後調用message所對應的Handler的dispatchMessage方法交由Handler處理,
3)Handler會調用handleMessage方法進行具體操作。

在這裏插入圖片描述
5.ThreadLocal在信息機制的作用及原理

Looper類中存在靜態的ThreadLocal對象,並在每次爲線程創建Looper對象時,都會將Looper對象存入ThreadLocal對象中。ThreadLocal用於幫助Handler找到當前線程對應的Looper,進而找到其MessageQueue,並調用其enqueueMessage方法。

ThreadLocal類爲每個線程創建了一個數組,當使用set函數時,會根據當前線程找到對應的數組,並通過ThreadLocal對象將其存入數組中的特定下標reference+1處。而get函數時同理,也會根據當前線程找到存儲的對應數組,並根據ThreadLocal對象找到數組的對應下標reference+1,並將數值取出。ThreadLocal在不同線程下變更的是不同數組,所以ThreadLocal可以在多個線程中互不干擾的存儲和修改數據。

6.Looper的工作原理與使用

想在子線程創建Handler(此Handler與子線程綁定)並使用,需要首先創建Looper,否則會報錯。Looper創建包括兩個靜態方法:Looper.prepare方法與Looper.loop方法

prepare方法中,會爲當前線程創建一個新的Looper實例,在Looper構造方法中還會創建MessageQueue。

loop方法中,會持續地調用MessageQueue的next方法,直至next方法返回了消息,此方法是個阻塞方法。但當消息隊列被標記位退出狀態時,可以由MessageQueue的next方法返回一個null,來停止這個持續的死循環。

7.MessageQueue的工作原理

MessageQueue內部其實是個單鏈表,主要包含兩個方法:enqueMessagenext方法。enqueueMessage方法會在Handler發送消息後調用並將消息插入鏈表;而**next方法是一個阻塞方法,會持續的去尋找鏈表中是否有元素,並刪除元素。**

8.Handler的工作原理
Handler有兩種創建方式,一種是post一種是send,send方式比較常用,兩種方式最終都會調用sendMessage方法將消息傳送。之後會調用當前handler所匹配的MessageQueue的enqueueMessage方法將消息添加至鏈表中。

當Looper調用消息隊列的next函數返回msg後,會調用msg匹配Handler的dispatchMessage方法交由Handler處理,最終Handler會調用handlemessage進行實際操作。

9.消息機制的具體使用實例

首先在主線程創建一個Handler實例,並重寫其handleMessage方法。開啓一個子線程,並在子線程耗時操作結束後使用Handler實例調用sendMessage發送消息。
在這裏插入圖片描述
10.Message的創建方式:

後兩種比較好,後兩種都是從Message池中返回一個Message實例,可以避免Message的重複創建。

1.Message = new Message();
2.Message = Message.obtain();
3.Message = handler1.obtainMessage();

11.使用Hanlder的postDelay()後消息隊列會發生什麼變化?

postDelay發送的消息並不是延遲一會在發送,而是 發送到MessageQueue後,直接阻塞線程。與MessageQueue的隊首元素根據觸發時間比較,始終讓觸發時間短的在隊首,讓觸發時間長的在隊尾。此時如果隊首就是delay的消息,就會將線程阻塞delay的時間,之後在執行該Message。

參考鏈接

本文中的圖出自:
android的消息處理機制(圖+源碼分析)——Looper,Handler,Message
要點提煉|開發藝術之消息機制

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章