消息機制流程簡介

  1、消息機制流程簡介
  
  在應用啓動的時候,會執行程序的入口函數main(),main()裏面會建立一個Looper對象,而後經過這個Looper對象開啓一個死循環,這個循環的工做是,不斷的從消息隊列MessageQueue裏面取出消息即Message對象,並處理。而後看下面兩個問題:
  
  循環拿到一個消息以後,如何處理?
  
  是經過在Looper的循環裏調用Handler的dispatchMessage()方法去處理的,而dispatchMessage()方法裏面會調用handleMessage()方法,handleMessage()就是平時使用Handler時重寫的方法,因此最終如何處理消息由使用Handler的開發者決定。
  
  MessageQueue裏的消息從哪來?
  
  使用Handler的開發者經過調用sendMessage()方法將消息加入到MessageQueue裏面。
  
  上面就是Android中消息機制的一個總體流程,也是 「Android中Handler,Looper,MessageQueue,Message有什麼關係?」 的答案。經過上面的流程能夠發現Handler在消息機制中的地位,是做爲輔助類或者工具類存在的,用來供開發者使用。
  
  對於這個流程有兩個疑問:
  
  Looper中是如何能調用到Handler的方法的?
  
  Handler是如何能往MessageQueue中插入消息的?
  
  這兩個問題會在後面給出答案,下面先來經過源碼,分析一下這個過程的具體細節:
  
  2、消息機制的源碼分析
  
  首先main()方法位於ActivityThread.java類裏面,這是一個隱藏類,源碼位置:frameworks/base/core/java/android/app/ActivityThread.java
  
  public static void main(String[] args) {
  
  ......
  
  Looper.prepareMainLooper();
  
  ActivityThread thread = new ActivityThread();
  
  thread.attach(false);
  
  if (sMainThreadHandler == null) {
  
  sMainThreadHandler = thread.getHandler();
  
  }
  
  Looper.loop();
  
  throw new RuntimeException("Main thread loop unexpectedly exited");
  
  }
  
  Looper的建立能夠經過Looper.prepare()來完成,上面的代碼中prepareMainLooper()是給主線程建立Looper使用的,本質也是調用的prepare()方法。建立Looper之後就能夠調用Looper.loop()開啓循環了。main方法很簡單,很少說了,下面看看Looper被建立的時候作了什麼,下面是Looper的prepare()方法和變量sThreadLocal:
  
  static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
  
  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));
  
  }
  
  很簡單,new了一個Looper,並把new出來的Looper保存到ThreadLocal裏面。ThreadLocal是什麼?它是一個用來存儲數據的類,相似HashMap、ArrayList等集合類。它的特色是能夠在指定的線程中存儲數據,而後取數據只能取到當前線程的數據,好比下面的代碼:
  
  ThreadLocal<Integer> mThreadLocal = new ThreadLocal<>();
  
  private void testMethod() {
  
  mThreadLocal.set(0);
  
  Log.d(TAG, "main  mThreadLocal=" + mThreadLocal.get());
  
  new Thread("Thread1") {
  
  @Override
  
  public void run() {
  
  mThreadLocal.set(1);
  
  Log.d(TAG, "Thread1  mThreadLocal=" + mThreadLocal.get());
  
  }
  
  }.start();
  
  new Thread("Thread2") {
  
  @Override
  
  public void run() {
  
  mThreadLocal.set(2);
  
  Log.d(TAG, "Thread1  mThreadLocal=" + mThreadLocal.get());
  
  }
  
  }.start();
  
  Log.d(TAG, "main  mThreadLocal=" + mThreadLocal.get());
  
  }
  
  輸出的log是
  
  main  mThreadLocal=0
  
  Thread1  mThreadLocal=1
  
  Thread2  mThreadLocal=2
  
  main  mThreadLocal=0
  
  經過上面的例子能夠清晰的看到ThreadLocal存取數據的特色,只能取到當前所在線程存的數據,若是所在線程沒存數據,取出來的就是null。其實這個效果能夠經過HashMap<Thread, Object>來實現,考慮線程安全的話使用ConcurrentMap<Thread, Object>,不過使用Map會有一些麻煩的事要處理,好比當一個線程結束的時候咱們如何刪除這個線程的對象副本呢?若是使用ThreadLocal就不用有這個擔憂了,ThreadLocal保證每一個線程都保持對其線程局部變量副本的隱式引用,只要線程是活動的而且 ThreadLocal 實例是可訪問的;在線程消失以後,其線程局部實例的全部副本都會被垃圾回收(除非存在對這些副本的其餘引用)。更多ThreadLocal的講解參考:Android線程管理之ThreadLocal理解及應用場景
  
  好了回到正題,prepare()建立Looper的時候同時把建立的Looper存儲到了ThreadLocal中,經過對ThreadLocal的介紹,獲取Looper對象就很簡單了,sThreadLocal.get()便可,源碼提供了一個public的靜態方法能夠在主線程的任何地方獲取這個主線程的Looper(注意一下方法名myLooper(),多個地方會用到):
  
  public static @Nullable Looper myLooper() {
  
  return sThreadLocal.get();
  
  }
  
  Looper建立完了,接下來開啓循環,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;
  
  for (;;) {
  
  Message msg = queue.next(); // might block
  
  if (msg == null) {
  
  // No message indicates that the message queue is quitting.
  
  return;
  
  }
  
  try {
  
  msg.target.dispatchMessage(msg);
  
  } finally {
  
  if (traceTag != www.chenghyLpt.com) {
  
  Trace.traceEnd(traceTag);
  
  }
  
  }
  
  msg.recycleUnchecked();
  
  }
  
  }
  
  上面的代碼,首先獲取主線程的Looper對象,而後取得Looper中的消息隊列final MessageQueue queue = me.mQueue;,而後下面是一個死循環,不斷的從消息隊列裏取消息Message msg = queue.next();,能夠看到取出的消息是一個Message對象,若是消息隊列裏沒有消息,就會阻塞在這行代碼,等到有消息來的時候會被喚醒。取到消息之後,經過msg.target.dispatchMessage(msg);來處理消息,msg.target 是一個Handler對象,因此這個時候就調用到咱們重寫的Hander的handleMessage(www.changjianggw.com)方法了。
  
  msg.target 是在何時被賦值的呢?要找到這個答案很容易,msg.target是被封裝在消息裏面的,確定要從發送消息那裏開始找,看看Message是如何封裝的。那麼就從Handler的sendMessage(msg)方法開始,過程以下:
  
  public final boolean sendMessage(Message msg) {
  
  return sendMessageDelayed(msg, 0);
  
  }
  
  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 =www.seoxinyang.cn= null) {
  
  RuntimeException e = new RuntimeException(
  
  this + " sendMessageAtTime(www.haihongyule.com) called with no mQueue");
  
  Log.w("Looper", e.getMessage(), e);
  
  return false;
  
  }
  
  return enqueueMessage(queue, msg, uptimeMillis);
  
  }
  
  private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
  
  msg.target = this;
  
  if (mAsynchronous) {
  
  msg.setAsynchronous(true);
  
  }
  
  return queue.enqueueMessage(msg, uptimeMillis);
  
  }
  
  能夠看到最後的enqueueMessage()方法中msg.target = this;,這裏就把發送消息的handler封裝到了消息中。同時能夠看到,發送消息其實就是往MessageQueue裏面插入了一條消息,而後Looper裏面的循環就能夠處理消息了。Handler裏面的消息隊列是怎麼來的呢?從上面的代碼能夠看到enqueueMessage()裏面的queue是從sendMessageAtTime傳來的,也就是mQueue。而後看mQueue是在哪初始化的,看Handler的構造方法以下:
  
  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(www.chaoyuepint.com));
  
  }
  
  }
  
  mLooper = Looper.myLooper(www.yuchengyule.com);
  
  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;
  
  }
  
  mQueue的初始化很簡單,首先取得Handler所在線程的Looper,而後取出Looper中的mQueue。這也是Handler爲何必須在有Looper的線程中才能使用的緣由,拿到mQueue就能夠很容易的往Looper的消息隊列裏插入消息了(配合Looper的循環+阻塞就實現了發送接收消息的效果)。
  
  以上就是主線程中消息機制的原理。
  
  那麼,在任何線程下使用handler的以下作法的緣由、原理、內部流程等就很是清晰了:
  
  new Thread() {
  
  @Override
  
  public void run(www.yaoshiyulegw.com) {
  
  Looper.prepare();
  
  Handler handler = new Handler();
  
  Looper.loop();
  
  }
  
  }.start();
  
  首先Looper.prepare()建立Looper並初始化Looper持有的消息隊列MessageQueue,建立好後將Looper保存到ThreadLocal中方便Handler直接獲取。
  
  而後Looper.loop()開啓循環,從MessageQueue裏面取消息並調用handler的 dispatchMessage(msg) 方法處理消息。若是MessageQueue裏沒有消息,循環就會阻塞進入休眠狀態,等有消息的時候被喚醒處理消息。
  
  再而後咱們new Handler()的時候,Handler構造方法中獲取Looper而且拿到Looper的MessageQueue對象。而後Handler內部就能夠直接往MessageQueue裏面插入消息了,插入消息即發送消息,這時候有消息了就會喚醒Looper循環去處理消息。處理消息就是調用dispatchMessage(msg) 方法,最終調用到咱們重寫的Handler的handleMessage()方法。
  
  3、經過一些問題的研究增強對消息機制的理解
  
  源碼分析完了,下面看一下文章開頭的兩個問題:
  
  Looper中是如何能調用到Handler的方法的?
  
  Handler是如何能往MessageQueue中插入消息的?
  
  這兩個問題源碼分析中已經給出答案,這裏作一下總結,首先搞清楚如下對象在消息機制中的關係:
  
  Looper,MessageQueue,Message,ThreadLocal,Handler
  
  Looper對象有一個成員MessageQueue,MessageQueue是一個消息隊列,用來存儲消息Message
  
  Message消息中帶有一個handler對象,因此Looper取出消息後,能夠很方便的調用到Handler的方法(問題1解決)
  
  Message是如何帶有handler對象的?是handler在發送消息的時候把本身封裝到消息裏的。
  
  Handler是如何發送消息的?是經過獲取Looper對象從而取得Looper裏面的MessageQueue,而後Handler就能夠直接往MessageQueue裏面插入消息了。(問題2解決)
  
  Handler是如何獲取Looper對象的?Looper在建立的時候同時把本身保存到ThreadLocal中,並提供一個public的靜態方法能夠從ThreadLocal中取出Looper,因此Handler的構造方法裏能夠直接調用靜態方法取得Looper對象。
  
  帶着上面的一系列問題看源碼就很清晰了,下面是知乎上的一個問答:
  
  Android中爲何主線程不會由於Looper.loop()裏的死循環卡死?
  
  緣由很簡單,循環裏有阻塞,因此死循環並不會一直執行,相反的,大部分時間是沒有消息的,因此主線程大多數時候都是處於休眠狀態,也就不會消耗太多的CPU資源致使卡死。
  
  阻塞的原理是使用Linux的管道機制實現的
  
  主線程沒有消息處理時阻塞在管道的讀端
  
  binder線程會往主線程消息隊列裏添加消息,而後往管道寫端寫一個字節,這樣就能喚醒主線程從管道讀端返回,也就是說looper循環裏queue.next()會調用返回...
  
  這裏說到binder線程,具體的實現細節沒必要深究,考慮下面的問題:
  
  主線程的死循環如何處理其它事務?
  
  首先須要看懂這個問題,主線程進入Looper死循環後,如何處理其餘事務,好比activity的各個生命週期的回調函數是如何被執行到的(注意這裏是在同一個線程下,代碼是按順序執行的,若是在死循環這阻塞了,那麼進入死循環後循環之外的代碼是如何執行的)。
  
  首先再看main函數的源碼
  
  Looper.prepareMainLooper();
  
  ActivityThread thread = new ActivityThread();
  
  thread.attach(false);
  
  if (sMainThreadHandler == null) {
  
  sMainThreadHandler = thread.getHandler();
  
  }
  
  Looper.loop();
  
  在Looper.prepare和Looper.loop之間new了一個ActivityThread並調用了它的attach方法,這個方法就是開啓binder線程的,另外new ActivityThread()的時候同時會初始化它的一個H類型的成員,H是一個繼承了Handler的類。此時的結果就是:在主線程開啓loop死循環以前,已經啓動binder線程,而且準備好了一個名爲H的Handler,那麼接下來在主線程死循環以外作一些事務處理就很簡單了,只須要經過binder線程向H發送消息便可,好比發送 H.LAUNCH_ACTIVITY 消息就是通知主線程調用Activity.onCreate() ,固然不是直接調用,H收到消息後會進行一系列複雜的函數調用最終調用到Activity.onCreate()。
  
  至於誰來控制binder線程來向H發消息就不深刻研究了,下面是《Android開發藝術探索》裏面的一段話:
  
  ActivityThread 經過 ApplicationThread 和 AMS 進行進程間通信,AMS 以進程間通訊的方式完成 ActivityThread 的請求後會回調 ApplicationThread 中的 Binder 方法,而後 ApplicationThread 會向 H 發送消息,H 收到消息後會將 ApplicationThread 中的邏輯切換到 ActivityThread 中去執行,即切換到主線程中去執行,這個過程就是主線程的消息循環模型。
  
  這個問題就到這裏,更多內容看知乎原文
  
  最後
  
  和其餘系統相同,Android應用程序也是依靠消息驅動來工做的。網上的這句話仍是頗有道理的。
  
  文章參考:
  
  《Android開發藝術探索》
  
  Android中爲何主線程不會由於Looper.loop()裏的死循環卡死?
  
  Android線程管理之ThreadLocal理解及應用場景
  
  Android 消息機制——你真的瞭解Handler
  
  Android Handler究竟是什麼java

相關文章
相關標籤/搜索