關於Handler的這些都沒搞懂,還怎麼去跳槽

前言

做 Android 開發肯定離不開跟 Handler 打交道,它通常被我們用來做主線程與子線程之間的通信工具,而 Handler 作爲 Android 中消息機制的重要一員也確實給我們的開發帶來了極大的便利。

可以說只要有異步線程與主線程通信的地方就一定會有 Handler。
那麼;

  • Handler 的通信機制的背後的原理是什麼?
  • Handler、Thread 和 HandlerThread 的差別?
  • 消息機制 Handler 作用 ?有哪些要素 ?流程是怎樣的 ?
  • Handler 引起的內存泄露原因以及最佳解決方案
  • 使用 Handler 的 postDealy 後消息隊列會有什麼變化?
  • 可以在子線程直接 new 一個 Handler 嗎?怎麼做?
  • Handler 中有 Loop 死循環,爲什麼沒有阻塞 主線程,原理是什麼

本文將持續爲你揭曉

一丶Handler原理

handler 整個流程中,主要有四個對象,handlerMessage,MessageQueue,Looper。 當應用創建的時候,就會在主線程中創建 handler 對象,

Android 中主線程是不能進行耗時操作的,子線程是不能進行更新 UI 的。所以就有了 handler, 它的作用就是實現線程之間的通信。

當應用創 建的時候,就會在主線程中創建 handler對象, 我們通過要傳送的消息保存到 Message 中,handler 通過調用 sendMessage 方法 將 Message 發送到 MessageQueue 中,Looper 對象就會不斷的調用 loop()方法

不斷的從 MessageQueue 中取出 Message 交給 handler 進行處理。從而實現線程 之間的通信。

二丶Handler、Thread 和 HandlerThread 的差別:

1) Handler 線程的消息通訊的橋樑,主要用來發送消息及處理消息。

2) Thread 普通線程,如果需要有自己的消息隊列,需要調用 Looper.prepare()創建 Looper 實例,調用 loop()去循環消息。

3) HandlerThread 是一個帶有 Looper 的線程,在 HandleThreadrun()方法中調用了 Looper.prepare()創建了 Looper 實例,並調用 Looper.loop()開啓了 Loop 循環,循環從消息隊 列中獲取消息並交由 Handler 處理。利用該線程的 Looper 創建 Handler 實例,此 Handler 的 handleMessage()方法是運行在子線程中的。即 Handler 利用哪個線程的 Looper 創建的實例, 它就和相應的線程綁定到一起,處理該線程上的消息,它的 handleMessage()方法就是在那 個線程中運行的,無參構造默認是主線程。

HandlerThread 提供了 quit()/quitSafely()方法退出 HandlerThread 的消息循環,它們分別調用 LooperquitquitSafely 方法,quit 會將消息 隊列中的所有消息移除,而 quitSafely 會將消息隊列所有延遲消息移除,非延遲消息派發出 去讓 Handler 去處理。

HandlerThread 適合處理本地 IO 讀寫操作(讀寫數據庫或文件),因爲本地 IO 操作耗 時不長,對於單線程+異步隊列不會產生較大阻塞,而網絡操作相對比較耗時,容易阻塞後 面的請求,因此HandlerThread 不適合加入網絡操作

三丶消息機制 Handler 作用 ?有哪些要素 ?流程是怎樣的 ?

負責跨線程通信,這是因爲在主線程不能做耗時操作,而子線程不能更新 UI,所以當子線程中進行耗時操作後需要更新 UI時,通過 Handler 將有關 UI 的操作切換到主線程中執行。

具體分爲四大要素:
①Message(消息): 需要被傳遞的消息,消息分爲硬件產生的消息(如按鈕、觸摸)和軟件生成的消息。

MessageQueue(消息隊列): 負責消息的存儲與管理,負責管理由 Handler 發送過來的 Message。讀取會自動刪除消息,單鏈表維護,插入和刪除上有優勢。在其 next()方法中會無限循環,不斷判斷是否有消息,有就返回這條消息並移除

③Handler(消息處理器): 負責 Message 的發送及處理。主要向消息池發送各種消息事件(Handler.sendMessage())和處理相應消息事件(Handler.handleMessage()),按照先進先出執行,內部使用的是單鏈表的結構。

④Looper(消息池): 負責關聯線程以及消息的分發,在該線程下從 MessageQueue 獲取 Message,分發給Handler,Looper 創建的時候會創建一個MessageQueue,調用 loop()方法的時候消息循環開始,其中會不斷調用 messageQueuenext()方法,當有消息就處理,否則阻塞在 messageQueuenext()方法中。當 Looperquit()被調用的時候會調用messageQueuequit(),此時 next()會返回 null,然後 loop()方法也就跟着退出。

四丶Handler 引起的內存泄露原因以及最佳解決方案

泄露原因:
Handler 允許我們發送延時消息,如果在延時期間用戶關閉了 Activity,那麼該 Activity會泄露。 這個泄露是因爲 Message會持有 Handler,而又因爲 Java 的特性,內部類會持有外部類,使得 Activity 會被Handler 持有,這樣最終就導致 Activity 泄露。

解決方案:
Handler 定義成靜態的內部類,在內部持有
Activity 的弱引用,並在 AcitivityonDestroy()中調用 handler.removeCallbacksAndMessages(null)及時移除所有消息。

五丶使用 Handler 的 postDealy 後消息隊列會有什麼變化?

如果隊列中只有這個消息,那麼消息不會被髮送,而是計算到時喚醒的時間,先將 Looper 阻塞,到時間就喚醒它。但如果此時要加入新消息,該消息隊列的對頭跟 delay 時間相比更長,則插入到頭部,按照觸發時間進行排序,隊頭的時間最小、隊尾的時間最大

六丶可以在子線程直接 new 一個 Handler 嗎?怎麼做?

不可以,因爲在主線程中,Activity 內部包含一個 Looper 對象,它會自動管理 Looper,處理子線程中發送過來的消息。而對於子線程而言,沒有任何對象幫助我們維護 Looper 對象,所以需要我們自己手動維護。所以要在子線程開啓 Handler 要先創建 Looper,並開啓 Looper 循環

   //示例代碼
   new Thread(new Runnable(){
            @Override
             public void run() {
                 looper.prepare();
                 new Handler() {
                         @Override 
                         piblic void handlerMessage(Message msg) {
                             super,handleMessage(msg);
                         }
                 }
                 looper.loop();
            }
       }).start();

七丶Handler 中有 Loop 死循環,爲什麼沒有阻塞 主線程,原理是什麼

該問題很難被考到,但是如果一旦問到,100%會回答 不上來。開發者很難注意到一個主線程的四循環居然沒有阻塞住主 線程。

應該從 主線程的消息循環機制 與 Linux 的循環異步等 待作用講起。最後將 handle 引起的內存泄漏,內存泄漏一定是一 個加分項

這裏有簡單的幾個問題拋出來:

1.Looper 死循環爲什麼不會導致應用卡死,會消耗大量資源嗎?
2.主線程的消息循環機制是什麼(死循環如何處理其它事務)?
3.ActivityThread 的動力是什麼?(ActivityThread 執行 Looper 的線程是什麼)
4.Handler 是如何能夠線程切換,發送 Message 的?(線程間通訊)
5.子線程有哪些更新 UI 的方法。
6.子線程中 Toast,showDialog,的方法。(和子線程不能更新 UI 有關嗎)
7.如何處理 Handler 使用不當導致的內存泄露?

1.Looper 死循環爲什麼不會導致應用卡死?

線程默認沒有 Looper 的,如果需要使用 Handler 就必須爲 線程創建 Looper。我們經常提到的主線程,也叫 UI 線程, 它就是 ActivityThreadActivityThread 被創建時就會初 始化 Looper,這也是在主線程中默認可以使用 Handler 的 原因

我們先來看一段代碼:

 new Thread(new Runnable() {
        @Override
        public void run() {
            Log.e("qdx", "step 0 ");
            Looper.prepare();

            Toast.makeText(MainActivity.this, "run on Thread", Toast.LENGTH_SHORT).show();

            Log.e("qdx", "step 1 ");
            Looper.loop();

            Log.e("qdx", "step 2 ");

        }
    }).start();

我們知道Looper.loop();裏面維護了一個死循環方法,所以按照理論,上述代碼執行的應該是 step 0 –>step 1 也就是說循環在Looper.prepare();與Looper.loop();之間。

在子線程中,如果手動爲其創建了Looper,那麼在所有的事情完成以後應該調用quit方法來終止消息循環,否則這個子線程就會一直處於等待(阻塞)狀態,而如果退出Looper以後,這個線程就會立刻(執行所有方法並)終止,因此建議不需要的時候終止Looper

執行結果也正如我們所說,這時候如果瞭解ActivityThread,並且在main方法中我們會看到主線程也是通過Looper方式來維持一個消息循環。

public static void main(String[] args) {
    Looper.prepareMainLooper();//創建Looper和MessageQueue對象,用於處理主線程的消息

    ActivityThread thread = new ActivityThread();
    thread.attach(false);//建立Binder通道 (創建新線程)

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

    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    Looper.loop();

    //如果能執行下面方法,說明應用崩潰或者是退出了...
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

那麼回到我們的問題上,這個死循環會不會導致應用卡死,即使不會的話,它會慢慢的消耗越來越多的資源嗎?

對於線程即是一段可執行的代碼,當可執行代碼執行完成後,線程生命週期便該終止了,線程退出。而對於主線程,我們是絕不希望會被運行一段時間,自己就退出,那麼如何保證能一直存活呢?簡單做法就是可執行代碼是能一直執行下去的,死循環便能保證不會被退出,例如,binder線程也是採用死循環的方法,通過循環方式不同與Binder驅動進行讀寫操作,當然並非簡單地死循環,無消息時會休眠。但這裏可能又引發了另一個問題,既然是死循環又如何去處理其他事務呢?通過創建新線程的方式。真正會卡死主線程的操作是在回調方法onCreate/onStart/onResume等操作時間過長,會導致掉幀,甚至發生ANRlooper.loop本身不會導致應用卡死。
主線程的死循環一直運行是不是特別消耗CPU資源呢? 其實不然,這裏就涉及到Linux pipe/epoll機制,簡單說就是在主線程的MessageQueue沒有消息時,便阻塞在loopqueue.next()中的nativePollOnce()方法裏,此時主線程會釋放CPU資源進入休眠狀態,直到下個消息到達或者有事務發生,通過往pipe管道寫端寫入數據來喚醒主線程工作。這裏採用的epoll機制,是一種IO多路複用機制,可以同時監控多個描述符,當某個描述符就緒(讀或寫就緒),則立刻通知相應程序進行讀或寫操作,本質同步I/O,即讀寫是阻塞的。 所以說,主線程大多數時候都是處於休眠狀態,並不會消耗大量CPU資源。

2.主線程的消息循環機制是什麼?

事實上,會在進入死循環之前便創建了新binder線程,在代碼ActivityThread.main()中:

  public static void main(String[] args) {
  //創建Looper和MessageQueue對象,用於處理主線程的消息
  Looper.prepareMainLooper();

   //創建ActivityThread對象
   ActivityThread thread = new ActivityThread(); 

   //建立Binder通道 (創建新線程)
   thread.attach(false);

   Looper.loop(); //消息循環運行
   throw new RuntimeException("Main thread loop unexpectedly exited");
   }

Activity的生命週期都是依靠主線程的 Looper.loop,當收到不同Message時則採用相應措施:一旦退出消息循環,那麼你的程序也就可以退出了。 從消息隊列中取消息可能會阻塞,取到消息會做出相應的處理。如果某個消息處理時間過長,就可能會影響UI線程的刷新速率,造成卡頓的現象。

thread.attach(false)方法函數中便會創建一個Binder線程(具體是指ApplicationThreadBinder的服務端,用於接收系統服務AMS發送來的事件),該Binder線程通過HandlerMessage發送給主線程。「Activity 啓動過程」

比如收到msg=H.LAUNCH_ACTIVITY,則調用ActivityThread.handleLaunchActivity()方法,最終會通過反射機制,創建Activity實例,然後再執行Activity.onCreate()等方法;

再比如收到msg=H.PAUSE_ACTIVITY,則調用ActivityThread.handlePauseActivity()方法,最終會執行Activity.onPause()等方法。

主線程的消息又是哪來的呢?當然是App進程中的其他線程通過Handler發送給主線程進程

3.ActivityThread 的動力是什麼?

進程 每個app運行時前首先創建一個進程,該進程是由Zygote fork出來的,用於承載App上運行的各種Activity/Service等組件。進程對於上層應用來說是完全透明的,這也是google有意爲之,讓App程序都是運行在Android Runtime。大多數情況一個App就運行在一個進程中,除非在AndroidManifest.xml中配置Android:process屬性,或通過native代碼fork進程

線程 線程對應用來說非常常見,比如每次new Thread().start都會創建一個新的線程。該線程與App所在進程之間資源共享,從Linux角度來說進程與線程除了是否共享資源外,並沒有本質的區別,都是一個task_struct結構體,在CPU看來進程或線程無非就是一段可執行的代碼,CPU採用CFS調度算法,保證每個task都儘可能公平的享有CPU時間片。

其實承載ActivityThread的主線程就是由Zygote fork而創建的進程。

4.Handler 是如何能夠線程切換

其實看完上面我們大致也清楚線程間是共享資源的。所以Handler處理不同線程問題就只要注意異步情況即可。

這裏再引申出Handler的一些小知識點。 Handler創建的時候會採用當前線程的Looper來構造消息循環系統,Looper在哪個線程創建,就跟哪個線程綁定,並且Handler是在他關聯的Looper對應的線程中處理消息的。(敲黑板)

那麼Handler內部如何獲取到當前線程的Looper呢—–ThreadLocalThreadLocal可以在不同的線程中互不干擾的存儲並提供數據,通過ThreadLocal可以輕鬆獲取每個線程的Looper

當然需要注意的是:

①線程是默認沒有Looper的,如果需要使用Handler,就必須爲線程創建Looper。我們經常提到的主線程,也叫UI線程,它就是ActivityThread
ActivityThread被創建時就會初始化Looper,這也是在主線程中默認可以使用Handler的原因。

系統爲什麼不允許在子線程中訪問UI?(摘自《Android開發藝術探索》)

這是因爲Android的UI控件不是線程安全的,如果在多線程中併發訪問可能會導致UI控件處於不可預期的狀態,那麼爲什麼系統不對UI控件的訪問加上鎖機制呢?
缺點有兩個:

①首先加上鎖機制會讓UI訪問的邏輯變得複雜
②鎖機制會降低UI訪問的效率,因爲鎖機制會阻塞某些線程的執行。 所以最簡單且高效的方法就是採用單線程模型來處理UI操作

5.子線程有哪些更新UI的方法

主線程中定義Handler,子線程通過mHandler發送消息,主線程Handler的handleMessage更新UI。 用Activity對象的runOnUiThread方法。 創建Handler,傳入getMainLooperView.post(Runnabler)

runOnUiThread 第一種咱們就不分析了,我們來看看第二種比較常用的寫法。

先重新溫習一下上面說的

Looper在哪個線程創建,就跟哪個線程綁定,並且Handler是在他關聯的Looper對應的線程中處理消息的。(敲黑板)

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

            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    //DO UI method

                }
            });

        }
  }).start();

  final Handler mHandler = new Handler();

  public final void runOnUiThread(Runnable action) {
     if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);//子線程(非UI線程)
     } else {
        action.run();
     }
  }

進入Activity類裏面,可以看到如果是在子線程中,通過mHandler發送的更新UI消息。 而這個Handler是在Activity中創建的,也就是說在主線程中創建,所以便和我們在主線程中使用Handler更新UI沒有差別。 因爲這個Looper,就是ActivityThread中創建的Looper(Looper.prepareMainLooper())

創建Handler,傳入getMainLooper 那麼同理,我們在子線程中,是否也可以創建一個Handler,並獲取MainLooper,從而在子線程中更新UI呢? 首先我們看到,在Looper類中有靜態對象sMainLooper,並且這個sMainLooper就是在ActivityThread中創建的MainLooper

  private static Looper sMainLooper;  // guarded by Looper.class

  public static void prepareMainLooper() {
     prepare(false);
     synchronized (Looper.class) {
         if (sMainLooper != null) {
             throw new IllegalStateException("The main Looper has already been prepared.");
         }
         sMainLooper = myLooper();
     }
  }

所以不用多說,我們就可以通過這個sMainLooper來進行更新UI操作

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

            Log.e("qdx", "step 1 "+Thread.currentThread().getName());

            Handler handler=new Handler(getMainLooper());
            handler.post(new Runnable() {
                @Override
                public void run() {

                    //Do Ui method
                    Log.e("qdx", "step 2 "+Thread.currentThread().getName());
                }
            });

        }
  }).start();

View.post(Runnabler)老樣子,我們點入源碼
//View

/**
 * <p>Causes the Runnable to be added to the message queue.
 * The runnable will be run on the user interface thread.</p>
 *
 * @param action The Runnable that will be executed.
 *
 * @return Returns true if the Runnable was successfully placed in to the
 *         message queue.  Returns false on failure, usually because the
 *         looper processing the message queue is exiting.
 *
 */
  public boolean post(Runnable action) {
     final AttachInfo attachInfo = mAttachInfo;
     if (attachInfo != null) {
         return attachInfo.mHandler.post(action); //一般情況走這裏
     }

     // Postpone the runnable until we know on which thread it needs to run.
     // Assume that the runnable will be successfully placed after attach.
     getRunQueue().post(action);
     return true;
  }


     /**
      * A Handler supplied by a view's {@link android.view.ViewRootImpl}. This
      * handler can be used to pump events in the UI events queue.
      */
     final Handler mHandler;

居然也是Handler從中作祟,根據Handler的註釋,也可以清楚該Handler可以處理UI事件,也就是說它的Looper也是主線程的sMainLooper。這就是說我們常用的更新UI都是通過Handler實現的。

另外更新UI 也可以通過AsyncTask來實現,難道這個AsyncTask的線程切換也是通過 Handler 嗎? 沒錯,也是通過Handler……

6.子線程中Toast,showDialog,的方法
可能有些人看到這個問題,就會想: 子線程本來就不可以更新UI的啊 而且上面也說了更新UI的方法.兄臺且慢,且聽我把話寫完

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

            Toast.makeText(MainActivity.this, "run on thread", Toast.LENGTH_SHORT).show();//崩潰無疑

        }
    }).start();

看到這個崩潰日誌,是否有些疑惑,因爲一般如果子線程不能更新UI控件是會報如下錯誤的(子線程不能更新UI)

所以子線程不能更新Toast的原因就和Handler有關了,據我們瞭解,每一個Handler都要有對應的Looper對象,那麼。 滿足你。

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

            Looper.prepare();
            Toast.makeText(MainActivity.this, "run on thread", Toast.LENGTH_SHORT).show();
            Looper.loop();

        }
  }).start();

這樣便能在子線程中Toast,不是說子線程…? 老樣子,我們追根到底看一下Toast內部執行方式
//Toast

/**
 * Show the view for the specified duration.
 */
  public void show() {
    ``````

     INotificationManager service = getService();//從SMgr中獲取名爲notification的服務
     String pkg = mContext.getOpPackageName();
     TN tn = mTN;
     tn.mNextView = mNextView;

     try {
         service.enqueueToast(pkg, tn, mDuration);//enqueue? 難不成和Handler的隊列有關?
     } catch (RemoteException e) {
         // Empty
     }
 }

在show方法中,我們看到Toast的show方法和普通UI 控件不太一樣,並且也是通過Binder進程間通訊方法執行Toast繪製。這其中的過程就不在多討論了,有興趣的可以在NotificationManagerService類中分析。

現在把目光放在TN 這個類上(難道越重要的類命名就越簡潔,如H類),通過TN 類,可以瞭解到它是Binder的本地類。在Toast的show方法中,將這個TN對象傳給NotificationManagerService就是爲了通訊!並且我們也在TN中發現了它的show方法。

  private static class TN extends ITransientNotification.Stub {//Binder服務端的具體實現類

     /**
      * schedule handleShow into the right thread
      */
      @Override
      public void show(IBinder windowToken) {
         mHandler.obtainMessage(0, windowToken).sendToTarget();
      }


      final Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
             IBinder token = (IBinder) msg.obj;
             handleShow(token);
         }
      };

  }

看完上面代碼,就知道子線程中Toast報錯的原因,因爲在TN中使用Handler,所以需要創建Looper對象。 那麼既然用Handler來發送消息,就可以在handleMessage中找到更新Toast的方法。 在handleMessage看到由handleShow處理。
//Toast的TN類

   public void handleShow(IBinder windowToken) {

            ``````
            mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);

            mParams.x = mX;
            mParams.y = mY;
            mParams.verticalMargin = mVerticalMargin;
            mParams.horizontalMargin = mHorizontalMargin;
            mParams.packageName = packageName;
            mParams.hideTimeoutMilliseconds = mDuration ==
                Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;
            mParams.token = windowToken;
            if (mView.getParent() != null) {
                mWM.removeView(mView);
            }
            mWM.addView(mView, mParams);//使用WindowManager的addView方法
            trySendAccessibilityEvent();
        }
    }

看到這裏就可以總結一下:

Toast本質是通過window顯示和繪製的(操作的是window),而主線程不能更新UI 是因爲ViewRootImplcheckThread方法在Activity維護的View樹的行爲。 Toast中TN類使用Handler是爲了用隊列和時間控制排隊顯示Toast,所以爲了防止在創建TN時拋出異常,需要在子線程中使用Looper.prepare();和Looper.loop();(但是不建議這麼做,因爲它會使線程無法執行結束,導致內存泄露)

Dialog亦是如此。同時我們又多了一個知識點要去研究:Android 中Window是什麼,它內部有什麼機制?

7.如何處理Handler 使用不當導致的內存泄露? 首先上文在子線程中爲了節目效果,使用如下方式創建Looper

    Looper.prepare();
        ``````
    Looper.loop();

實際上這是非常危險的一種做法

在子線程中,如果手動爲其創建Looper,那麼在所有的事情完成以後應該調用quit方法來終止消息循環,否則這個子線程就會一直處於等待的狀態,而如果退出Looper以後,這個線程就會立刻終止,因此建議不需要的時候終止Looper。(【 Looper.myLooper().quit();】)

那麼,如果在Handler的handleMessage方法中(或者是run方法)處理消息,如果這個是一個延時消息,會一直保存在主線程的消息隊列裏,並且會影響系統對Activity的回收,造成內存泄露。

具體可以參考Handler內存泄漏分析及解決

總結一下,解決Handler內存泄露主要2點

1 有延時消息,要在Activity銷燬的時候移除Messages
2 匿名內部類導致的泄露改爲匿名靜態內部類,並且對上下文或者Activity使用弱引用。

總結

想不到Handler居然可以騰出這麼多浪花,與此同時感謝前輩的摸索。

另外Handler還有許多不爲人知的祕密,等待大家探索,下面我再簡單的介紹兩分鐘

HandlerThread
IdleHandler

HandlerThread

HandlerThread繼承Thread,它是一種可以使用Handler的Thread,它的實現也很簡單,在run方法中也是通過Looper.prepare()來創建消息隊列,並通過Looper.loop()來開啓消息循環(與我們手動創建方法基本一致),這樣在實際的使用中就允許在HandlerThread中創建Handler了。

由於HandlerThread的run方法是一個無限循環,因此當不需要使用的時候通過quit或者quitSafely方法來終止線程的執行。

HandlerThread的本質也是線程,所以切記關聯的Handler中處理消息的handleMessage爲子線程。

IdleHandler

/**
 * Callback interface for discovering when a thread is going to block
 * waiting for more messages.
 */
  public static interface IdleHandler {
     /**
      * Called when the message queue has run out of messages and will now
      * wait for more.  Return true to keep your idle handler active, false
      * to have it removed.  This may be called if there are still messages
      * pending in the queue, but they are all scheduled to be dispatched
      * after the current time.
      */
     boolean queueIdle();
 }

根據註釋可以瞭解到,這個接口方法是在消息隊列全部處理完成後或者是在阻塞的過程中等待更多的消息的時候調用的,返回值false表示只回調一次,true表示可以接收多次回調。

具體使用如下代碼

    Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
        @Override
        public boolean queueIdle() {


            return false;
        }
    });

另外提供一個小技巧:在HandlerThread中獲取LooperMessageQueue方法之反射。

因爲Looper.myQueue()如果在主線程調用就會使用主線程looper 使用handlerThread.getLooper().getQueue()最低版本需要23 //HandlerThread中獲取MessageQueue

  Field field = Looper.class.getDeclaredField("mQueue");
        field.setAccessible(true);
        MessageQueue queue = (MessageQueue) field.get(handlerThread.getLooper());

那麼Android的消息循環機制是通過Handler,是否可以通過IdleHandler來判斷Activity的加載和繪製情況(measure,layout,draw等)呢?並且IdleHandler是否也隱藏着不爲人知的特殊功能?

最後

知識點彙總

  • Handler 的通信機制的背後的原理是什麼?
  • Handler、Thread 和 HandlerThread 的差別?
  • 消息機制 Handler 作用 ?有哪些要素 ?流程是怎樣的 ?
  • Handler 引起的內存泄露原因以及最佳解決方案
  • 使用 Handler 的 postDealy 後消息隊列會有什麼變化?
  • 可以在子線程直接 new 一個 Handler 嗎?怎麼做?
  • Handler 中有 Loop 死循環,爲什麼沒有阻塞 主線程,原理是什麼
  • Looper 死循環爲什麼不會導致應用卡死,會消耗大量資源嗎?
  • 主線程的消息循環機制是什麼(死循環如何處理其它事務)?
  • ActivityThread 的動力是什麼?(ActivityThread 執行 Looper 的線程是什麼)
  • Handler 是如何能夠線程切換,發送 Message 的?(線程間通訊)
  • 子線程有哪些更新 UI 的方法。
  • 子線程中 Toast,showDialog,的方法。(和子線程不能更新 UI 有關嗎)
  • 如何處理 Handler 使用不當導致的內存泄露?

Handler 簡單易用的背後藏着工程師大量的智慧,要努力向他們學習。

看完並理解本文可以說你對 Handler 有了一個非常深入且全面的瞭解,應對面試肯定是綽綽有餘了。

更多內容的面試彙總PDF版本,含有BATJ.字節跳動面試專題,算法專題,高端技術專題,混合開發專題,java面試專題,Android,Java小知識,到性能優化.線程.View.OpenCV.NDK。

Android面試大全+視頻教程+學習筆記

順手留下GitHub鏈接,需要獲取相關面試等內容的可以自己去找
https://github.com/xiangjiana/Android-MS
希望2020年 你我都有所收穫
更多完整項目下載。未完待續。源碼。圖文知識後續上傳github。
可以點擊關於我聯繫我獲取

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