Android 開發之Handler

談到Android開發,就離不開線程操作,而我們需要在子線程中更新UI,一般有以下幾種方式:

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

而我們今天的主要目標就是Handler,首先我們看下handler的官方定義:

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

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

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

  • 當發布或發送到Handler時,你可以在消息隊列準備就緒后立即處理該項目或者指定一個延遲時間去處理該消息隊列,或者指定一個具體時間處理該消息。 后兩者允許您實現超時,定時和其他基于時間的行為。
  • 當為你的應用創建一個進程時,其主線程專用于運行一個消息隊列,該消息隊列負責管理頂級應用程序對象(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:消息體,封裝了我們傳輸消息所需的數據結構。

那么我們從哪里開始看起呢,好吧, 從創建一個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處理。

  • 使用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內部持有其引用且為強引用時,其就不會立即銷毀,產生延遲銷毀的情況。

面試問題:
  • Handler延遲消息處理:
    MessageQueue,以隊列的形式管理message,message先進先出,但其內部是采用單鏈表來存儲消息列表。Handler的方法post(Runnable r)、postDelayed(Runnabler, long delayMillis)、sendMessage(Message msg)、sendMessageDelayed(Message msg, long delayMillis)最終調用的都是sendMessageAtTime(Message msg, long uptimeMillis),其中又調用了enqueueMessage()將msg插入隊列中。即不管發送的消息有沒有延遲,都會先插入隊列中,如果有延遲的話,looper不會立刻取出消息,時間到后才會取出消息,也就是延遲指延遲處理,不是延遲發送。
image

Handler可以調用sendMessageAtFrontOfQueue(Messagemsg),postAtFrontOfQueue(Runnable r),將消息插入隊頭,最先取出,最先執行,之后再處理隊列中的其他消息。

image

如果隊列中只有延遲消息,此時發送一個普通消息,普通消息會插入隊頭,最先處理,而不會等延遲消息取出后,再取出普通消息。

  • 為什么在ActivityThread的main方法中有死循環(Loop.loop()),不會卡死

我們看到在ActivityThread的main中調用了 Looper.loop()

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

    //建立一個Binder通道(會創建新線程,向主線程的messageQueue中發送消息)
    ActivityThread thread = new ActivityThread();
    thread.attach(false);

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

    ......
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

要說清楚問題,我們要知道android是基于消息驅動的。具體體現在上述代碼中,在代碼注釋的地方我們可以看到 ActivityThread thread = new ActivityThread(); thread.attach(false);這兩行代碼,執行這兩句代碼會建立一個與ActivityManagerService連接的binder通道。ActivityManagerService負責管理所有activity的生命周期方法,例如oncreat,onresume等,當ActivityManagerService開始需要activity執行生命周期方法時,會首先通過建立好的binder通道調用應用程序進程的ApplicationThread的相關方法中。ApplicationThread會通過一個類型為Handler的H類將相關信息發送到主線程的消息隊列中,然后通過handler來處理這個消息。這樣就不會導致程序的主線程卡死。
上面只是說明了一種情況(activity的生命周期調用),其實所有的情況都是如此。又比如界面的更新:當界面需要更新的時候,也是講這個消息封裝在message對象中,然后添加到主線程的消息隊列中,由消息隊列統一管理。因此有消息時會進行處理,沒有消息時,主線程處于休眠狀態。
所以,由于android主線程是基于消息驅動的,因此雖然有Loop.loop()這個死循環,但是主線程不會卡。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容