Android--Handler機制及原始碼詳解

喜歡SHA發表於2018-10-05

主要的內容包括Handler的機制以及四個組成部分和原始碼的分析

下面的程式碼分析都是基於Android8.0 - Oreo的原始碼

1. 訊息機制簡介

在應用啟動時,會執行main()方法,main()會建立一個Looper物件,然後開啟一個死迴圈,目的是不斷從訊息佇列MessageQueue裡面取出Message物件並處理。

在Android中使用訊息機制,會優先想到的是Handler。Handler可以輕鬆的將一個任務切換到Handler所在的執行緒去執行。在多執行緒的應用場景中,可以將工作執行緒中需要更新UI的操作資訊傳遞到主執行緒去執行,從而實現工作執行緒更新UI的操作,最終實現非同步訊息的處理。

Handler流程

2. Handler機制模型

訊息機制主要包含HandlerMessageMessageQueueLooper這四個類。

  • Handler:訊息輔助類。主要功能將Message物件傳送到MessageQueue中,同時將自己的引用賦值給Message#target(Handler.sendMessage())。也可以實現handleMessage()方法處理回撥。
  • Message:訊息實體。需要傳遞的訊息也可以傳遞資料。
  • MessageQueue:訊息佇列。**內部實現並不是佇列,而是利用單連結串列去實現因為在插入和刪除資料有優勢。**用於儲存Handler發給來的訊息(Message)以及取出。內部使用單連結串列實現
  • Looper:與執行緒繫結,不止侷限於主執行緒,繫結的執行緒來處理Message。不斷迴圈執行Looper.loop(),從MessageQueue中讀取Message,按分發機制將訊息分發出去給目標處理(將Message發到Handler.dispatchMessage方法去處理)。

3. Handler執行流程

Handler執行流程
工作流程:非同步通訊準備==>訊息入隊==>訊息迴圈==>訊息處理

  1. 非同步通訊準備

    假定在主執行緒建立Handler,則會直接在主執行緒中建立Looper,MessageQueueHandler物件。Looper和MessageQueue物件均屬於其建立執行緒(由主執行緒建立則屬於主執行緒)。建立Looper時會自動建立MessageQueue物件,建立好MessageQueue物件後,Looper自動進入迴圈。Handler自動繫結Looper以及MessageQueue

    Looper物件的建立方法一般通過Looper.prepareMainLooper()Looper.prepare()方法。

  2. 訊息入隊

    工作執行緒通過Handler傳送MessageMessageQueue中。訊息內容一般是UI操作,通過Handler.sendMessage(Message message)Handler.post(Runable r)傳送。加入MessageQueue一般通過MessageQueue.enqueueMessage(Message msg,long when)操作。

  3. 訊息迴圈

    分為訊息出隊訊息分發兩個步驟

    • 訊息出隊:LooperMessageQueue中迴圈取出Message
    • 訊息分發:Looper將取出的Message分發給建立訊息的Handler

    訊息迴圈過程中,MessageQueue為空,則執行緒堵塞

  4. 訊息處理

    Handler接受發過來的Message並處理。

4. Handler使用過程的注意點

  1. 在工作執行緒中建立自己的訊息佇列時必須要呼叫Looper.prepare(),並且在一個執行緒中只可以呼叫一次,然後需要呼叫Looper.loop(),開啟訊息迴圈。

    在開發過程中基本不會呼叫上述方法,因為預設會呼叫主執行緒的Looper,然後一個執行緒中只能有一個Looper物件和一個MessageQueue。

  2. 要注意Handler可能引起的記憶體洩漏(在下面會介紹到為何會引發洩漏)。

    錯誤的寫法:

    private final Handler mHandler = new Handler(){        
        @Override
        public void handleMessage(Message msg) {            
            super.handleMessage(msg);
           }
        };
    複製程式碼

    非靜態的內部類和匿名內部類都會隱式的持有其外部類的引用,而靜態內部類不會持有外部類的引用。

    正確的寫法:

    繼承Handler時候要麼放在單獨的類檔案中,要麼直接使用靜態內部類。

    //需要在靜態內部類中呼叫外部類時,可以直接使用  `弱引用`  進行處理
    private static final class MyHandler extends Handler{
        private final WeakReference<MyActivity> mWeakReference;
        public MyHandler(MyActivity activity){
             mWeakReference = new WeakReference<>(activity);
        }
        @Override
        public void handlerMessage(Message msg){
            super.handlerMessage(msg);
            MyActivity activity = mWeakReference.get();
        }
    }
    //呼叫方法
    private MyHandler mHandler = new MyHandler(this);
    複製程式碼

5. Handler原始碼解析

  1. 建立迴圈器物件(Looper)和建立訊息佇列物件(MessageQueue)

    建立Looper物件主要有兩個方法:Looper.prepareMainLooper()Looper.prepare()

    建立MessageQueue物件方法:建立Looper物件時會自動建立MessageQueue

    // 原始碼位置:../core/java/android/os/Looper.java
    
    final MessageQueue mQueue;
    final Thread mThread;
    //Looper物件建立時會自動建立一個MessageQueue物件。
    private Looper(boolean quitAllowed) {
         mQueue = new MessageQueue(quitAllowed);
         mThread = Thread.currentThread();
        }
    
    //為當前執行緒(子執行緒)建立一個Looper物件 需要在子執行緒中主動呼叫該方法
    public static void prepare() {
            prepare(true);
        }
    
    private static void prepare(boolean quitAllowed) {
        //判斷sThreadLocal是否為null,不為空則直接跑出異常 可以保證一個執行緒只可以呼叫一次prepare方法
        if (sThreadLocal.get() != null) {
              throw new RuntimeException("Only one Looper may be created per thread");
           }
        sThreadLocal.set(new Looper(quitAllowed));
        }
    
    //為主執行緒建立一個Looper物件 該方法會在主執行緒建立時自動呼叫
    public static void prepareMainLooper() {
            prepare(false);
            synchronized (Looper.class) {
                if (sMainLooper != null) {
                    throw new IllegalStateException("The main Looper has already been prepared.");
                }
                sMainLooper = myLooper();
            }
        }
    複製程式碼

    總結:

    1. 建立Looper物件時會自動建立MessageQueue物件

    2. 主執行緒的Looper物件是自動生成的,而子執行緒需要呼叫Looper.prepare()建立Looper物件

      建立主執行緒是呼叫了ActivityThreadmain()方法。

      然後按照流程呼叫了Looper.prepareMainLooper()Looper.loop()。所以主執行緒不需要呼叫程式碼生成Looper物件。

      //原始碼位置: ../core/java/android/app/ActivityThread.java
       public static void main(String[] args) {
           ...
            Looper.prepareMainLooper();
            Looper.loop();
           ...
       }
      複製程式碼
    3. Handler的主要作用是(在主執行緒更新UI),所以Handler主要是在主執行緒建立的

    4. Looper與Thread是通過ThreadLocal關聯的。由於ThreadLocal是與執行緒直接關聯的,參考prepare()

    5. 子執行緒建立Handler物件:無法在子執行緒直接呼叫Handler無參構造方法Handler建立時需要繫結Looper物件 。需要使用HandlerThread

  2. 開啟Looper即訊息迴圈

    建立了Looper和MessageQueue物件後,自動進入訊息迴圈,使用Looper.loop()方法開始訊息迴圈。

    //原始碼位置:../core/java/android/os/Looper.java
    public static void loop(){
        //現獲取Looper例項,保證呼叫loop時已有Looper,否則丟擲異常
        final Looper me = myLooper();
        if (me == null) {
                throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
           }
        //獲取對應Looper例項建立的MessageQueue物件
        final MessageQueue queue = me.mQueue;
        ...
         //開啟訊息迴圈-無限迴圈   
         for (;;) {
                //從MessageQueue取出Message物件
                Message msg = queue.next(); // might block
                //取出訊息為null,則退出迴圈
                if (msg == null) {
                    // No message indicates that the message queue is quitting.
                    return;
                }
             //把Message分發給相應的target
             try {
                    msg.target.dispatchMessage(msg);
                    end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
                } finally {
                    if (traceTag != 0) {
                        Trace.traceEnd(traceTag);
                    }
                }
             //釋放訊息佔據的資源
              msg.recycleUnchecked();
         }
    }
    
    複製程式碼
  3. 建立Handler物件

    建立Handler物件即可以進行訊息的傳送與處理

    //原始碼位置:.../core/java/android/os/Handler.java 
    //Handler預設構造方法
    public Handler() {
            this(null, false);
     }
    
    public Handler(Callback callback, boolean async) {
            if (FIND_POTENTIAL_LEAKS) {
                final Class<? extends Handler> klass = getClass();
                if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                        (klass.getModifiers() & Modifier.STATIC) == 0) {
                    Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                        klass.getCanonicalName());
                }
            }
            //從當前執行緒的ThreadLocal獲取Looper物件
            mLooper = Looper.myLooper();
            if (mLooper == null) {
                throw new RuntimeException(
                    "Can't create handler inside thread that has not called Looper.prepare()");
            }
            //獲取當前Looper的訊息佇列
            mQueue = mLooper.mQueue;
            mCallback = callback;
            //設定訊息是否為非同步處理方式
            mAsynchronous = async;
        }
    
    public Handler(Looper looper, Callback callback, boolean async) {
            mLooper = looper;
            mQueue = looper.mQueue;
            mCallback = callback;
            mAsynchronous = async;
        }
    
    複製程式碼

    Handler的無參構造方法會預設關聯當前執行緒的Looper物件和MessageQueue物件,設定callback回撥方法為null,且訊息處理方式為同步處理。

  4. 建立訊息物件

    Handler傳送Message並且進入MessageQueue迴圈,建立方式分為兩種new Message()Message.obtain()。通常使用Message.obtain()。這種方式有效避免建立重複Message物件

    //建立訊息物件
    Message msg = Message.obtain();
    msg.what = 1;
    msg.obj = "test";
    
    //原始碼位置 .../core/java/android/os/Message.java
        /** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).
        */
    //new Message 方法
    public Message() {
        }
    
    private static final Object sPoolSync = new Object();
    //維護一個Message池,用於複用Message物件
    private static Message sPool;
    //obtain方法 直接從池內獲取Message物件,避免new佔用記憶體
    public static Message obtain() {
            synchronized (sPoolSync) {
                if (sPool != null) {
                    Message m = sPool;
                    sPool = m.next;
                    m.next = null;
                    m.flags = 0; // clear in-use flag
                    sPoolSize--;
                    //直接從池中取出
                    return m;
                }
            }
            //無可複用物件,則重新new獲取
            return new Message();
        }
    複製程式碼
  5. 傳送訊息(Message)

    Handler主要有以下幾種傳送訊息的方式:

    • sendMessage(Message msg)
    • sendMessageDelayed(int what, long delayMillis)
    • post(Runnable r)
    • postDelayed(Runnable r, long delayMillis)
    • sendMessageAtTime(Message msg, long uptimeMillis)

    最終都是會呼叫到sendMessageAtTime(Message msg, long uptimeMillis)然後繼續呼叫到enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)放入MessageQueue

    //原始碼位置:.../core/java/android/os/Handler.java 
    //post方法
    public final boolean post(Runnable r)
        {
           return  sendMessageDelayed(getPostMessage(r), 0);
        }
    public final boolean postAtTime(Runnable r, long uptimeMillis)
        {
            return sendMessageAtTime(getPostMessage(r), uptimeMillis);
        }
    public final boolean postAtTime(Runnable r, Object token, long uptimeMillis)
        {
            return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
        }
    public final boolean postDelayed(Runnable r, long delayMillis)
        {
            return sendMessageDelayed(getPostMessage(r), delayMillis);
        }
    //利用post()方式傳送訊息,需要轉換為Message向下傳遞
    private static Message getPostMessage(Runnable r, Object token) {
            Message m = Message.obtain();
            m.obj = token;
            //將runnable賦值到callback上 以便後續判斷是post還是sendMessage方式傳送的訊息
            m.callback = r;
            return m;
        }
    
    //sendMessage方法
    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物件 
            MessageQueue queue = mQueue;
            //獲取物件為空 丟擲異常
            if (queue == null) {
                RuntimeException e = new RuntimeException(
                        this + " sendMessageAtTime() called with no mQueue");
                Log.w("Looper", e.getMessage(), e);
                return false;
            }
            //物件不為空 呼叫enqueueMessage方法
            return enqueueMessage(queue, msg, uptimeMillis);
        }
    
    //該方法為了 向MessageQueue插入Message
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
            // 把當前的Handler設定為 訊息標記位 即把訊息派發給相對應的Handler例項
            msg.target = this;
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            //呼叫MessageQueue的enqueueMessage方法
            return queue.enqueueMessage(msg, uptimeMillis);
        }
    複製程式碼
    //原始碼位置:..core/java/android/os/MessageQueue.java
    //內部是一個單連結串列有序序列,由 Message.when 作為排序依據,該值為一個相對時間。
    boolean enqueueMessage(Message msg, long when) {
        ...
        synchronized (this) {
                //正在退出 回收Message
                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;
                // p == null判斷當前佇列中是否有訊息,插入訊息作為佇列頭 
                // when == 0||when < p.when 佇列當前處於等待狀態 喚醒佇列
                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;
                        //迴圈到佇列尾部或者出現一個when小於當前Message的when
                        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;
      }
    
    
    複製程式碼

    總結:

    • 傳送訊息時Message.when表示期望該訊息被分發的時間即SystemClock.uptimeMillis() + delayMillisSystemClock.uptimeMills代表自系統開機到呼叫到該方法的時間差。
    • Message.when利用時間差來表達期望事件分發的時間,所以使用的是一個相對時間。
  6. 獲取訊息

    傳送了訊息後,MessageQueue維護了訊息佇列,在Looper中通過loop()不斷獲取Message。通過next()獲取Message.

     //原始碼位置:..core/java/android/os/MessageQueue.java
     Message next(){
     //該引數用於確定訊息佇列中是否有訊息 下一個訊息到來前需要等待的時長
       int nextPollTimeoutMillis = 0;
        for (;;) {
               if (nextPollTimeoutMillis != 0) {
                   Binder.flushPendingCommands();
               }
               //該方法位於native層 若nextPollTimeoutMillis為-1 代表訊息佇列處於等待狀態 阻塞操作
               nativePollOnce(ptr, nextPollTimeoutMillis);
               ...
               synchronized (this) {
                   ...
                    Message msg = mMessages;
                    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;
                           //標記訊息使用狀態 flag |= FLAG_IN_USE
                           msg.markInUse();
                           //返回一條訊息
                           return msg;
                       }
                   } else {
                       // No more messages.
                       nextPollTimeoutMillis = -1;
                   }
                   //訊息正在退出
                   if (mQuitting) {
                       dispose();
                       return null;
                   }
               }
         }
     }
    
    複製程式碼
  7. 分發訊息

    分發訊息到對應的Handler例項並根據傳入的Message做對應的操作

    //原始碼位置:.../core/java/android/os/Handler.java 
    
      public void dispatchMessage(Message msg) {
            //若callback不為空,則代表使用了post(Runnable r)方式傳送了訊息,執行handleCallback方法
            if (msg.callback != null) {
                handleCallback(msg);
            } else {
                //代表使用了sendMessage()方式傳送了訊息,呼叫handleMessage方法
                if (mCallback != null) {
                    if (mCallback.handleMessage(msg)) {
                        return;
                    }
                }
                handleMessage(msg);
            }
        }
    
    //建立Handler例項時複寫 自定義訊息處理方法
    public void handleMessage(Message msg) {
        }
    
    //直接回撥runnable物件的run()
    private static void handleCallback(Message message) {
            message.callback.run();
        }
    複製程式碼

    總結:

    • msg.target.dispatchMessage(msg)msg.target指向的就是對應Handler例項,
    • 訊息分發的優先順序:
      1. Message的回撥方法message.callback.run()
      2. Handler中Callback的回撥方法mCallback,handleMessage(msg)
      3. Handler的預設方法handleMessage()
  8. Message回收

    上面講到了新建Message推薦使用obtain(),因為可以有效的複用訊息,其中裡面複用的就是sPool變數,它是在Message回收的時候進行賦值的。

    //原始碼位置 .../core/java/android/os/Message.java
    /*package*/ boolean isInUse() {
            return ((flags & FLAG_IN_USE) == FLAG_IN_USE);
        }
    
    public void recycle() {
            //正在使用 無法回收
            if (isInUse()) {
                if (gCheckRecycle) {
                    throw new IllegalStateException("This message cannot be recycled because it "
                            + "is still in use.");
                }
                return;
            }
            recycleUnchecked();
        }
    
    void recycleUnchecked() {
            // Mark the message as in use while it remains in the recycled object pool.
            // Clear out all other details.
            //置為使用標記
            flags = FLAG_IN_USE;
            what = 0;
            arg1 = 0;
            arg2 = 0;
            obj = null;
            replyTo = null;
            sendingUid = -1;
            when = 0;
            target = null;
            callback = null;
            data = null;
    
            //將Message放在了列表裡,快取的物件由obtain()拿出來複用
            synchronized (sPoolSync) {
                if (sPoolSize < MAX_POOL_SIZE) {
                    next = sPool;
                    sPool = this;
                    sPoolSize++;
                }
            }
        }
    複製程式碼
  9. Looper退出

    Looper.loop()內部由一個無限迴圈組成,預設情況下不會退出迴圈。需要退出就需要呼叫quit()或者quitSafely()

    //原始碼位置 .../core/java/android/os/Looper.java
        public void quit() {
            mQueue.quit(false);
        }
    
        public void quitSafely() {
            mQueue.quit(true);
        }
    
    //原始碼位置 .../core/java/android/os/MessageQueue.java
    void quit(boolean safe) {
            if (!mQuitAllowed) {
                throw new IllegalStateException("Main thread not allowed to quit.");
            }
    
            synchronized (this) {
                if (mQuitting) {
                    return;
                }
                mQuitting = true;
    
                if (safe) {
                    removeAllFutureMessagesLocked();
                } else {
                    removeAllMessagesLocked();
                }
    
                // We can assume mPtr != 0 because mQuitting was previously false.
                //喚醒等待執行緒
                nativeWake(mPtr);
            }
        }
    
    //直接移除MessageQueue中的所有訊息
     private void removeAllMessagesLocked() {
            Message p = mMessages;
            while (p != null) {
                Message n = p.next;
                //回收未被處理的訊息
                p.recycleUnchecked();
                p = n;
            }
            //由於訊息為null 則return 出無限迴圈
            mMessages = null;
        }
    
    //直接移除未處理的訊息 已經在執行的繼續處理
    private void removeAllFutureMessagesLocked() {
            final long now = SystemClock.uptimeMillis();
            Message p = mMessages;
            if (p != null) {
                //還未處理的Message
                if (p.when > now) {
                    removeAllMessagesLocked();
                } else {
                    Message n;
                    for (;;) {
                        n = p.next;
                        if (n == null) {
                            return;
                        }
                        if (n.when > now) {
                            break;
                        }
                        p = n;
                    }
                    //不接收後續訊息
                    p.next = null;
                    do {
                        p = n;
                        n = p.next;
                        p.recycleUnchecked();
                    } while (n != null);
                }
            }
        }
    複製程式碼

相關文章