重看 Android 消息機制 參考

談起Android 消息機制,相信各位會首先想到Handler,Handler是Android 提供給給開發者實現線程間通信的工具。Android的消息機制包含四大內容,ThreadLocal保證每個線程都有自己的消息輪詢器Looper,MessageQueue用來存放消息,Looper負責取消息,最後Handler負責消息的發送與消息的處理。

  • 先來一張腦圖回顧整體知識

ThreadLocal

  • 我們知道,每個Handler 都有其所在線程對應的Looper,查看Handler構造方法
/**Handler 構造方法*/
public Handler(Callback callback, boolean async) {
      .......
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
      ......
    }
/** Looper 中 sThreadLocal 聲明*/    
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
/** Looper 中 myLooper方法*/ 
public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }    
/** Looper 中 prepare方法*/ 
private static void prepare(boolean quitAllowed) {
       ....
        sThreadLocal.set(new Looper(quitAllowed));
    }    
  • 通過以上源碼,可以知道,Looper.myLooper()獲取不到Looper則會拋異常,所以創建Handler之前都要調用一下Looper.prepare方法,也就是在該方法中新建了Looper並存放到ThreadLocal中。這裏就會產生一個疑問,ThreadLocal能保證每個線程有自己對應的Looper?沒錯,它就真能保證,接下來就看看什麼是ThreadLocal。

什麼是ThreadLocal

  • ThreadLocal是一個線程內部數據存儲類,但存放數據並不是它實現的,它只是幫助類,真正存放數據的是ThreadLocalMap。

  • 先看一個簡單的例子

public class Test {

    static ThreadLocal<String> name =new ThreadLocal<>();

    public static void main(String[] args) {

        new Thread(new Runnable() {
            @Override
            public void run() {
                name.set("xiaoming");
                System.out.println("---------------"+name.get()+"-------------------");
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {

     System.out.println("---------------"+name.get()+"-------------------");
            }
        }).start();
    }
}

### 運行結果
> Task :Test.main()
---------------xiaoming-------------------
---------------null-------------------
  • 上面例子當中,兩個線程訪問的都是一個ThreadLocal對象,但是第二個線程沒有設置初始值,則獲取爲null,也就可以說明每個線程操作的是自己對應的一份數據,雖然都是從ThreadLocal的get方法獲取,但是get方法則是獲取對應線程的ThreadLocal.ThreadLocalMap來獲取值。

ThreadLocal分析

ThreadLocal的set方法

/**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    
     /**
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    
    /**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
  • 通過以上代碼,代碼層面首先獲取當前線程,然後獲取
    ThreadLocalMap,如果存在,則獲取當前線程的ThreadLocalMap;如果不存在則根據當前線程和當前需要存入的數據新建ThreadLocalMap來存放線程內部數據,也就是當前ThreadLocal作爲key,而存儲的值最爲value來存儲。

ThreadLocal的get方法

 /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
    
    /**
     * Variant of set() to establish initialValue. Used instead
     * of set() in case user has overridden the set() method.
     *
     * @return the initial value
     */
    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }
    
    protected T initialValue() {
        return null;
    }
  • 從以上代碼,ThreadLocal的get方法根據當前線程來獲取對應的ThreadLocalMap,如果獲取不到,說明還沒有創建,由createMap方法來創建ThreadLocalMap,initialValue方法則設置了value的初始值爲null,也呼應前面的例子打印結果。

ThreadLocal原理

  • Thread類有一個類型爲ThreadLocal.ThreadLocalMap的成員變量threadLocals,如果你瞭解Java內存模型,threadLocals的值都是new出來的話,很容易明白threadLocals是存放在堆內存中的,而每一個線程只是在堆內存中存放了自己的threadLocals,也就是每個線程本地內存(邏輯上),物理上本地內存只是在堆內存中佔有一塊區域,每個線程只玩自己對應的threadLocals,各個線程的對應ThreadLocal互不干擾,這也就實現了各個線程間數據的隔離,也就是每個Handler所在線程都有其對應的Looper對象

  • Thread類中 threadLocals 聲明

/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
  • 簡單來說就是數據複製很多份存放在堆內存,各個線程獲取自己對應的那份數據

  • 這個可以舉一個共享汽車的例子,假如剛開始共享汽車試運行,大街上只有一輛,大家都搶着去開,這就會出現問題,而後來發展普及,每輛車複製迅速生產,滿大街都是共享汽車,每個人都可以通過專屬二維碼開對應共享汽車,這裏開車人就對應線程,大家互不干擾,共享汽車就對應ThreadLocals,而大街就相當於堆內存。

ThreadLocalMap

  • ThreadLocal中真正存放數據的是ThreadLocalMap,他的內部實現是一個環形數組來存放數據,具體分析可以查看以下文章,這裏就不在進行展開了。
  • ThreadLocal源碼解讀

MessageQueue消息隊列工作原理

  • MessageQueue字面意思是消息隊列,而他的實現則不是消息隊列,它的內部實現數據結構爲單鏈表,單鏈表在頻繁插入刪除方面是有優勢的,鏈表的插入刪除操作對應消息的存儲和取出,方法分別對應enqueueMessage和next方法

存放消息enqueueMessage

  • 查看Handler的源碼,很容易發現發消息的方法最終都是調用了sendMessageAtTime方法,uptimeMillis爲系統開機時間加上設置消息的延時時間,Handler的enqueueMessage方法將Message的Target設爲當前Handler,存放消息則調用了MessageQueue的enqueueMessage方法。
/** Handler的sendMessageAtTime方法*/
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        ......
        return enqueueMessage(queue, msg, uptimeMillis);
    }
/** Handler的enqueueMessage方法*/
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
       msg.target = this;
       .......
        return queue.enqueueMessage(msg, uptimeMillis);
    }
  • 接着存放消息看到MessageQueue的enqueueMessage方法
/** MessageQueue的enqueueMessage方法*/
boolean enqueueMessage(Message msg, long when) {
        ......
        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
  • 通過以上源碼,enqueueMessage邏輯主要爲單鏈表的插入操作,如果鏈表中沒有消息,或者當前存入消息延時爲零,又或者當前存入消息延時小於鏈表P節點的延時,則將當前消息插入到鏈表的頭節點,否則遍歷鏈表中的每個節點,找延時小於當前消息的節點存入消息。話句話說,單鏈表裏面消息是按Message的觸發時間順序排序的。

取消息 next

  • 接着看MessageQueue取消息的方法next
Message next() {
        ......
        //省略部分代碼
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

            //省略部分代碼   
            } 
            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }
  • 通過以上代碼,nextPollTimeoutMillis字段是關鍵,它代表next在獲取下一個消息時需要等待的時長,他的取值有三種情況:
  1. 當nextPollTimeoutMillis小於零,表示消息隊列中無消息,會一直等待下去
  2. 當nextPollTimeoutMillis等於零,則不會等待,直接出了取出消息
  3. 當nextPollTimeoutMillis大於零,則等待nextPollTimeoutMillis值的時間,單位是毫秒
  • 通過對nextPollTimeoutMillis的瞭解,next方法是如何等待呢?換個詞可能更準確,應該叫阻塞,這裏注意到next方法循環中的nativePollOnce(ptr, nextPollTimeoutMillis)方法,它的實現在native層,可以實現阻塞的功能,具體原理是使用epoll,它是一種linux的I/O事件通知機制,I/O輸入輸出對象使用的是管道(pipe),具體native層分析請看Gityuan大佬的分析文章Android消息機制2-Handler(Native層)
private native static void nativeWake(long ptr);

private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/

  • 到此,next方法的邏輯就很清晰了,開始nextPollTimeoutMillis的值是等於零的,獲取消息過程就不會受到nativePollOnce方法的阻塞,然後判斷取出的消息是否延時,有延時則計算nextPollTimeoutMillis進入下一循環進入nativePollOnce方法阻塞,否則返回取出的消息,有阻塞肯定就有喚醒,這個喚醒的方法就是nativeWake(long ptr)方法,它的實現也在native層,它的調用在我們前面分析enqueueMessage方法邏輯有出現,當有消息進入消息隊列,如果當前線程正在被阻塞,調用nativeWake方法,nativePollOnce就會立即返回,取消阻塞,這樣循環取到沒有延時的消息,則直接返回消息;如果沒有消息,nextPollTimeoutMillis等於 -1,繼續阻塞狀態

  • 經過前面的分析,消息插入鏈表是sendMessageAtTime方法觸發的,而接下來就會有一個疑問,那又是誰調用 next() 方法取消息呢?沒錯,就是接下來要了解的Looper

Looper 工作原理

  • Looper在Android消息機制中是消息輪詢器的作用,他會不斷到MessageQueue中去取消息,取消息根據前面next 方法分析,如果阻塞,則說明沒有消息
  • 先看Looper源碼註釋中有一段示例代碼
/* This is a typical example of the implementation of a Looper thread,
  * using the separation of {@link #prepare} and {@link #loop} to create an
  * initial Handler to communicate with the Looper. */
  
    class LooperThread extends Thread {
       public Handler mHandler;
  
        public void run() {
           Looper.prepare();
  
            mHandler = new Handler() {
                public void handleMessage(Message msg) {
                    // process incoming messages here
                }
           };
  
            Looper.loop();
        }
     }
  • 由example代碼所示,使用 Handler 之前調用了Looper.prepare(),如下代碼所示,就是在ThreadLocal中存放當前線程的Looper對象,在Looper構造方法中創建了MessageQueue
 public static void prepare() {
        prepare(true);
    }

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

 private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }    
  • 接着創建完Handler之後,又調用Looper.loop()方法,如下
 /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;

        //省略部分代碼.....

        for (;;) {
            Message msg = queue.next(); // might block
            
            //省略部分代碼.....
            
            try {
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
               //省略部分代碼。。。。
            }
            
            //省略部分代碼。。。。
        }
    }
  • 首先看到第一行myLooper(),前面在分析ThreadLocal已經瞭解過,myLooper就是獲取ThreadLocal獲取我們存儲的Looper對象,如果獲取不到就會報異常,提示我們我們沒有調用Looper.prepare(),這也就是子線程使用Handler必須調用Looper.prepare()的原因。是不是有恍然大悟的感覺。然後就是就是根據構造方法創建的MessageQueue來獲取消息queue.next(),該方法經過前面分析在沒有消息或者消息延時時間還沒到是阻塞的;獲取到消息後,根據msg.target.dispatchMessage(msg)調用的便是Handler的dispatchMessage方法(前文分析中msg.target的值爲當前Handler)。

主線程Looper.prepare()

  • 經過前面的分析,你也許會有一個疑問,在Android使用Handler怎麼不用調用Looper.prepare()方法?
  • 解下來我們看到Android的主線程ActivityThread的main方法,嚴格來說,ActivityThread並不是線程類,但是Android主線程肯定是存在的,只是主線程在ActivityThread的 main 方法中創建,並在該方法調用了Looper.prepareMainLooper() 方法和Looper.loop() 方法,所以我們在Android 主線程就可以直接使用Handler
/**ActivityThread 的 main 方法*/
public static void main(String[] args) {
        //省略部分代碼....
        Looper.prepareMainLooper();
        //省略部分代碼....
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);
        //省略部分代碼....
        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();
       
    }
    //省略部分代碼....
}
/**Looper 的 prepareMainLooper 方法*/
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

Handler 工作原理

  • 前面已經瞭解過Handler發送消息的sendMessageAtTime方法,接着我們來看看Handler的dispatchMessage方法
/**
     * 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);
        }
    }

 private static void handleCallback(Message message) {
        message.callback.run();
    }    
  • 這裏邏輯就很簡單了,如果發送的消息設置了Runnable類型的callback對象,則調用他的run方法,沒有則判斷是否設置了Handler.Callback,設置則調用Handler.Callback接口的handleMessage方法,否則調用Handler空實現方法handleMessage。

Looper.loop()死循環,爲什麼不會導致主線程發生ANR?

  • 根據前面的分析,Looper.loop()的方法獲取不到數據,則會阻塞,這個阻塞和卡死是兩回事,阻塞是Linux pipe/epoll機制文件讀寫的等待,等待及休眠,則會釋放佔用CPU的資源,而我們開發遇見的卡死一般都是在主線程做了太多耗時操作,Activity 5s,BroadcastReceiver 10s和Service 20s未響應引起的ANR,具體背後分析還請看Gityuan的知乎解答Android中爲什麼主線程不會因爲Looper.loop()裏的死循環卡死?

參考

書籍

  • 《Android開發藝術探索》

鏈接

About me

blog:

mail:

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