Android之Handler淺析

Android之Handler淺析

Handler相信每一個從事Android開發的小夥伴都很是熟悉了, 最經常使用的場景就是在子線程中進行數據操做而後經過handler消息機制通知到UI線程來更新UI,地球人都知道在子線程中更新UI,通常狀況下都會報錯。往往出去面試被問到「handler原理」,「消息是怎麼從子線程發送到主線程的」等等handler底層的實現,就懵逼了。面試

雖然網上關於分析handler的博客問丈夫很是多,已經有不少大佬分析的很是清晰了。這裏分析主要是爲了讓本身加深理解,另外一方面就是想分享所學知識。框架

Android消息循環流程圖以下所示:ide

主要涉及的角色以下所示:oop

  • Message:消息。
  • MessageQueue: 消息隊列,負責消息的存儲於管理,負責管理由handler發過來的Message,讀取會自動刪除消息,單鏈表維護,插入和刪除上有優點。 在其next()方法中會無限循環,不短判斷是否有消息,有就返回這條消息並移除。
  • Looper: 消息循環器,負責關聯線程以及消息的分發,在該線程下從MessageQueue獲取Message,分發給handler,Looper建立的時候會建立一個MessageQueue,調用loop()方法的時候消息循環開始,其中會不斷調用MessageQueue的next()方法,有消息就處理,不然就阻塞在MessageQueue的next()方法中,當Looper的quit()被調用的時候會調用MessageQueue的quit(),此時的next()會返回null,而後loop()方法也就跟着退出。
  • Handler:消息處理器,負責發送並處理消息,面向開發則,提供API,並隱藏背後實現的細節。

整個消息循環流程仍是比較清晰的,具體來講:post

  • 1.Handler經過sendMessage()發送消息Message到隊列MessageQueue.
  • 2.Looper經過loop()不斷提取觸發條件的Message,並將Message交給對應的target handler來處理。
  • 3.target handler調用自身的handleMessage()方法來處理Message。

事實上,在整個消息循環的流程中,並不止只有Java層參與,不少重要的工做都是在C++層來完成,咱們來看看下圖的這些類的調用它關係。ui

注:虛線表示關聯關係,實現表示調用關係。this

在這些類中MessageQueue 是Java層與C++層維繫的橋樑,MessageQueue與Looper相關功能都經過MessageQueue的Native方法來完成,而其餘虛線鏈接的類只有關聯關係,並無直接調用的關係,它們發生關係的橋樑是MessageQueue.spa

總結:插件

  • Handler發送的消息由MessageQueue存儲管理,並由Looper負責回調消息到handlerMessage()
  • 線程的切換由Looper完成,handlerMessage()所在線程由Looper.loop()調用者所在線程決定。

下面來列舉咱們幾個工做中或許都會遇到的問題。

Handler引發的內存泄漏緣由以及最佳解決方案

Handler容許咱們發送延遲消息,若是在延時期間內用戶關閉了activity,那麼該activity會泄漏。這個泄漏是由於Message會出油Handler,而又由於Java的特性,內部類會持有外部類,使得activity會被Handler持有, 這樣最終就會致使activity泄漏了。線程

解決的辦法就是:將Handler定義爲靜態的內部類,在內部持有activity的弱引用,並在activity的ondestroy()中調用handler.removeCallbacksAndMessage(null)及時移除全部消息。

private static class SafeHandler extends Handler {

    private WeakReference<HandlerActivity> ref;

    public SafeHandler(HandlerActivity activity) {
        this.ref = new WeakReference(activity);
    }

    @Override
    public void handleMessage(final Message msg) {
        HandlerActivity activity = ref.get();
        if (activity != null) {
            activity.handleMessage(msg);
        }
    }
}

而且再在 Activity.onDestroy() 前移除消息,加一層保障:

@Override
protected void onDestroy() {
  safeHandler.removeCallbacksAndMessages(null);
  super.onDestroy();
}

爲何咱們能在主線程直接使用Handler,而不須要建立Looper?

一般咱們認爲ActivityThread就是主線程,事實上它並非一個線程,而是主線程操做的管理者。在ActivityThread.main()方法中調用了Looper.prepareMainLooper()方法建立了主線程的looper,而且調用了loop()方法,全部咱們就能夠直接使用Handler了。

所以咱們能夠利用Callback這個攔截機制來攔截Handler的消息,如大部分插件化框架中的Hook ActivityThread.mH的處理。

主線程的Looper不容許退出

主線程不容許退出,退出就意味APP要掛了。

Handler裏藏着Callback能幹什麼?

Handler.Callback 有優先處理消息的權利 ,當一條消息被 Callback 處理並攔截(返回 true),那麼 Handler 的 handleMessage(msg) 方法就不會被調用了;若是 Callback 處理了消息,可是並無攔截,那麼就意味着一個消息能夠同時被 Callback 以及 Handler 處理。

建立Message實例的最佳方式

爲了節省開銷,Android給Message設計了回收機制,因此咱們在使用的時候儘可能複用Message,減小內存的消耗:

  • 經過Message的靜態方法Message.obtain()
  • 經過Handler的共有方法handler.obtainMessage()

子線程裏彈Toast的正確姿式

本質上是由於Toast的實現依賴於Handler,按子線程使用Handler的要求修改便可,同理的還有就是dialog。

new Thread(new Runnable() {
  @Override
  public void run() {
    Looper.prepare();
    Toast.makeText(MainActivity.this, "不會崩潰啦!", Toast.LENGTH_SHORT).show();
    Looper.loop();
  }
}).start();

妙用Looper機制

  • 將Runnable post 到主線程執行
  • 利用Looper判斷當前線程是不是主線程
public final class MainThread {

    private MainThread() {
    }

    private static final Handler HANDLER = new Handler(Looper.getMainLooper());

    public static void run(@NonNull Runnable runnable) {
        if (isMainThread()) {
            runnable.run();
        }else{
            HANDLER.post(runnable);
        }
    }

    public static boolean isMainThread() {
        return Looper.myLooper() == Looper.getMainLooper();
    }

}

主線程的死循環一直運行是否是特別消耗CPU資源呢?

並非,這裏就涉及到Linux pipe/epoll機制,簡單說就是在主線程的MessageQueue沒有消息時,便阻塞在loop的queue.next()中的nativePollOnce()方法裏,此時主線程會釋放CPU資源進入休眠狀態,直到下個消息到達或者有事務發生,經過往pipe管道寫端寫入數據來喚醒主線程工做。這裏採用的epoll機制,是一種IO多路複用機制,能夠同時監控多個描述符,當某個描述符就緒(讀或寫就緒),則馬上通知相應程序進行讀或寫操做,本質是同步I/O,即讀寫是阻塞的。因此說,主線程大多數時候都是處於休眠狀態,並不會消耗大量CPU資源。

相關文章
相關標籤/搜索