Android Handler淺析

Handler 相信每個作 Android 開發的小夥伴都很是熟悉了,最經常使用的場景就是在子線程中進行數據操做而後經過 Handler 消息機制通知到 UI 線程來更新 UI ,地球人都知道在子線程中更新 UI 通常狀況下都會報錯。是吧!咱用的賊溜,各類姿式發送 Handler 消息都熟練掌握,可是若是這時候出去面試被問到「 Handler 原理」,「消息是怎麼從子線程發送到主線程的」等等 Handler 底層的實現,就懵逼了。java

雖然網上關於分析 Handler 的博客文章很是多,已經有不少大佬分析的很是透徹清楚了,但我這裏仍是想在看過大佬們的文章後本身再寫一篇,一方面是讓本身加深理解,另外一方面就是想分享所學知識(分享會讓知識變的更多)。android

看了不少大佬去大廠面試的面經,Handler幾乎是必問的,因此咱們更加必須知其因此然了。git

1、Handler是啥

1.1 一句話描述 Handler

簡單來講Handler是結合線程消息隊列來發送和處理 MessageRunnable 對象來實現線程間通訊的工具。面試

1.2 典型實例

先來一塊兒看一個關於 Handler 使用的典型實例(實例來源 Gityuan 博客)c#

class LooperThread extends Thread {
    public Handler mHandler;

    public void run() {
        Looper.prepare(); 

        mHandler = new Handler() {  
            public void handleMessage(Message msg) {
                //TODO 定義消息處理邏輯. 
            }
        };

        Looper.loop();  
    }
}
複製代碼

在線程中聲明一個Handler必須按如下順序三步走:app

  1. 調用 Looper.prepare() 。
  2. new Handler() 。
  3. 調用 Looper.loop() 。

以上三步曲我稱之爲:Looper 肉夾饃 (手動狗頭)異步

這裏產生一個問題了:async

Question 1: 建立 Handler 以前爲啥必定要先調用 Looper.prepare() 呢?ide

咱們帶着這個問題繼續往下看。工具

2、Handler 工做原理

提及 Handler 那就必需要了解 Android 的消息機制了, Android 中有大量的交互場景是經過消息機制來驅動的,如咱們最熟悉不過的 Activity 的生命週期等等。而 Handler 就是 Android 消息機制的重要組成部分。

咱們先來看一張圖:

image

圖中描述的是消息是怎樣從子線程流到主線程從而完成線程間通訊的大概流程。Android 整個消息機制主要由Handler,Looper,MessageQueue,Message四個部分組成。這裏先簡單介紹一下這四位具體職責:

  • Handler : 負責發送消息和接收消息並進行處理。

  • MessageQueue : 消息隊列,負責消息存儲與管理。

  • Looper : 負責關聯 Handler 當前所處線程,進行消息分發,經過不斷循環實現將消息從消息隊列取出,而後分發給對應的消息處理者。

  • Message : 消息載體。

2.1 原理解析

看完上圖腦海中應該對 Android 的消息機制已經有了一個大概的概念了,下面咱們開始從源碼來看 Android 的消息機制具體實現。這裏就以上圖中的主線程和子線程通訊爲思路。

先來看上圖對應的代碼實例

public class MainActivity extends AppCompatActivity {
    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            //todo UI線程接收消息並處理
            Log.d("MainActivity","handleMessage....");
        }
    };
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        new Thread(){
            @Override
            public void run() {
                super.run();
                //子線程發送消息
                handler.sendMessage(new Message());
            }
        }.start();
    }
}
複製代碼

2.1.1 建立 Handler

主( UI )線程中實例化一個 Handle 對象。咱們跟到源碼中看一下 Handle()作了什麼事情。

  • Handle 默認構造方法
public Handler() {
    this(null, false);
}
複製代碼
  • 默認構造方法最終會走到 Handler(Callback callback, boolean async)
public Handler(Callback callback, boolean async) {

    ......
    //獲取當前Looper對象
    mLooper = Looper.myLooper();
    //檢查當前線程Looper是否存在
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
   ......
}
複製代碼

源碼解析:

在該構造方法中,每次實例化 Handler 都會檢查當前線程是否已綁定 Looper ,若是沒有則會拋出異常提示你沒有調用 Looper.Prepare()。看到這裏【1.2】的 Question 1`是否是就有答案了。

Question 1 正解:

在建立 Handler 對象以前必需要先建立 Looper 而且將 Looper 對象綁定到當前線程,不然在實例化 Handler 時會報異常。

2.1.2 建立 Looper

那麼 Looper.Prepare() 方法裏又是怎麼建立 Looper 對象並綁定到當前線程的呢?跟進源碼看一下。

public static void prepare() {
    prepare(true);
}

private static void prepare(boolean quitAllowed) {
    //檢查當前線程是否已有 Looper,每一個線程只容許執行一次該方法,第二次執行時線程的TLS已有數據,則會拋出異常。
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    //建立Looper對象,並保存到當前線程的TLS區域
    sThreadLocal.set(new Looper(quitAllowed));
}
複製代碼

源碼解析:

  • 調用無參 prepare() 方法默認會調用 prepare(true)

prepare(boolean quitAllowed) 方法參數 quitAllowed 表示是否當前 Looper 是否可退出,傳 true 表示可退出,false 爲不可退出。

  • prepare(boolean quitAllowed) 方法中會先檢查當前線程是否已經有 Looper ,若是有則會拋出異常,也就意味着每一個線程中 Looper.Prepare()方法只能夠調用一次!

這裏咱們就知道了,Looper 和當前線程是一一對應的關係,一個 Looper 只能關聯一個線程。

  • 檢查完當前線程是否已經有 LoopersThreadLocal.set(new Looper(quitAllowed)) 就開始建立一個 Looper 對象並保存到當前線程的TLS(關於 ThreadLocal 請自行查閱資料)區域,這樣就完成了 Looper 對象的建立和關聯到當前線程。

Looper.Prepare()調用後 Looper 建立好了,咱們再回到 Handler 構造方法中:

Looper.myLooper()方法

public static @Nullable Looper myLooper() {
       return sThreadLocal.get();
 }
複製代碼

源碼解析: 這個方法很簡單就是去當前線程的中的 TLS 區域 Get Looper 對象,沒錯就是 Looper.Prepare()方法中sThreadLocal.set(new Looper(quitAllowed)) Set 進去的。

2.1.3 建立 MessageQueue

最後 mQueue = mLooper.mQueue 會獲取 Looper 對象的消息隊列,若是你剛纔從 prepare(boolean quitAllowed) 方法中繼續跟進 new Looper(quitAllowed)方法中你就會發現:

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
複製代碼

建立 Looper 對象時會爲當前 Looper 對象建立一個消息隊列 new MessageQueue(quitAllowed)

2.1.4 強行Question 2

不知道細心的你發現了沒,咱們的圖一的代碼實例中是否是少了點什麼。
沒錯,少了肉夾饃!

image

吶!Question 2來啦:
不是反覆強調建立 Handler 以前要先調用 Looper.Prepare()建立 Looper 嗎?那圖一的代碼實例中主線程並無調用啊,直接就建立了 Handler ,你這代碼確定會拋這個異常:

RuntimeException( "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
複製代碼

悄咪咪告訴你,若是你在圖一的代碼實例中建立Handler 以前調用 Looper.Prepare()又會拋出如下異常哦:

RuntimeException("Only one Looper may be created per thread");
複製代碼

image

答案在 ActivityThread.main() 中。

ActivityThreadmain()方法就是整個APP的入口,也就是咱們一般所講的主線程UI線程。 但他實際上並非一個線程,ActivityThread 並無繼承 Thread 類,咱們能夠把他理解爲主線程的管理者,負責管理主線程的執行,調度等操做,能夠看下這個類源碼的註釋。

/** * This manages the execution of the main thread in an * application process, scheduling and executing activities, * broadcasts, and other operations on it as the activity * manager requests. * * {@hide} */
public final class ActivityThread extends ClientTransactionHandler {
    ......
}
複製代碼

好了,咱們來看下 ActivityThread.main() 一探究竟(這裏只貼關鍵代碼了)。

public static void main(String[] args) {
 
        .......

        Looper.prepareMainLooper();

        .......
        
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        .......

        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
複製代碼

源碼解析:

  1. Looper.prepareMainLooper();
    此方法就是爲咱們的主線程建立了 Looper,內部實現調用了 prepare(false)方法。傳 false 表示當前 Looper不可退出。
    /** * Initialize the current thread as a looper, marking it as an * application's main looper. The main looper for your application * is created by the Android environment, so you should never need * to call this function yourself. See also: {@link #prepare()} */
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
    複製代碼

以上,就是Question 2的正解所在,Android 程序入口 ActivityThread.main() 中調用 Looper.prepareMainLooper(); 爲主線程建立了 Looper,因此咱們的主線程能夠直接建立 Handler

  1. Looper.loop();
    和【1.2】中的實例同樣,標準的 Looper 肉夾饃寫法,這個方法幹啥用的,後面再說。

2.1.5 小結

OK,到這裏,Handler,Looper,MessageQueue 都已經準備就緒,圖一的流程差很少能夠跑起來了。咱們總結一下重點:

  1. 初始化 Handler 會檢測當前線程 Looper 是否存在,沒有則會拋出異常。
  2. 基於第一點,因此每次建立 Handler 以前必須先調用 Looper.Prepare() 建立 Looper
  3. Looper 能夠理解爲當前線程的消息調度者,負責消息分發。和當前線程的關聯綁定經過 sThreadLocal.set(new Looper(quitAllowed)); 實現。
  4. Looper 和當前線程是一對一的關係, Looper.Prepare()方法中有進行校驗,重複建立 Looper 綁定會拋出異常。
  5. LooperMessagQueue 也是一對一的關係,構造 Looper 對象時會爲其建立一個對應的 MessagQueue 對象。

2.2 消息發送 - Handler

Handler 消息發送實際上是一個消息入隊的過程,調用 Handler 相應的發送消息方法將當前消息添加到 MessageQueue 消息隊列,最終由 Looper 負責進行消息分發。
Handler 爲咱們提供了一系列的消息發送方法:

public final boolean sendMessage(Message msg){
    return sendMessageDelayed(msg, 0);
}

public final boolean sendEmptyMessage(int what){
    return sendEmptyMessageDelayed(what, 0);
}

public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageDelayed(msg, delayMillis);
}

public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageAtTime(msg, uptimeMillis);
}
複製代碼

發送消息的方法還有不少就不一一列出來了,全部發送消息的方法最終都是調用到 enqueueMessage 方法中:

image

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
複製代碼

該方法實現最終調用的是queue.enqueueMessage(msg,uptimeMillis)方法即 MessageQueue 添加消息到消息隊列中。

2.3 消息管理與存儲 - MessageQueue

前面提到 Handler 全部發送的消息最終都經過 enqueueMessage 加入到 MessageQueue 消息隊列中,因此消息的存儲與管理由 MessageQueue 來負責。

2.3.1 消息入隊

消息入隊由 enqueueMessage(msg,uptimeMillis) 方法負責:

boolean enqueueMessage(Message msg, long when) {
    //驗證Message 是否有target,這裏的target就是對應的handler
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }

    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 {
            //將消息按時間順序插入到MessageQueue。通常地,不須要喚醒事件隊列,除非
            //消息隊頭存在barrier,而且同時Message是隊列中最先的異步消息。
            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;
}
複製代碼

消息入隊順序是按照 Message 觸發時間 long when入隊的,有新消息加入時,會循環遍歷當前消息隊列,對比消息觸發時間,直到找到當前消息合適的插入位置,以此來保證全部消息的觸發時間順序。

2.3.1 消息出隊

MessageQueuenext 方法負責從消息隊列中取出一條消息。

Message next() {

    .......
    
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }

        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            // 嘗試檢索下一條消息。若是找到,則返回。
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                ////當消息Handler爲空時,查詢MessageQueue中的下一條異步消息msg,則退出循環。
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    //當異步消息觸發時間大於當前時間,則設置下一次輪詢的超時時長
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // 獲取一條消息,並返回
                    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; //成功地獲取MessageQueue中的下一條即將要執行的消息
                }
            } else {
                //沒有消息
                nextPollTimeoutMillis = -1;
            }

            //消息正在退出,返回null
            if (mQuitting) {
                dispose();
                return null;
            }
         }
         
        //IdleHandler相關內容
        .......
    }
}
複製代碼

2.4 消息分發 - Looper

有人( Handler )在子線程把消息發到我(MessageQueue)這裏來了,我負責把消息存起來,而且告訴你們存儲(enqueueMessage)消息和提取(next)消息的方法 ,那麼誰來負責把我這裏的消息分發出去而且告訴主線程的人(Handler)呢?

幹這個活的就是咱們的 Looper 了!

還記得大明湖畔的的 Looper 肉夾饃 麼!
如今咱們就來看一下 Looper 肉夾饃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 (;;) {
        //循環遍歷,不斷從MessagQueue中獲取Messag
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }

        ......
        
        try {
            //分發消息到對應的msg.target
            msg.target.dispatchMessage(msg);
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
       ......

        msg.recycleUnchecked();
    }
}
複製代碼

源碼解析: 調用 loop() 方法後,方法內部會開啓一個死循環,經過調用 MessageQueue 消息出棧方法 next() 獲取 Message,而後調用 msg.target.dispatchMessage(msg); 將消息分發到 Message 對應的 target

msg.target 就是發送該消息的 Handler 對象。

2.5 消息接收 - Handler

loop() 方法中調用 msg.target.dispatchMessage(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;
            }
        }
        //回調到 Handler 的 handleMessage 方法
        handleMessage(msg);
    }
}
複製代碼

源碼解析:
dispatchMessage(msg) 方法內部調用了 handleMessage(msg)方法,這個方法用過 Handler 的應該都很是熟悉了,它是 Handler 的一個空實現方法,通常在建立 Handler 的線程重寫此方法,就能夠回調到子線程發出的消息了。

/** * Subclasses must implement this to receive messages. */
public void handleMessage(Message msg) {
}
    
複製代碼

3、 總結

  1. Looper 肉夾饃 職責:

    • 爲當前線程建立 Looper 對象,並關聯當前線程。
    • 爲當前線程建立 Handler 對象。
    • 開啓循環,不斷從 MessageQueue 消息隊列中取消息,取到後分發給 Message 對應的 target
  2. 整個消息從子線程流轉到主線程流程:

    • 主線程使用 Looper肉夾饃
    • 子線程調用 Handler 相應發送消息方法,最終經過 enqueueMessage 將消息加入消息隊列 MessageQueue
    • Looper 循環從 MessageQueue 獲取到消息,調用當前消息對應的 targetdispatchMessage(msg) 方法。
    • dispatchMessage(msg) 回調 handleMessage(msg) 方法,消息即從子線程流轉到重寫 handleMessage(msg)方法所在線程。
  3. Android 主線程默認會使用 Looper 肉夾饃(詳見 ActivityThread.main()),所以 Android 主線程中只需建立 Handler 對象便可。

  4. 主線程 Looper 不能夠退出,所以 ActivityThread.main() 方法中調用的 Looper.prepareMainLooper(); 方法中 prepare(false); 傳參是 false。 若是強行退出主線程 Looper 會拋出如下異常:

Caused by: java.lang.IllegalStateException: Main thread not allowed to quit.
  at android.os.MessageQueue.quit(MessageQueue.java:415)
  at android.os.Looper.quit(Looper.java:240)
複製代碼

緣由是:Android App 主進程依賴消息機制驅動,主線程消息循環退出了,那麼 App 也會退出。

4、問題思考

  1. Looper 中開啓了一個死循環來從 MessageQueue 中取消息,並且沒有單獨開線程,爲何不會形成 Android 主線程卡死 ANR?
    問題答案能夠看 Gityuan 大佬的回答

5、參考資料

Android消息機制1-Handler(Java層)
Handler 都沒搞懂,拿什麼去跳槽啊?

相關文章
相關標籤/搜索