Android 面試(五):探索 Android 的 Handler

這是 面試系列 的第五期。本期咱們未來探討一下 Android 異步消息處理線程 —— Handler。前端

往期內容傳遞:
Android 面試(一):說說 Android 的四種啓動模式
Android 面試(二):如何理解 Activity 的生命週期
Android 面試(三):用廣播 BroadcastReceiver 更新 UI 界面真的好嗎?
Android 面試(四):Android Service 你真的能應答自如了嗎?java

開始

Android 的消息機制,也就是 Handler 機制,相信各位都已是爛熟於心了吧。即建立一個 Message 對象,而後藉助 Handler 發送出去,以後在 HandlerhandleMessage() 方法中獲取剛纔發送的 Message 對象,而後在這裏進行 UI 操做就不會出現崩潰了。面試

既然 Handler 操做都爛熟於心,還講這個幹什麼?

嗯,對,在 Android 開發中,咱們確實常常用到它,對於基本代碼流程天然也是滾瓜爛熟,但瞭解它的原理的人卻不是不少,因此面試官一般會考驗你對 Handler 源碼機制的理解,畢竟只有知己知彼,才能百戰不殆嘛。微信

咱們都知道子線程中進行 UI 操做會阻塞主線程,一般是怎麼在子線程更新 UI 的?

  • Handlerapp

  • Activity.runOnUiThread()異步

  • View.post(Runnable r)ide

講講 Handler 機制吧

Handler 主要由如下部分組成。oop

  • Handlerpost

Handler 是一個消息輔助類,主要負責向消息池發送各類消息事件Handler.sendMessage() 和處理相應的消息事件Handler.handleMessage()ui

  • Message

Message 即消息,它能容納任意數據,至關於一個信息載體。

  • MessageQueue

MessageQueue 如其名,消息隊列。它按時序將消息插入隊列,最小的時間戳將被優先處理。

  • Looper

Looper 負責從消息隊列讀取消息,而後分發給對應的 Handler 進行處理。它是一個死循環,不斷地調用 MessageQueue.next() 去讀取消息,在沒有消息分發的時候會變成阻塞狀態,在有消息可用時繼續輪詢。

在 Android 開發中使用 Handler 有什麼須要注意的

首先天然是在工做線程中建立本身的消息隊列必需要調用 Looper.prepare(),而且在一個線程中只能調用一次。固然,僅僅建立了 Looper 還不行,還必須使用 Looper.loop() 開啓消息循環,要否則要 Looper 也沒用。

咱們平時在開發中不用調用是由於默認會調用主線程的 Looper。
此外,一個線程中只能有一個 Looper 對象和一個 MessageQueue 對象。

大概的標準寫法是這樣。

Looper.prepare();
Handler mHandler = new Handler() {
   @Override
   public void handleMessage(Message msg) {
          Log.i(TAG, "在子線程中定義Handler,並接收到消息。。。");
   }
};
Looper.loop();

另一個比較常考察的就是 Handler 可能引發的內存泄漏了。

Handler 可能引發的內存泄漏

咱們常常會寫這樣的代碼。

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

當你這樣寫的時候,你必定會收到編譯器的黃色警告。

In Android, Handler classes should be static or leaks might occur, Messages enqueued on the application thread’s MessageQueue also retain their target Handler. If the Handler is an inner class, its outer class will be retained as well. To avoid leaking the outer class, declare the Handler as a static nested class with a WeakReference to its outer class

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

要解決這樣的問題,咱們在繼承 Handler 的時候,要麼是放在單獨的類文件中,要麼直接使用靜態內部類。當須要在靜態內部類中調用外部的 Activity 的時候,咱們能夠直接採用弱引用進行處理,因此咱們大概修改後的代碼以下。

private static final class MyHandler extends Handler{
        private final WeakReference<MainActivity> mWeakReference;
        
        public MyHandler(MainActivity activity){
            mWeakReference = new WeakReference<>(activity);
        }
        
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            MainActivity activity = mWeakReference.get();
            if (activity != null){
                // 開始寫業務代碼
            }
        }
    }
    
private MyHandler mMyHandler = new MyHandler(this);

其實在咱們實際開發中,不止一個地方可能會用到內部類,咱們都須要在這樣的狀況下儘可能使用靜態內部類加弱引用的方式解決咱們可能出現的內存泄漏問題。

用過 HandlerThread 嗎?它是幹嗎的?

HandlerThread 是 Android API 提供的一個便捷的類,使用它可讓咱們快速地建立一個帶有 Looper 的線程,有了 Looper 這個線程,咱們又能夠生成 Handler,本質上也就是一個創建了內部 Looper 的普通 Thread

咱們在上面提到的子線程創建 Handler 大概代碼是這樣。

new Thread(new Runnable() {
    @Override public void run() {
        // 準備一個 Looper,Looper 建立時對應的 MessageQueue 也會被建立
        Looper.prepare();
        // 建立 Handler 並 post 一個 Message 到 MessageQueue
        new Handler().post(new Runnable() {
            @Override 
            public void run() {
                  MLog.i("Handler in " + Thread.currentThread().getName());
            }
        });
        // Looper 開始不斷的從 MessageQueue 取出消息並再次交給 Handler 執行
        // 此時 Lopper 進入到一個無限循環中,後面的代碼都不會被執行
        Looper.loop();
    }
}).start();

而採用 HandlerThread 能夠直接把步驟簡化爲這樣:

// 1. 建立 HandlerThread 並準備 Looper
handlerThread = new HandlerThread("myHandlerThread");
handlerThread.start();

// 2. 建立 Handler 並綁定 handlerThread 的 Looper
new Handler(handlerThread.getLooper()).post(new Runnable() {
    @Override 
    public void run() {
          // 注意:Handler 綁定了子線程的 Looper,這個方法也會運行在子線程,不能夠更新 UI
          MLog.i("Handler in " + Thread.currentThread().getName());
    }
});

// 3. 退出
@Override public void onDestroy() {
    super.onDestroy();
    handlerThread.quit();
}

其中必須注意的是,workerThread.start() 是必需要執行的。

至於如何使用 HandlerThread 來執行任務,主要是調用 Handler 的 API。

  • 使用 post 方法提交任務,postAtFrontOfQueue() 將任務加入到隊列前端, postAtTime() 指定時間提交任務, postDelayed() 延後提交任務。

  • 使用 sendMessage() 方法能夠發送消息,sendMessageAtFrontOfQueue() 將該消息放入消息隊列前端,sendMessageAtTime() 指定時間發送消息, sendMessageDelayed() 延後提交消息。

HandlerThread 的 quit() 和 quitSafety() 有啥區別?

兩個方法做用都是結束 Looper 的運行。它們的區別是,quit() 方法會直接移除 MessageQueue 中的全部消息,而後終止 MesseageQueue,而 quitSafety() 會將 MessageQueue 中已有的消息處理完成後(再也不接收新消息)再終止 MessageQueue

作不完的開源,寫不完的矯情。歡迎掃描下方二維碼或者公衆號搜索「nanchen」關注個人微信公衆號,目前多運營 Android ,盡本身所能爲你提高。若是你喜歡,爲我點贊分享吧~
nanchen

相關文章
相關標籤/搜索