Android消息機制(一) Handler looper messengerQueue

參考
Handler和他的小伙伴們(上)
Android:異步處理之Handler+Thread的應用(一)
Android:異步處理之Handler、Looper、MessageQueue之間的恩怨(三)
android的消息處理機制(圖+源碼分析)——Looper,Handler,Message
Android中為什么主線程不會因為Looper.loop()里的死循環(huán)卡死

主線程是指進程所擁有的線程,默認情況下一個進程只有一個線程就是主線程,主線程主要處理界面交互,又叫UI線程;除了主線程之外都是子線程,又叫工作線程。

一、為什么不能直接更新UI

不要在UI主線程中進行耗時操作,你可能會疑問什么是UI主線程,UI主線程主要運行的就是Activity、Service等里面的生命周期方法,所以不要在生命周期方法如onCreate()中進行下載這些大事件。對于耗時操作,我們應該新建一個子線程并交給他處理。但是還需要注意一點,不要在子線程中更新UI界面。
為什么不允許子線程訪問UI呢?這是因為UI控件是線程不安全的,如果多線程并發(fā)訪問會導致UI控件處于不可預期狀態(tài)。那為什么不加鎖機制來解決呢?缺點有兩個:

  • 鎖機制會讓UI訪問邏輯變復雜
  • 鎖機制會阻塞某些線程的執(zhí)行,降低UI訪問效率
    所以最簡單高效方式就是單線程處理UI操作。
二、Handler

現(xiàn)在我們需要進行耗時操作(例如下載文件)時不能在主線程執(zhí)行,我們又需要在UI界面通知用戶我們活干完了不能在子線程中執(zhí)行。這似乎是一個棘手的熱山芋呀,幸好谷歌給我們提供了一個救我們于危難之中的Handler,一個能讓主線程監(jiān)聽子線程發(fā)送來消息的東東。
<pre>
private Button btn;
private TextView text;

private Handler handler = new Handler(){
  private int process = 0;
  @Override
  public void handleMessage(Message msg) {
    switch(msg.what){
    case 0://更細下載進度
      process += 1;
      text.setText("下載" + process + "%");//在主線程中更新UI界面
      break;
    case 1://提示下載完成
      text.setText("下載完成");//在主線程中更新UI界面
      break;
    default:
      break;
    }
  }
};
//onCreate之類的生命周期的方法就是允許在UI主線程中
@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

btn = (Button) findViewById(R.id.btn);
  text = (TextView) findViewById(R.id.text);

btn.setOnClickListener(new OnClickListener() {
  @Override
  public void onClick(View v) {
    new Thread(){
      @Override
      public void run() {
        //為了不阻塞主線程,將下載任務通過子線程來執(zhí)行
        for(int i = 0; i < 100; i++){
          try {
            Thread.sleep(200);//休眠0.2秒,模擬耗時操作
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
          handler.sendEmptyMessage(0);//發(fā)送消息到handler,通知下載進度
        }
        handler.sendEmptyMessage(1);//發(fā)送消失到handler,通知主線程下載完成
        }
      }.start();
    }
  });
}
</pre>
但是如果你覺得每次都要重寫handlerMessage()比較麻煩,我們完全可以用更加簡略的方法來解決我們的需求,就是用handler中的post方法。代碼如下
<pre>
new Thread(){
  @Override
  public void run() {
    //在子線程中進行下載操作
    try {
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    handler.post(new Runnable() {
      @Override
      public void run() {
        text.setText("下載完成");
      }
    });//發(fā)送消失到handler,通知主線程下載完成
  }
}.start();
</pre>
這樣處理的話我們就可以不用重寫handlerMessage()方法了,適合子線程與主線程進行較為單一的交流。但在這里我們要強調的一點的是,post里面的Runnable還是在UI主線程中運行的,而不會另外開啟線程運行,千萬不要在Runnable的run()里面進行耗時任務
如果你有時候連handler都不想搞,還可以這樣寫代碼滴。我們只需要把handler換成View組件進行post,更新任務自然會加載到UI主線程中進行處理。
<pre>
text.post(new Runnable() {
  @Override
  public void run() {
    text.setText("下載完成");
  }
});//發(fā)送消失到handler,通知主線程下載完成
</pre>

關于sendMessage和post詳細區(qū)別,可參考承香墨影Android--多線程之Handler

三、Handler looper messengerQueue

這里給大家寫了一個向子線程發(fā)送消息并顯示輸出的例子,強調一下下哦,是向子線程喲。
<pre>
public class MainActivity extends ActionBarActivity {

private Handler handler;
private Button btn;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    
    btn = (Button) findViewById(R.id.sendmsg);
    
    new HandlerThread().start();//啟動子線程
    
    btn.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            handler.sendEmptyMessage(0);//向子線程發(fā)送消息
        }
    });
}

class HandlerThread extends Thread{
    @Override
    public void run() {
        //開始建立消息循環(huán)
        Looper.prepare();//初始化Looper
        handler = new Handler(){//默認綁定本線程的Looper
            @Override
            public void handleMessage(Message msg) {
                switch(msg.what){
                case 0:
                    Toast.makeText(MainActivity.this, "子線程收到消息", Toast.LENGTH_SHORT).show();
                }
            }
        };
        Looper.loop();//啟動消息循環(huán)
    }
}

}
</pre>

說一下消息發(fā)送的過程:

1、啟動一個子線程,并在子線程初始化一個Looper。

<pre>
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));
}
private Looper(boolean quitAllowed) {
  mQueue = new MessageQueue(quitAllowed);
  mThread = Thread.currentThread();
}
</pre>
在Looper()中,實例化了一個消息隊列(MessageQueue)!并且如我們所愿的綁定到了mQueue這個局部變量上,在這里我們可以得出這么一個結論:調用Looper. prepare()的線程就建立起一個消息循環(huán)的對象。

2、在HandlerThread中實例化Handler,Handler自動綁定上當前線程的Looper。

<pre>
public Handler() {
  this(null, false);
}
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;
}
</pre>
在代碼中我們通過handler = new Handler() 調用到了Handler(Callback callback, boolean async)這個方法;我們發(fā)現(xiàn)mLooper = Looper.myLooper()把線程中的Looper綁定到了Handler上,通過mQueue = mLooper.mQueue獲取了線程的消息隊列(單鏈表,方便插入刪除),我當然也可以換句話說:Handler已經(jīng)綁定到了創(chuàng)建此Handler對象的線程的消息隊列上了,所以咱們可以開始干壞事了。。。。

3、重寫Handler里面的消息處理方法。

<pre>
@Override
public void handleMessage(Message msg) {
switch(msg.what){
case 0:
Toast.makeText(MainActivity.this, "子線程收到消息", Toast.LENGTH_SHORT).show();
}
}
</pre>

4、執(zhí)行Looper.loop()啟動消息循環(huán),子線程進入等待消息狀態(tài)。

<pre>
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;

Binder.clearCallingIdentity();
  final long ident = Binder.clearCallingIdentity();

for (;;) {
    Message msg = queue.next(); // might block
    if (msg == null) {
      return;
    }

Printer logging = me.mLogging;
    if (logging != null) {
      logging.println(">>>>> Dispatching to " + msg.target + " " +
        msg.callback + ": " + msg.what);
    }

msg.target.dispatchMessage(msg);

if (logging != null) {
      logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
    }

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.recycle();
  }
}
</pre>
在loop()的這個靜態(tài)方法中,我們可以注意到for (;;)這個方法,這是死胡同死循環(huán),所以我們將其稱作為“消息循環(huán)”,說起來挺形象滴。在消息循環(huán)中會調用queue.next()來獲取消息隊列中排隊等待處理的消息,并將其賦值到msg這個變量上;接下來就判斷如果msg != null 就開始分發(fā)消息,也就是執(zhí)行msg.target.dispatchMessage(msg)。在分發(fā)消息結束后,將會回收掉這個消息,體現(xiàn)在msg.recycle()這個函數(shù)上。
msg.target是一個handler對象,表示需要處理這個消息的handler對象,所以我們回到Handler看看dispatchMessage()這個方法了:
<pre>
public void dispatchMessage(Message msg) {
  if (msg.callback != null) {
    handleCallback(msg);//post方法傳遞的Runnable參數(shù)
  } else {
    if (mCallback != null) {
      if (mCallback.handleMessage(msg)) {
        return;
      }
    }
    handleMessage(msg);
  }
}
</pre>
不知道大家有沒有一眼發(fā)現(xiàn)handleMessage()這個方法,這可不是我們在第三步重寫Handler中的方法么。真相大白,當 msg.callback != null 并且 mCallback != null 時將會調用 handleMessage(msg) 來處理其他線程發(fā)送來的消息,我們通過覆蓋這個方法來實現(xiàn)我們具體的消息處理過程;這也就是Handler消息處理機制的全部內(nèi)容。
通讀全文,我們可以知道消息循環(huán)機制的核心就是Looper,因為Looper持有了MessageQueue的對象,并且可以被一個線程設為該線程的一個局部變量,我們可以這么認為這個線程通過Looper擁有了一個消息隊列。而Handler的用處就是封裝了消息發(fā)送和消息處理的方法,在線程通信中,線程可以通過Handler發(fā)送消息給創(chuàng)建Handler的線程,通過Looper將消息放入進入消息接收線程的消息隊列,等待Looper取出消息并在最后交給Handler處理具體消息。
我們會發(fā)現(xiàn)在Activity中實例化一個Handler并不需要Looper.prepare()來初始化一個Looper和Looper.loop()來啟動消息循環(huán),因為Activity在構造過程中已經(jīng)對Looper進行了初始化并且建立了消息循環(huán),參見ActivityThread.java中的代碼:
<pre>
public final class ActivityThread {
public static final void main(String[] args) {
......
Looper.prepareMainLooper();
......
ActivityThread thread = new ActivityThread();
thread.attach(false);
......
Looper.loop();
......
thread.detach();
......
}
}
</pre>
Android應用程序進程在啟動的時候,會在進程中加載ActivityThread類,并且執(zhí)行這個類的main函數(shù),應用程序的消息循環(huán)過程就是在這個main函數(shù)里面實現(xiàn)的。

總結:一個線程只有一個Looper, 而一個Looper持有一個MessageQueue, 當調用Looper.prepare()時,Looper就與當前線程關聯(lián)起來了(在Activity里沒有顯示調用Looper.prepare()是因為系統(tǒng)自動在主線程里幫我們調用了),而Handler是與Looper的線程是綁定的,查看Handler類的源碼可以發(fā)現(xiàn)它幾個構造函數(shù),其中有接收一個Looper參數(shù)的,也有不接收Looper參數(shù)的,從上面的代碼上看,我們沒有為Handler指定Looper,那么Handler就默認更當前線程(即主線程)的Looper關聯(lián)起來了,之所以啰嗦那么多就是因為這決定了Handler.handlerMessage(msg)方法體里的代碼到底在哪個線程里執(zhí)行,我們再梳理一下,Looper.prepare調用決定了Looper與哪個線程關聯(lián),間接決定了與這個Looper相關聯(lián)的Handler.handlerMessage(msg)方法體里的代碼執(zhí)行的線程。(太啰嗦了)
現(xiàn)在回到上面的代碼,我們的Handler是在主線程里的定義的,所以也默認跟主線程的Looper相關聯(lián),即handlerMessage方法的代碼會在UI線程執(zhí)行。

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

推薦閱讀更多精彩內(nèi)容