Android 開發之Handler的前世今生

文章獨家授權公眾號:碼個蛋
更多分享:http://www.cherylgood.cn

  • 談到Android開發,就離不開線程操作,而面試中也會常常問到有關異步線程、多線程、Handler等問題,作為面試中中獎率如此之高的一個問題,我們今天不妨來瞅瞅這handler長啥樣!

目前:假設我們需要在子線程中更新UI,一般有以下幾種方式:

  • 1、view.post(Runnable action)
  • 2、activity.runOnUiThread(Runnable action)
  • 3、AsyncTask
  • 4、Handler

  • 而我們今天的主要目標就是Handler,咱來一層一層撕開它的真面目吧!

  • 說了那么多廢話,我們開始進入正題,首先我們看下handler的官方定義:

  • A Handler allows you to send and process Message and Runnable objects associated with a thread's MessageQueue. Each Handler instance is associated with a single thread and that thread's message queue. When you create a new Handler, it is bound to the thread / message queue of the thread that is creating it -- from that point on, it will deliver messages and runnables to that message queue and execute them as they come out of the message queue.

  • Handler允許你通過使用一個與線程的MessageQueue相關聯的Message和Runnable對象去發送和處理消息。 每個處理程序實例與單個線程和該線程的消息隊列相關聯。 當您創建一個新的處理程序時,它綁定到正在創建它的線程的線程/消息隊列 - 從那時起,它將向消息隊列傳遞消息和可運行文件,并在消息發出時執行它們 隊列。

  • There are two main uses for a Handler: (1) to schedule messages and runnables to be executed as some point in the future; and (2) to enqueue an action to be performed on a different thread than your own.

  • Handler有兩個主要用途:(1)在可預見的時間內去調度消息和作為一些點的可運行程序(2)將不同于自己的線程執行的操作排入隊列中。

  • Scheduling messages is accomplished with the post(Runnable), postAtTime(Runnable, long), postDelayed(Runnable, long), sendEmptyMessage(int), sendMessage(Message), sendMessageAtTime(Message, long), and sendMessageDelayed(Message, long) methods. The post versions allow you to enqueue Runnable objects to be called by the message queue when they are received; the sendMessage versions allow you to enqueue a Message object containing a bundle of data that will be processed by the Handler's handleMessage(Message) method (requiring that you implement a subclass of Handler).

  • 消息的調度是通過post(Runnable),postAtTime(Runnable,long),postDelayed(Runnable,long),sendEmptyMessage(int),sendMessage(Message),sendMessageAtTime(Message,long)和sendMessageDelayed(Message,long)來完成的 。 后臺版本允許你將接收到的消息隊列調用的Runnable對象排入隊列; sendMessage版本允許你將包含將由處理程序的handleMessage(Message)方法處理的數據包(要求您實現Handler的子類)的Message對象排入隊列。

  • When posting or sending to a Handler, you can either allow the item to be processed as soon as the message queue is ready to do so, or specify a delay before it gets processed or absolute time for it to be processed. The latter two allow you to implement timeouts, ticks, and other timing-based behavior.

  • 當發布或發送到Handler時,你可以在消息隊列準備就緒后立即處理該項目或者指定一個延遲時間去處理該消息隊列,或者指定一個具體時間處理該消息。 后兩者允許您實現超時,定時和其他基于時間的行為。

  • When a process is created for your application, its main thread is dedicated to running a message queue that takes care of managing the top-level application objects (activities, broadcast receivers, etc) and any windows they create. You can create your own threads, and communicate back with the main application thread through a Handler. This is done by calling the same post or sendMessage methods as before, but from your new thread. The given Runnable or Message will then be scheduled in the Handler's message queue and processed when appropriate.

  • 當為你的應用創建一個進程時,其主線程專用于運行一個消息隊列,該消息隊列負責管理頂級應用程序對象(activitys, broadcast receivers 等)及其創建的任何窗口。 你可以創建你自己的線程并通過Handler與主應用程序線程進行通信。 這可以通過從你的新線程中調用同樣的post或sendMessage方法來實現。 給定的Runnable或Message將在Handler的消息隊列中進行調度,并在適當時進行處理。

  • 在查看Handler源碼之前,我們先了解幾個類:
    Handler 、Looper、MessageQueue、Message、ThreadLocation
    Handler我們就不在介紹該類,上面的官方文檔已給出了詳細的介紹,我們來看下其余幾個:

  • 1、ThreadLocal:每個使用該變量的線程提供獨立的變量副本,每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本。ThreadLocal內部是通過map進行實現的;
  • 2、Looper:可以理解為循環器,就是扶著管理一個消息循環隊列(MessageQueue)的;
  • 3、MessageQueue:消息隊列,用來存放handler發布的消息
  • 4、Message:消息體,封裝了我們傳輸消息所需的數據結構。
  • ok,那么我們從哪里開始看起呢,好吧, 從創建一個Handler實例為入口,首先我們看handler的構造方法:

    public Handler() {
      this(null, false);
    }
    public Handler(Callback callback) {
      this(callback, false);
    }
    public Handler(Looper looper) {
        this(looper, null, false);
    }
    public Handler(Looper looper, Callback callback) {
        this(looper, callback, false);
    }
    public Handler(boolean async) {
      this(null, async);
    }
    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());
          }
      }
    
      mLooper = Looper.myLooper();
      if (mLooper == null) {
          throw new RuntimeException(
              "Can't create handler inside thread that has not called Looper.prepare()");
      }
      mQueue = mLooper.mQueue;
      mCallback = callback;
      mAsynchronous = async;
    }
    
    public Handler(Looper looper, Callback callback, boolean async) {
      mLooper = looper;
      mQueue = looper.mQueue;
      mCallback = callback;
      mAsynchronous = async;
    }
    
  • 可以看到,有多個構造方法,但是最終都會調用到最后一個。我們看倒數第二個,有這么一句: mLooper = Looper.myLooper();這里我們看到了個looper,可以看到,在handler里面會有一個mlooper對象與之關聯,我們先不看mlooper是怎么來的,我們先把下面的看完;繼續看下面一句: mQueue = mLooper.mQueue;我們的handler里面也有一個隊列的對象,實際上mQueue就是MessageQueue,后面我們會講解到。好的,繼續往下看, mCallback = callback;一般情況下mCallback是null,我們通常new 一個Handler是不是調用的無參構造方法?callback的作用后面也會講解到,好的最后一句: mAsynchronous = async;表示我們的執行過程是異步的還是同步的,一般情況下,默認是異步的。

小結: Handler會存有Looper對象以及消息隊列mQueue,通過關聯looper與mQueue,可以想象,handler要把message插入消息隊列中,最直接的方式當然是拿到消息隊列的實例,實現消息的發送;

  • 看了Handler的構造,接下來我們看下Looper.mLooper:
    public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
    }
    -可以看到,Looper.myLooper內部是調用了sThreadlocal.get();這個sThreadLocal其實就是我們之前說的ThreadLocal類的實例,他負責存儲當先線程的Looper實例;是不是真的呢?我們看下sThreadLocal在哪里賦值的,很好,我們找到了一個prepare方法,看名字是準備的意思,也就是為我們準備我們需要的looper對象,我們繼續看:

     public static void prepare() {
      prepare(true);
    }
    
    private static void prepare(boolean quitAllowed) {
      if (sThreadLocal.get() != null) {
          throw new RuntimeException("Only one Looper may be created per thread");
      }
      sThreadLocal.set(new Looper(quitAllowed));
      }
    
  • 首先我們可以看到,會先判斷sThreadLocal.get() != null,說明Looper.prepare()只能被調用一次哦,不然就會拋出異常,這樣做是為了保證一個線程只有一個looper存在,然后我們的可以看到里面通過new Looper(quitAllowed)獲得當先線程的looper,我們繼續看Looper的構造方法:

      private Looper(boolean quitAllowed) {
      mQueue = new MessageQueue(quitAllowed);
      mThread = Thread.currentThread();
        }
    
  • 在looper的構造方法里,主要做了兩件事:1、創建一個looper管理的消息隊列 messageQueue;2、獲得當前的線程;

小結:Looper里面會存儲當前的線程,以及所管理的消息隊列mQueue,一個Looper只會管理一個消息隊列MessageQueue;

  • 從上面的代碼中我們可以知道,在new 一個handler的同時,我們就獲得了一個handler實例、一個當前線程的looper、一個looper管理的messagequeue,好像擁有了這三個對象,我們就可以發送消息了哦。

  • 大家都知道looper從創建之后,就會開始循環,在looper類的頂部,官方給出了一段代碼:

    class LooperThread extends Thread {
    public Handler mHandler;
    public void run() {
      Looper.prepare();
        mHandler = new Handler() {
           public void handleMessage(Message msg) {
              // process incoming messages here
           }
       };
        Looper.loop();
    }
    
  • 當我們使用handler發消息時,步驟是:

  • 1、 調用 Looper.prepare(); 初始化所需的looper以及messageQueue
  • 2、 實例化一個handler對象,我們可以在handleMessage獲得message做一些操作,此時handleMessage方法是在當前的Looper中執行的,也就是說,如果當前的looper是UI Looper,那么你可以更新UI,如果當前looper不是UI Looper,那么你更新UI肯定會報錯,你可能會說,我用handler時,好像都不用調用Looper.prepare();,我怎么知道我當前的looper是UI的還是不是呢,其實系統一般默認都幫我們獲取了UI 的Looper,后面我們會講解到;
  • 3、調用 Looper.loop();讓Looper跑起來吧!

  • Looper.prepare();我們前面已經分析過了,主要是實例化一個messageQueue,而且只能調用一次;那么我們重點就轉移懂到 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;
    
      // Make sure the identity of this thread is that of the local process,
      // and keep track of what that identity token actually is.
      Binder.clearCallingIdentity();
      final long ident = Binder.clearCallingIdentity();
    
      for (;;) {
          Message msg = queue.next(); // might block
          if (msg == null) {
              // No message indicates that the message queue is quitting.
              return;
          }
    
          // This must be in a local variable, in case a UI event sets the logger
          final Printer logging = me.mLogging;
          if (logging != null) {
              logging.println(">>>>> Dispatching to " + msg.target + " " +
                      msg.callback + ": " + msg.what);
          }
    
          final long traceTag = me.mTraceTag;
          if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
              Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
          }
          try {
              msg.target.dispatchMessage(msg);
          } finally {
              if (traceTag != 0) {
                  Trace.traceEnd(traceTag);
              }
          }
    
          if (logging != null) {
              logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
          }
    
          // Make sure that during the course of dispatching the
          // identity of the thread wasn't corrupted.
          final long newIdent = Binder.clearCallingIdentity();
          if (ident != newIdent) {
              Log.wtf(TAG, "Thread identity changed from 0x"
                      + Long.toHexString(ident) + " to 0x"
                      + Long.toHexString(newIdent) + " while dispatching to "
                      + msg.target.getClass().getName() + " "
                      + msg.callback + " what=" + msg.what);
          }
    
          msg.recycleUnchecked();
      }
    }
    
  • 1、調用final Looper me = myLooper();獲得一個looper,myLooper方法我們前面分析過,返回的是sThreadLocal中存儲的Looper實例,當me==null拋出異常;所以,在looper執行loop跑起來之前,我們要記得調用prepare()哦。當獲得當前的looper后,調用 final MessageQueue queue = me.mQueue; 獲取looper管理的MessageQueue;然后我們可以看到一個很有意思的for語句: for (;;) {...} 這就是循環的開始了,此時我在想,我的天,這不是個無限死循話么?怎么可能呢?當然有退出的條件,不然不就傻逼了么!

  • 2、我們可以看到:他會從looper的queue中獲取message,當message==null,循環停止!
    Message msg = queue.next(); // might block
    if (msg == null) {
    // No message indicates that the message queue is quitting.
    return;
    }

  • 3、循環起來了,咱的looper也沒閑著,他一直知道它的工作是什么,我們可以看到:msg.target.dispatchMessage(msg);通過調用msg對象里的target對象的dispatchMessage(msg)方法把消息處理了。其實msg對象里的target對象就是我們new出來的handler,我們后面會講到。

小結:

looper主要做了如下工作:

  • 1、將自己與當前線程關聯在一起,通過ThreadLocal存儲當前線程的looper,確保當前線程只有一個looper實例;
  • 2、創建一個MessageQueue與當前looper綁定,通過prepare方法控制looper只能有一個messageQueue實例;
  • 3、調用loop()方法,不斷從MessageQueue中去取消息,通過調用msg.target.dispatchMessage(msg)處理;

  • 分析完了looper、接下來當然是hanlder發送消息了,我們又回到了handler中,我們通過handler發消息,自然少不了我們得sendMessag方法,那么我們就從它入手吧:

     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);
    }
    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 queue = mQueue;
      if (queue == null) {
          RuntimeException e = new RuntimeException(
                  this + " sendMessageAtTime() called with no mQueue");
          Log.w("Looper", e.getMessage(), e);
          return false;
      }
      return enqueueMessage(queue, msg, uptimeMillis);
    }
    
    public final boolean sendMessageAtFrontOfQueue(Message msg) {
      MessageQueue queue = mQueue;
      if (queue == null) {
          RuntimeException e = new RuntimeException(
              this + " sendMessageAtTime() called with no mQueue");
          Log.w("Looper", e.getMessage(), e);
          return false;
      }
      return enqueueMessage(queue, msg, 0);
    }
    

  • 可以看到我們的sendMessage有多種方法,但最終都會調用enqueueMessage方法,我們看enqueueMessage方法源碼:
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
    msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
    }
  • 可以看到里面會講當前的this賦值給msg.target,this
    當前就是我們當前的handler了,這也就是之前在分析looper時說的,通過調用msg.target. dispatchMessage(msg)方法處理消息;后面后調用queue.enqueueMessage(msg, uptimeMillis);把消息放入當前的looper的MessageQueue隊列中去處理,消息的發送流程就分析完了,發送了,接下來就是處理消息了!

  • 我們用handler時,都是在handleMessage方法中處理消息的,那么我們就從handleMessage方法入手:
    /**
    * Subclasses must implement this to receive messages.
    */
    public void handleMessage(Message msg) {
    }

  • 可以看到handleMessage是一個空的方法,我們看handleMessage在哪被調用的呢?

      /**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
      if (msg.callback != null) {
          handleCallback(msg);
      } else {
          if (mCallback != null) {
              if (mCallback.handleMessage(msg)) {
                  return;
              }
          }
          handleMessage(msg);
      }
    }
    
  • 可以看到handleMessage在dispatchMessage中被調用了,奇怪,怎么有兩個handleMessage方法呢?大家不要弄混了哦,我們handler的handleMessage方法返回值時void,所以mCallback.handleMessage肯定不是我們handler的了;

  • 第一個縣判斷msg.callback!=null 調用 handleCallback(msg);
    然后我們追進去看:
    private static void handleCallback(Message message) {
    message.callback.run();
    }
    看到了run(),是不是想到了Runnable?其實message中的callback就是Runnable,我們可以從Message的創建函數中看到:
    private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
    }

  • 我們繼續回到dispatchMessage方法:也就是說,如果我們的給massge設置了callback,那么我們的handleMessage方法就不會被執行了,當然,一般我們的massge.callback都是null的。后面就會繼續判斷mCallback!=null如果成立則調用mCallback.handleMessage(msg) mCallback其實是一個回調接口,可以看到,如果mCallback.handleMessage(msg)返回true,就不會執行我們的Handler.handleMessage方法,所以我們其實可以通過給handler添加Callback來實現一個message的過濾或者攔截功能。

  • 我們的Handler.handleMessage經過重重阻撓,最終終于可以執行了。

總結:
  • 1、在Looper.prepare()中會通過sThreadLocal保存一個looper實例,控制當前線程只能有一個looper實例;
  • 2、創建looper實例時,會創建一個MessageQueue與looper關聯;
  • 3、因為looper只會存在一個實例,所以 當前線程也會只存在一個MessageQueue隊列;
  • 4、調用Looper.loop()讓looper跑起來吧,然后looper就可以不停的從MessageQueue把消息拿出來,然后通過調用msg.target.dispatchMessage(msg)處理消息,也是讓消息最終進入我們的Handler.handleMessage方法,被我們給處理了;所以我們在實例化handler時需要重寫handleMessage方法;
  • 5、實例化Handler時,handler中會獲得當前線程的looper以及looper的messageQueue;
  • 6、在調用sendMessage發送消息時,最終會調用enqueueMessage方法,在enqueueMessage方法里會將msg.target=handler,講handler關聯到msg中,這樣looper在取出messageQueue中的消息時,才知道該消息是要發給那個handler處理的,將handler與msg關聯后,就將msg加入隊列中去了,等待looper處理。

  • 來個圖解吧,畫的不是很好


    Looper.png
  • 使用Handler注意事項:

  • 1、創建massage對象時,推薦使用obtain()方法獲取,因為Message內部會維護一個Message池用于Message的復用,這樣就可以避免 重新new message而沖內心分配內存,減少new 對象產生的資源的消耗。
  • 2、handler 的handleMessage方法內部如果有調用外部activity或者fragment的對象,一定要用弱飲用,handler最好定義成static的,這樣可以避免內存泄漏;為什么呢?因為一但handler發送了消息。而handler內部有對外部變量的引用,此時handler已經進入了looper的messageQueue里面。此時activity或者fragment退出了可視區域,但是handler內部持有其引用且為強引用時,其就不會立即銷毀,產生延遲銷毀的情況。

更多分享:http://www.cherylgood.cn

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,533評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,055評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,365評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,561評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,346評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,889評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,118評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,637評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,558評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,739評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,980評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,619評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,347評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,702評論 2 370

推薦閱讀更多精彩內容