Handler訊息傳遞機制(子執行緒中傳遞new Handler和主執行緒中new Handle傳遞訊息)
> 子執行緒中更新UI
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
// Toast.makeText(MainActivity.this,"一口仨饃",Toast.LENGTH_SHORT).show();
new AlertDialog.Builder(MainActivity.this)
.setTitle("Game of Thrones")
.setMessage("winter is coming")
.setPositiveButton("yes,my lord", null)
.show();
Looper.loop();
}
}).start();
> 那麼什麼情況下面我們的子執行緒才能看做是一個有Looper的執行緒呢?我們如何得到它Looper的控制代碼呢?
Looper.myLooper();獲得當前的Looper
Looper.getMainLooper () 獲得UI執行緒的Lopper
如 果一個執行緒中呼叫Looper.prepare(),那麼系統就會自動的為該執行緒建立一個訊息佇列,然後呼叫 Looper.loop();
> Android非同步訊息機制中主要涉及到:Thread、Handler、MessageQueue、Looper,在整個機制中它們扮演著不同的角色也承擔著各自的不同責任。
深入探討Android非同步精髓Handler - http://blog.csdn.net/lfdfhl/article/details/53332936
Google官方建議開發人員使用Handler實現非同步重新整理UI,我們在平常的工作中也很好地採納了這個提議:首先在主執行緒中建立Handler,然後在子執行緒中利用
handler.sendMessage(message)傳送訊息至主執行緒,最終訊息在handleMessage(Message msg) {}得到相應的處理。
> HandlerThread 繼承自Thread,內部封裝了Looper。
首先Handler和HandlerThread的主要區別是:Handler與Activity在同一個執行緒中,HandlerThread與Activity不在同一個執行緒,而是別外新的執行緒中(Handler中不能做耗時的操作)。
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Handler.Callback;
import android.os.HandlerThread;
import android.os.Message;
public class MainActivity extends Activity {
HandlerThread handlerThread = new HandlerThread("test");
Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handlerThread.start();
System.out.println("handlerThread.id=" + handlerThread.getId());
//post(runnable),只是直接運行了run(),run()內ThreadId與UIThread是一樣的
// handler = new Handler();
//post(runnable),將runnable執行在handlerThread中,這是非UIThread的
handler = new Handler(handlerThread.getLooper(), new Callback() {
@Override
public boolean handleMessage(Message msg) {
System.out.println("receive message.whatA=" + msg.what);
if (msg.what == 1) {
return true;//不再向外層傳遞
} else {
return false; //外層的handleMessage() 繼續執行
}
}
}) {
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
super.handleMessage(msg);
System.out.println("receive message.whatB=" + msg.what);
}
};
handler.post(new Runnable() {
@Override
public void run() {
System.out.println("handler_post_cur_id="+Thread.currentThread().getId());
handler.sendEmptyMessage(1);
handler.sendEmptyMessage(2);
}
});
}
}
執行結果:
01-23 07:12:31.590: I/System.out(12386): cur_id=1
01-23 07:12:31.590: I/System.out(12386): handlerThread.id=1866
01-23 07:12:31.590: I/System.out(12386): handler_post_cur_id=1866
01-23 07:12:31.600: I/System.out(12386): receive message.whatA=1
01-23 07:12:31.600: I/System.out(12386): receive message.whatA=2
01-23 07:12:31.600: I/System.out(12386): receive message.whatB=2
》》》在android中,Activity,Service屬於主執行緒,在主執行緒中才能更新UI,如toast等。其他執行緒中不能直接使用更新UI,這時可以使用Handler來處理,Handler可以在Activity和Service中定義。
熟悉Windows程式設計的朋友可能知道Windows程式是訊息驅動的,並且有全域性的訊息迴圈系統。而Android應用程式也是訊息驅動的,按道理來說也應該提供訊息迴圈機制。實際上谷歌參考了Windows的訊息迴圈機制,也在Android系統中實現了訊息迴圈機制。Android通過 Looper、Handler來實現訊息迴圈機制,Android訊息迴圈是針對執行緒的(每個執行緒都可以有自己的訊息佇列和訊息迴圈)。本文深入介紹一下 Android訊息處理系統原理。
Android系統中Looper負責管理執行緒的訊息佇列和訊息迴圈,具體實現請參考上面的Looper的原始碼。 可以通過Loop.myLooper()得到當前執行緒的Looper物件(這是我們一再強調的重點),通過呼叫Loop.getMainLooper()可以獲得當前程序的主執行緒的Looper物件。
前面提到Android系統的訊息佇列和訊息迴圈都是針對具體執行緒的,一個執行緒可以存在(當然也可以不存在)一個訊息佇列和一個訊息迴圈(Looper)。特定執行緒的訊息只能分發給本執行緒,不能進行跨執行緒,跨程序通訊。但是建立的工作執行緒預設是沒有訊息迴圈和訊息佇列的,如果想讓該執行緒具有訊息佇列和訊息迴圈功能,需要線上程中首先呼叫Looper.prepare()來建立訊息佇列,然後呼叫Looper.loop()進入訊息迴圈。
如下例 所示:
class LooperThread extends Thread {
public HandlermHandler;
public void run() {
Looper.prepare();
mHandler = newHandler(Looper.myLooper()) {
public voidhandleMessage(Message msg) {
// processincoming messages here
}
};
Looper.loop();
}
}
這樣你的執行緒就具有了訊息處理機制了,在Handler中進行訊息處理。
Activity是一個UI執行緒,運行於主執行緒中,Android系統在啟動的時候會為Activity建立一個訊息佇列和訊息迴圈(Looper)。詳細實現請參考ActivityThread.java檔案。 Handler的作用是把訊息加入特定的(Looper)訊息佇列中,並分發和處理該訊息佇列中的訊息。構造Handler的時候可以指定一個Looper物件,如果不指定則利用當前執行緒的Looper建立。
一個Activity中可以建立多個工作執行緒或者其他的元件,如果這些執行緒或者元件把他們的訊息放入Activity的主執行緒訊息佇列,那麼該訊息就會在主執行緒中處理了。因為主執行緒一般負責介面的更新操作,並且Android系統中的weget不是執行緒安全的,所以這種方式可以很好的實現 Android介面更新。在Android系統中這種方式有著廣泛的運用。
那麼另外一個執行緒怎樣把訊息放入主執行緒的訊息佇列呢?答案是通過Handle物件,只要Handler物件以主執行緒中建立,並用主執行緒的的Looper建立,那麼呼叫 Handler的sendMessage等介面,將會把訊息放入佇列都將是放入主執行緒的訊息佇列(這是handler與looper共用一個MessageQueue的結果)。並且將會在Handler主執行緒中呼叫該handler 的handleMessage介面來處理訊息。
這裡面涉及到執行緒同步問題,請先參考如下例子來理解Handler物件的執行緒模型:
(1)首先建立MyHandler工程。
(2)在MyHandler.java中加入如下的程式碼:
package com.simon;
import android.app.Activity;
import android.os.Bundle;
import android.os.Message;
import android.util.Log;
import android.os.Handler;
public class MyHandler extends Activity {
static final StringTAG = "Handler";
Handler h = newHandler(){ //建立處理物件,並定義handleMessage()函式
public voidhandleMessage (Message msg)
{
switch(msg.what)
{
case HANDLER_TEST:
Log.d(TAG, "Thehandler thread id = " + Thread.currentThread().getId() + "\n");
break;
}
}
};
static final int HANDLER_TEST = 1;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "The main thread id =" + Thread.currentThread().getId() + "\n");
newmyThread().start(); //建立子執行緒物件,並啟動子執行緒
setContentView(R.layout.main);
}
class myThread extends Thread
{
//這個執行緒計較簡單,Run()函式只是呼叫了在主執行緒中生成的處理類物件的一個傳送訊息的函式,沒有單獨生成looper進入迴圈
public void run()
{
Message msg = new Message();
msg.what =HANDLER_TEST;
h.sendMessage(msg);
Log.d(TAG, "The worker threadid = " + Thread.currentThread().getId() + "\n");
}
}
}
在這個例子中我們主要是列印,這種處理機制各個模組的所處的執行緒情況。如下是機器執行結果:
DEBUG/Handler(302):The main thread id = 1
DEBUG/Handler(302): The worker threadid = 8
DEBUG/Handler(302): The handler threadid = 1
我們可以看出訊息處理是在主執行緒中處理的,在訊息處理函式中可以安全的呼叫主執行緒中的任何資源,包括重新整理介面。工作執行緒和主執行緒執行在不同的執行緒中,所以必須要注意這兩個執行緒間的競爭關係。
上例中,你可能注意到在工作執行緒中訪問了主執行緒handler物件,並在呼叫handler的物件向訊息佇列加入了一個訊息。這個過程中會不會出現訊息佇列資料不一致問題呢?答案是handler物件不會出問題,因為handler物件管理的Looper物件是執行緒安全的,不管是加入訊息到訊息佇列 和從佇列讀出訊息都是有同步物件保護的。上例中沒有修改handler物件,所以handler物件不可能會出現資料不一致的問題。
通過上面的分析,我們可以得出如下結論:
1、如果通過工作執行緒重新整理介面,推薦使用handler物件來實現。
2、注意工作執行緒和主執行緒之間的競爭關係。推薦handler物件在主執行緒中構造完成(並且啟動工作執行緒之後不要再修改之,否則會出現資料不一致),然後在工作執行緒中可以放心的呼叫傳送訊息SendMessage等介面。
3、除了2所述的hanlder物件之外的任何主執行緒的成員變數如果在工作執行緒中呼叫,仔細考慮執行緒同步問題。如果有必要需要加入同步物件保護該變數。
4、handler物件的handleMessage介面將會在主執行緒中呼叫。在這個函式可以放心的呼叫主執行緒中任何變數和函式,進而完成更新UI的任務。
5、Android很多API也利用Handler這種執行緒特性,作為一種回撥函式的變種,來通知呼叫者。這樣Android框架就可以在其執行緒中將訊息傳送到呼叫者的執行緒訊息佇列之中,不用擔心執行緒同步的問題。
6. 下面再從執行緒的角度看一下,訊息處理過程中參與的執行緒,以及這些執行緒之間的同步。顯然的,這裡有執行緒HandlerThread的參 與,而且Looper::loop()就是執行在HandlerThread的run()方法裡,也就是在HandlerThread裡執行,這也就是說 訊息的分發處理和執行是在HandlerThread的執行緒上下文中。另外,還有至少一個執行緒存在,也就是建立了HandlerThread的執行緒B,以 及執行訊息傳送的執行緒C,B和C有可能是同一執行緒。
訊息的傳送是在另外一個執行緒裡,就是因為有了多個執行緒的存在,才有了執行緒的同步操作。可再次關注一下實現執行緒同步的Java原語wait()/notify()。
》1.在子執行緒中new Handler(),需使用Looper.prepare() , Looper.loop()。
public class LoopThread implements Runnable {
public Handler mHandler = null;
@Override
public void run() {
Looper.prepare();
mHandler = new Handler() { //在Handler執行緒中 ,多個Handler都可以共享同一Looper和MessageQueue了
public void handleMessage(Message msg) {
String result = msg ;
//完成了獲取北京天氣的操作;
Log.i("test", "handler"+result);
}
};
Looper.loop();
}
}
如果執行緒中使用Looper.prepare()和Looper.loop()建立了訊息佇列就可以讓訊息處理在該執行緒中完成。
---》子執行緒中不能更新UI,Toast是不算UI更新的,但是會報錯Only the original thread that created a view hierarchy can touch its views.
Android中的Looper類,是用來封裝訊息迴圈和訊息佇列的一個類,用於在android執行緒中進行訊息處理。handler其實可以看做是一個工具類,用來向訊息佇列中插入訊息的。
(1) Looper類用來為一個執行緒開啟一個訊息迴圈。 預設情況下android中新誕生的執行緒是沒有開啟訊息迴圈的。(主執行緒除外,主執行緒系統會自動為其建立Looper物件,開啟訊息迴圈。) Looper物件通過MessageQueue來存放訊息和事件。一個執行緒只能有一個Looper,對應一個MessageQueue。
(2) 通常是通過Handler物件來與Looper進行互動的。Handler可看做是Looper的一個介面,用來向指定的Looper傳送訊息及定義處理方法。 預設情況下Handler會與其被定義時所線上程的Looper繫結,比如,Handler在主執行緒中定義,那麼它是與主執行緒的Looper繫結。 mainHandler = new Handler() 等價於new Handler(Looper.myLooper()). Looper.myLooper():獲取當前程序的looper物件,類似的 Looper.getMainLooper() 用於獲取主執行緒的Looper物件。
(3) 在非主執行緒中直接new Handler() 會報如下的錯誤: E/AndroidRuntime( 6173): Uncaught handler: thread Thread-8 exiting due to uncaught exception E/AndroidRuntime( 6173): java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare() 原因是非主執行緒中預設沒有建立Looper物件,需要先呼叫Looper.prepare()啟用Looper。
(4) Looper.loop(); 讓Looper開始工作,從訊息佇列裡取訊息,處理訊息。
注意:寫在Looper.loop()之後的程式碼不會被執行,這個函式內部應該是一個迴圈,當呼叫mHandler.getLooper().quit()後,loop才會中止,其後的程式碼才能得以執行。
(5) 基於以上知識,可實現主執行緒給子執行緒(非主執行緒)傳送訊息。
把下面例子中的mHandler宣告成類成員,在主執行緒通過mHandler傳送訊息即可。 Android官方文件中Looper的介紹: Class used to run a message loop for a thread. Threads by default do not have a message loop associated with them; to create one, call prepare() in the thread that is to run the loop, and then loop() to have it process messages until the loop is stopped.
Most interaction with a message loop is through the Handler class.
This is a typical example of the implementation of a Looper thread, using the separation of prepare() and loop() to create an initial Handler to communicate with the Looper.
>>> android HandlerThread使用小例
之前研究過handler 和 looper 訊息佇列,不過android裡的handler不是另外開啟執行緒來執行的,還是在主UI執行緒中,如果想另啟執行緒的話需要用到HandlerThread來實現。在使用HandlerThread的時候需要實現CallBack介面以重寫handlerMessage方法,在handlerMessage方法中來處理自己的邏輯。下來給出一個小例子程式。
layout檔案很簡單,就一個按鈕來啟動HanlderTread執行緒
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:orientation="vertical" >
- <TextView
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:text="@string/hello" />
- <Button
- android:id="@+id/handlerThreadBtn"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="startHandlerThread" />
- </LinearLayout>
Activity程式碼如下:
- package com.tayue;
- import android.app.Activity;
- import android.os.Bundle;
- import android.os.Handler;
- import android.os.Handler.Callback;
- import android.os.HandlerThread;
- import android.os.Message;
- import android.view.View;
- import android.view.View.OnClickListener;
- import android.widget.Button;
- /**
- *
- * @author xionglei
- *
- */
- public class TestHandlerActivity extends Activity implements OnClickListener{
- public Button handlerThreadBTN;
- MyHandlerThread handlerThread;
- Handler handler;
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- //列印UI執行緒的名稱
- System.out.println("onCreate CurrentThread = " + Thread.currentThread().getName());
- setContentView(R.layout.main);
- handlerThreadBTN = (Button) findViewById(R.id.handlerThreadBtn);
- handlerThreadBTN.setOnClickListener(this);
- handlerThread = new MyHandlerThread("myHanler");
- handlerThread.start();
- //注意: 這裡必須用到handler的這個構造器,因為需要把callback傳進去,從而使自己的HandlerThread的handlerMessage來替換掉Handler原生的handlerThread
- handler = new Handler(handlerThread.getLooper(), handlerThread);
- }
- @Override
- public void onClick(View v) {
- //點選按鈕後來開啟執行緒
- handler.sendEmptyMessage(1);
- }
- privateclass MyHandlerThread extends HandlerThread implements Callback {
- public MyHandlerThread(String name) {
- super(name);
- }
- @Override
- public boolean handleMessage(Message msg) {
- //列印執行緒的名稱
- System.out.println(" handleMessage CurrentThread = " + Thread.currentThread().getName());
- return true;
- }
- }
- }
點選按鈕,列印的日誌如下(這裡點選了3次)
07-06 09:32:48.776: I/System.out(780): onCreate CurrentThread = main ,在主執行緒中
07-06 09:32:55.076: I/System.out(780): handleMessage CurrentThread = myHanler ,在Handler執行緒中
07-06 09:32:58.669: I/System.out(780): handleMessage CurrentThread = myHanler
07-06 09:33:03.476: I/System.out(780): handleMessage CurrentThread = myHanler
HandlerThread就這麼簡單。
>>>> 當然 android自己也有非同步執行緒的handler,就是AsyncTask,這個類就是封裝了HandlerThread 和handler來實現非同步多執行緒的操作的。
同樣可以這樣使用:
- private boolean iscancel = false; //使用者手動取消登入的標誌位
- handlerThread = new HandlerThread("myHandlerThread");
- handlerThread.start();
- handler = new MyHandler(handlerThread.getLooper());
- // 將要執行的執行緒物件新增到執行緒佇列中
- handler.post(new Runnable() {
- @Override
- public void run() {
- Message message = handler.obtainMessage();
- UserBean user = Bbs.getInstance().Login(username, password);//耗時任務
- Bundle b = new Bundle();
- b.putSerializable("user", user);
- message.setData(b);
- message.sendToTarget(); //或使用 handler.sendMessage(message);
- }
- });
- class MyHandler extends Handler {
- public MyHandler(Looper looper) {
- super(looper);
- }
- @Override
- public void handleMessage(Message msg) {
- if(iscancel == false){
- // 操作UI執行緒的程式碼
- Bundle b = msg.getData();
- UserBean user = (UserBean)b.get("user");
- ......
- }
- }
- }
》2. Looper,Handler,Message這幾個類的原始碼
Looper的字面意思是“迴圈者”,它被設計用來使一個普通執行緒變成Looper執行緒。所謂Looper執行緒就是迴圈工作的執行緒。在程式開發中(尤其是GUI開發中),我們經常會需要一個執行緒不斷迴圈,一旦有新任務則執行,執行完繼續等待下一個任務,這就是Looper執行緒。使用Looper類建立Looper執行緒很簡單:
public class LooperThread extends Thread {
@Override
public void run() {
// 將當前執行緒初始化為Looper執行緒
Looper.prepare();
// ...其他處理,如例項化handler
// 開始迴圈處理訊息佇列
Looper.loop();
}
}
通過上面兩行核心程式碼,你的執行緒就升級為Looper執行緒了!!!
1)Looper.prepare()
現在你的執行緒中有一個Looper物件,它的內部維護了一個訊息佇列MQ。注意,一個Thread只能有一個Looper物件,為什麼呢?咱們來看原始碼。
public class Looper {
// 每個執行緒中的Looper物件其實是一個ThreadLocal,即執行緒本地儲存(TLS)物件
private static final ThreadLocal sThreadLocal = new ThreadLocal();
// Looper內的訊息佇列
final MessageQueue mQueue;
// 當前執行緒
Thread mThread;
// 。。。其他屬性
// 每個Looper物件中有它的訊息佇列,和它所屬的執行緒
private Looper() {
mQueue = new MessageQueue();
mRun = true;
mThread = Thread.currentThread();
}
// 我們呼叫該方法會在呼叫執行緒的TLS中建立Looper物件
public static final void prepare() {
if (sThreadLocal.get() != null) {
// 試圖在有Looper的執行緒中再次建立Looper將丟擲異常
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper());
}
// 其他方法
}
通過原始碼,prepare()背後的工作方式一目瞭然,其核心就是將looper物件定義為ThreadLocal。
2)Looper.loop()
呼叫loop方法後,Looper執行緒就開始真正工作了,它不斷從自己的MQ中取出隊頭的訊息(也叫任務)執行。其原始碼分析如下:
public static final void loop() {
Looper me = myLooper(); //得到當前執行緒Looper
MessageQueue queue = me.mQueue; //得到當前looper的MQ
// 這兩行沒看懂= = 不過不影響理解
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
// 開始迴圈
while (true) {
Message msg = queue.next(); // 取出message
if (msg != null) {
if (msg.target == null) {
// message沒有target為結束訊號,退出迴圈
return;
}
// 日誌。。。
if (me.mLogging!= null) me.mLogging.println(
">>>>> Dispatching to " + msg.target + " "
+ msg.callback + ": " + msg.what
);
// 非常重要!將真正的處理工作交給message的target,即後面要講的handler
msg.target.dispatchMessage(msg);
// 還是日誌。。。
if (me.mLogging!= null) me.mLogging.println(
"<<<<< Finished to " + msg.target + " "
+ msg.callback);
// 下面沒看懂,同樣不影響理解
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);
}
// 回收message資源
msg.recycle();
}
}
}
除了prepare()和loop()方法,Looper類還提供了一些有用的方法,比如
Looper.myLooper()得到當前執行緒looper物件:
public static final Looper myLooper() {
// 在任意執行緒呼叫Looper.myLooper()返回的都是那個執行緒的looper
return (Looper)sThreadLocal.get();
}
getThread()得到looper物件所屬執行緒:
public Thread getThread() {
return mThread;
}
quit()方法結束looper迴圈:
public void quit() {
// 建立一個空的message,它的target為NULL,表示結束迴圈訊息
Message msg = Message.obtain();
// 發出訊息
mQueue.enqueueMessage(msg, 0);
}
到此為止,你應該對Looper有了基本的瞭解,總結幾點:
1.每個執行緒有且最多隻能有一個Looper物件,它是一個ThreadLocal
2.Looper內部有一個訊息佇列,loop()方法呼叫後執行緒開始不斷從佇列中取出訊息執行
3.Looper使一個執行緒變成Looper執行緒。
非同步處理大師 Handler
什麼是handler?handler扮演了往MQ上新增訊息和處理訊息的角色(只處理由自己發出的訊息),即通知MQ它要執行一個任務(sendMessage),並在loop到自己的時候執行該任務(handleMessage),整個過程是非同步的。handler建立時會關聯一個looper,預設的構造方法將關聯當前執行緒的looper,不過這也是可以set的。預設的構造方法:
public class handler {
final MessageQueue mQueue; // 關聯的MQ
final Looper mLooper; // 關聯的looper
final Callback mCallback;
// 其他屬性
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());
}
}
// 預設將關聯當前執行緒的looper
mLooper = Looper.myLooper();
// looper不能為空,即該預設的構造方法只能在looper執行緒中使用
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
// 重要!!!直接把關聯looper的MQ作為自己的MQ,因此它的訊息將傳送到關聯looper的MQ上
mQueue = mLooper.mQueue;
mCallback = null;
}
// 其他方法
}
下面我們就可以為之前的LooperThread類加入Handler:
public class LooperThread extends Thread {
private Handler handler1;
private Handler handler2;
@Override
public void run() {
// 將當前執行緒初始化為Looper執行緒
Looper.prepare();
// 例項化兩個handler
handler1 = new Handler();
handler2 = new Handler();
// 開始迴圈處理訊息佇列
Looper.loop();
}
}
一個執行緒可以有多個Handler,但是隻能有一個Looper!
Handler傳送訊息
// 此方法用於向關聯的MQ上傳送Runnable物件,它的run方法將在handler關聯的looper執行緒中執行
public final boolean post(Runnable r)
{
// 注意getPostMessage(r)將runnable封裝成message
return sendMessageDelayed(getPostMessage(r), 0);
}
private final Message getPostMessage(Runnable r) {
Message m = Message.obtain(); //得到空的message
m.callback = r; //將runnable設為message的callback,
return m;
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis)
{
boolean sent = false;
MessageQueue queue = mQueue;
if (queue != null) {
msg.target = this; // message的target必須設為該handler!
sent = queue.enqueueMessage(msg, uptimeMillis);
}
else {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
}
return sent;
}
總之通過handler發出的message有如下特點:
1.message.target為該handler物件,這確保了looper執行到該message時能找到處理它的handler,即loop()方法中的關鍵程式碼
msg.target.dispatchMessage(msg);
2.post發出的message,其callback為Runnable物件
Handler處理訊息
// 處理訊息,該方法由looper呼叫
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
// 如果message設定了callback,即runnable訊息,處理callback!
handleCallback(msg);
} else {
// 如果handler本身設定了callback,則執行callback
if (mCallback != null) {
/* 這種方法允許讓activity等來實現Handler.Callback介面,避免了自己編寫handler重寫handleMessage方法。見http://alex-yang-xiansoftware-com.iteye.com/blog/850865 */
if (mCallback.handleMessage(msg)) {
return;
}
}
// 如果message沒有callback,則呼叫handler的鉤子方法handleMessage
handleMessage(msg);
}
}
// 處理runnable訊息
private final void handleCallback(Message message) {
message.callback.run(); //直接呼叫run方法!
}
// 由子類實現的鉤子方法
public void handleMessage(Message msg) {
}
可以看到,除了(Message msg)和Runnable物件的run方法由開發者實現外(實現具體邏輯),handler的內部工作機制對開發者是透明的。這正是handler API設計的精妙之處!
Handler的用處
我在小標題中將handler描述為“非同步處理大師”,這歸功於Handler擁有下面兩個重要的特點:
1.handler可以在任意執行緒傳送訊息,這些訊息會被新增到關聯的MQ上。
2.handler是在它關聯的looper執行緒中處理訊息的。
這就解決了android最經典的不能在其他非主執行緒中更新UI的問題。android的主執行緒也是一個looper執行緒(looper在android中運用很廣),我們在其中建立的handler預設將關聯主執行緒MQ。因此,利用handler的一個solution就是在activity中建立handler並將其引用傳遞給worker thread,
worker thread執行完任務後使用handler傳送訊息通知activity更新UI。(過程如圖)
下面給出sample程式碼,僅供參考:
public class TestDriverActivity extends Activity {
private TextView textview;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
textview = (TextView) findViewById(R.id.textview);
// 建立並啟動工作執行緒
Thread workerThread = new Thread(new SampleTask(new MyHandler()));
workerThread.start();
}
public void appendText(String msg) {
textview.setText(textview.getText() + "\n" + msg);
}
class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
String result = msg.getData().getString("message");
// 更新UI
appendText(result);
}
}
}
public class SampleTask implements Runnable {
private static final String TAG = SampleTask.class.getSimpleName();
Handler handler;
public SampleTask(Handler handler) {
super();
this.handler = handler;
}
@Override
public void run() {
try { // 模擬執行某項任務,下載等
Thread.sleep(5000);
// 任務完成後通知activity更新UI
Message msg = prepareMessage("task completed!");
// message將被新增到主執行緒的MQ中
handler.sendMessage(msg);
} catch (InterruptedException e) {
Log.d(TAG, "interrupted!");
}
}
private Message prepareMessage(String str) {
Message result = handler.obtainMessage();
Bundle data = new Bundle();
data.putString("message", str);
result.setData(data);
return result;
}
}
當然,handler能做的遠遠不僅如此,由於它能post Runnable物件,它還能與Looper配合實現經典的Pipeline Thread(流水線執行緒)模式。請參考此文《Android Guts: Intro to Loopers and Handlers》
封裝任務 Message
在整個訊息處理機制中,message又叫task,封裝了任務攜帶的資訊和處理該任務的handler。message的用法比較簡單,這裡不做總結了。但是有這麼幾點需要注意(待補充):
1.儘管Message有public的預設構造方法,但是你應該通過Message.obtain()來從訊息池中獲得空訊息物件,以節省資源。
2.如果你的message只需要攜帶簡單的int資訊,請優先使用Message.arg1和Message.arg2來傳遞資訊,這比用Bundle更省記憶體
3.擅用message.what來標識資訊,以便用不同方式處理message.
· Handler的處理過程執行在建立Handler的執行緒裡
· 一個Looper對應一個MessageQueue
· 一個執行緒對應一個Looper
· 一個Looper可以對應多個Handler
· 不確定當前執行緒時,更新UI時儘量呼叫post方法