標籤:
在《Android非同步處理一:使用Thread+Handler實現非UI線程更新UI介面》中,我們講到使用Thread+Handler的方式來實現介面的更新,其實是在非UI線程發送訊息到UI線程,通知UI線程進行介面更新,這一篇我們將深入學習Android線程間通訊的實現原理。
概述:Android使用訊息機制實現線程間的通訊,線程通過Looper建立自己的訊息迴圈,MessageQueue是FIFO的訊息佇列,Looper負責從MessageQueue中取出訊息,並且分發到訊息指定目標Handler對象。Handler對象綁定到線程的局部變數Looper,封裝了發送訊息和處理訊息的介面。
例子:在介紹原理之前,我們先介紹Android線程通訊的一個例子,這個例子實現點擊按鈕之後從主線程發送訊息"hello"到另外一個名為” CustomThread”的線程。
代碼下載
LooperThreadActivity.java
package com.zhuozhuo;import android.app.Activity;import android.os.Bundle;import android.os.Handler;import android.os.Looper;import android.os.Message;import android.util.Log;import android.view.View;import android.view.View.OnClickListener;public class LooperThreadActivity extends Activity{ /** Called when the activity is first created. */ private final int MSG_HELLO = 0; private Handler mHandler; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); new CustomThread().start();//建立並啟動CustomThread執行個體 findViewById(R.id.send_btn).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) {//點擊介面時發送訊息 String str = "hello"; Log.d("Test", "MainThread is ready to send msg:" + str); mHandler.obtainMessage(MSG_HELLO, str).sendToTarget();//發送訊息到CustomThread執行個體 } }); } class CustomThread extends Thread { @Override public void run() { //建立訊息迴圈的步驟 Looper.prepare();//1、初始化Looper mHandler = new Handler(){//2、綁定handler到CustomThread執行個體的Looper對象 public void handleMessage (Message msg) {//3、定義處理訊息的方法 switch(msg.what) { case MSG_HELLO: Log.d("Test", "CustomThread receive msg:" + (String) msg.obj); } } }; Looper.loop();//4、啟動訊息迴圈 } }}
main.xml
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" ><TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello" /><Button android:text="發送訊息" android:id="@+id/send_btn" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button></LinearLayout>
Log列印結果:
原理:
我們看到,為一個線程建立訊息迴圈有四個步驟:
1、 初始化Looper
2、 綁定handler到CustomThread執行個體的Looper對象
3、 定義處理訊息的方法
4、 啟動訊息迴圈
下面我們以這個例子為線索,深入Android原始碼,說明Android Framework是如何建立訊息迴圈,並對訊息進行分發的。
1、 初始化Looper : Looper.prepare()
Looper.java
private static final ThreadLocal sThreadLocal = new ThreadLocal();public static final void prepare() { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper());}
一個線程在調用Looper的靜態方法prepare()時,這個線程會建立一個Looper對象,並放入到線程的局部變數中,而這個變數是不和其他線程共用的(關於ThreadLocal的介紹)。下面我們看看Looper()這個建構函式:
Looper.java
final MessageQueue mQueue;private Looper() { mQueue = new MessageQueue(); mRun = true; mThread = Thread.currentThread(); }
可以看到在Looper的建構函式中,建立了一個訊息佇列對象mQueue,此時,調用Looper. prepare()的線程就建立起一個訊息迴圈的對象(此時還沒開始進行訊息迴圈)。
2、 綁定handler到CustomThread執行個體的Looper對象 : mHandler= new Handler()
Handler.java
final MessageQueue mQueue; final Looper mLooper;public Handler() { 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 = null;}
Handler通過mLooper = Looper.myLooper();綁定到線程的局部變數Looper上去,同時Handler通過mQueue =mLooper.mQueue;獲得線程的訊息佇列。此時,Handler就綁定到建立此Handler對象的線程的訊息佇列上了。
3、定義處理訊息的方法:Override public void handleMessage (Message msg){}
子類需要覆蓋這個方法,實現接受到訊息後的處理方法。
4、啟動訊息迴圈 : Looper.loop()
所有準備工作都準備好了,是時候啟動訊息迴圈了!Looper的靜態方法loop()實現了訊息迴圈。
Looper.java
public static final void loop() { Looper me = myLooper(); 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(); while (true) { Message msg = queue.next(); // might block //if (!me.mRun) { // break; //} if (msg != null) { if (msg.target == null) { // No target is a magic identifier for the quit message. return; } if (me.mLogging!= null) me.mLogging.println( ">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what ); msg.target.dispatchMessage(msg); if (me.mLogging!= null) me.mLogging.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("Looper", "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(); } } }
while(true)體現了訊息迴圈中的“迴圈“,Looper會在迴圈體中調用queue.next()擷取訊息佇列中需要處理的下一條訊息。當msg != null且msg.target != null時,調用msg.target.dispatchMessage(msg);分發訊息,當分發完成後,調用msg.recycle();回收訊息。
msg.target是一個handler對象,表示需要處理這個訊息的handler對象。Handler的void dispatchMessage(Message msg)方法如下:
Handler.java
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); }}
可見,當msg.callback== null 並且mCallback == null時,這個例子是由handleMessage(msg);處理訊息,上面我們說到子類覆蓋這個方法可以實現訊息的具體處理過程。
總結:從上面的分析過程可知,訊息迴圈的核心是Looper,Looper持有訊息佇列MessageQueue對象,一個線程可以把Looper設為該線程的局部變數,這就相當於這個線程建立了一個對應的訊息佇列。Handler的作用就是封裝發送訊息和處理訊息的過程,讓其他線程只需要操作Handler就可以發訊息給建立Handler的線程。由此可以知道,在上一篇《Android非同步處理一:使用Thread+Handler實現非UI線程更新UI介面》中,UI線程在建立的時候就建立了訊息迴圈(在ActivityThread的public static final void main(String[] args)方法中實現),因此我們可以在其他線程給UI線程的handler發送訊息,達到更新UI的目的。
本博文地址:http://blog.csdn.net/mylzc/article/details/6771331 轉載請註明出處
Android非同步處理三:Handler+Looper+MessageQueue深入詳解