Google參考了Windows的消息處理機制,在Android系統中實現了一套類似的消息處理機制。學習Android的消息處理機制,有幾個概念(類)必須了解:
1. Message
消息,理解為線程間通訊的數據單元。例如后台線程在處理數據完畢后需要更新UI,則可發送一條包含更新信息的Message給UI線程。
2. Message Queue
消息隊列,用來存放通過Handler發布的消息,按照先進先出執行。
3. Handler
Handler是Message的主要處理者,負責將Message添加到消息隊列以及對消息隊列中的Message進行處理。
4. Looper
循環器,扮演Message Queue和Handler之間橋梁的角色,循環取出Message Queue里面的Message,並交付給相應的Handler進行處理。
5. 線程
UI thread 通常就是main thread,而Android啟動程序時會替它建立一個Message Queue。
每一個線程里可含有一個Looper對象以及一個MessageQueue數據結構。在你的應用程序里,可以定義Handler的子類別來接收Looper所送出的消息。
運行機理:
每個線程都可以並僅可以擁有一個Looper實例,消息隊列MessageQueue在Looper的構造函數中被創建並且作為成員變量被保存,也就是說MessageQueue相對於線程也是唯一的。Android應用在啟動的時候會默認會為主線程創建一個Looper實例,並借助相關的Handler和Looper里面的MessageQueue完成對Activities、Services、Broadcase Receivers等的管理。而在子線程中,Looper需要通過顯式調用Looper. Prepare()方法進行創建。Prepare方法通過ThreadLocal來保證Looper在線程內的唯一性,如果Looper在線程內已經被創建並且嘗試再度創建"Only one Looper may be created per thread"異常將被拋出。
Handler在創建的時候可以指定Looper,這樣通過Handler的sendMessage()方法發送出去的消息就會添加到指定Looper里面的MessageQueue里面去。在不指定Looper的情況下,Handler綁定的是創建它的線程的Looper。如果這個線程的Looper不存在,程序將拋出"Can't create handler inside thread that has not called Looper.prepare()"。
整個消息處理的大概流程是:1. 包裝Message對象(指定Handler、回調函數和攜帶數據等);2. 通過Handler的sendMessage()等類似方法將Message發送出去;3. 在Handler的處理方法里面將Message添加到Handler綁定的Looper的MessageQueue;4. Looper的loop()方法通過循環不斷從MessageQueue里面提取Message進行處理,並移除處理完畢的Message;5. 通過調用Message綁定的Handler對象的dispatchMessage()方法完成對消息的處理。
在dispatchMessage()方法里,如何處理Message則由用戶指定,三個判斷,優先級從高到低:1. Message里面的Callback,一個實現了Runnable接口的對象,其中run函數做處理工作;2. Handler里面mCallback指向的一個實現了Callback接口的對象,由其handleMessage進行處理;3. 處理消息Handler對象對應的類繼承並實現了其中handleMessage函數,通過這個實現的handleMessage函數處理消息。
Android的消息機制(一)
android 有一種叫消息隊列的說法,這里我們可以這樣理解:假如一個隧道就是一個消息隊列,那么里面的每一部汽車就是一個一個消息,這里我們先忽略掉超車等種種因素,只那么先進隧道的車將會先出,這個機制跟我們android 的消息機制是一樣的。
一、 角色描述
1.Looper:(相當於隧道)一個線程可以產生一個Looper對象,由它來管理此線程里的Message Queue(車隊,消息隧道)。
2.Handler:你可以構造Handler對象來與Looper溝通,以便push新消息到Message Queue里;或者接收Looper(從Message Queue取出)所送來的消息。
3. Message Queue(消息隊列):用來存放線程放入的消息。
4.線程:UI thread通常就是main thread,而Android啟動程序時會替它建立一個Message Queue。
每一個線程里可含有一個Looper對象以及一個MessageQueue數據結構。在你的應用程序里,可以定義Handler的子類別來接收Looper所送出的消息。
在你的Android程序里,新誕生一個線程,或執行(Thread)時,並不會自動建立其Message Loop。
Android里並沒有Global的Message Queue數據結構,例如,不同APK里的對象不能透過Massage Queue來交換訊息(Message)。
例如:線程A的Handler對象可以傳遞消息給別的線程,讓別的線程B或C等能送消息來給線程A(存於A的Message Queue里)。
線程A的Message Queue里的訊息,只有線程A所屬的對象可以處理。
使用Looper.myLooper可以取得當前線程的Looper對象。
使用mHandler = new EevntHandler(Looper.myLooper());可用來構造當前線程的Handler對象;其中,EevntHandler是自已實現的Handler的子類別。
使用mHandler = new EevntHandler(Looper.getMainLooper());可誕生用來處理main線程的Handler對象;其中,EevntHandler是自已實現的Handler的子類別。
這樣描述可能太抽像,下面舉幾個實際的例子來說明:
二、 舉例
1. 同線程內不同組件間的消息傳遞
Looper類用來管理特定線程內對象之間的消息交換(Message Exchange)。你的應用程序可以產生許多個線程。而一個線程可以有許多個組件,這些組件之間常常需要互相交換訊息。如果有這種需要,您可以替線程構造一個Looper對象,來擔任訊息交換的管理工作。Looper對象會建立一個MessageQueue數據結構來存放各對象傳來的消息(包括UI事件或System事件等)。如下圖:
每一個線程里可含有一個Looper對象以及一個MessageQueue數據結構。在你的應用程序里,可以定義Handler的子類別來接收Looper所送出的消息。
同線程不同組件之間的消息傳遞:
publicclass Activity1extends Activityimplements OnClickListener{
Buttonbutton =null;
TextViewtext =null;
@Override
protectedvoid onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity1);
button = (Button)findViewById(R.id.btn);
button.setOnClickListener(this);
text = (TextView)findViewById(R.id.content);
}
publicvoid onClick(View v) {
switch (v.getId()) {
case R.id.btn:
Looper looper = Looper.myLooper();//取得當前線程里的looper
MyHandler mHandler =new MyHandler(looper);//構造一個handler使之可與looper通信
//buton等組件可以由mHandler將消息傳給looper后,再放入messageQueue中,同時mHandler也可以接受來自looper消息
mHandler.removeMessages(0);
String msgStr ="主線程不同組件通信:消息來自button";
Message m = mHandler.obtainMessage(1, 1, 1, msgStr);//構造要傳遞的消息
mHandler.sendMessage(m);//發送消息:系統會自動調用handleMessage方法來處理消息
break;
}
}
privateclass MyHandlerextends Handler{
public MyHandler(Looper looper){
super(looper);
}
@Override
publicvoid handleMessage(Message msg) {//處理消息
text.setText(msg.obj.toString());
}
}
}
說明:
此程序啟動時,當前線程(即主線程, main thread)已誕生了一個Looper對象,並且有了一個MessageQueue數據結構。
looper = Looper.myLooper ();
調用Looper類別的靜態myLooper()函數,以取得目前線程里的Looper對象.
mHandler = new MyHandler (looper);
構造一個MyHandler對象來與Looper溝通。Activity等對象可以藉由MyHandler對象來將消息傳給Looper,然后放入MessageQueue里;MyHandler對象也扮演Listener的角色,可接收Looper對象所送來的消息。
Message m = mHandler.obtainMessage(1, 1, 1, obj);
先構造一個Message對象,並將數據存入對象里。
mHandler.sendMessage(m);
就透過mHandler對象而將消息m傳給Looper,然后放入MessageQueue里。
此時,Looper對象看到MessageQueue里有消息m,就將它廣播出去,mHandler對象接到此訊息時,會呼叫其handleMessage()函數來處理,於是輸出"This my message!"於畫面上,
Android消息處理機制(二)
角色綜述(回顧):
(1)UI thread通常就是main thread,而Android啟動程序時會替它建立一個MessageQueue。
(2)當然需要一個Looper對象,來管理該MessageQueue。
(3)我們可以構造Handler對象來push新消息到Message Queue里;或者接收Looper(從Message Queue取出)所送來的消息。
(4)線程A的Handler對象可以傳遞給別的線程,讓別的線程B或C等能送訊息來給線程A(存於A的Message Queue里)。
(5)線程A的Message Queue里的消息,只有線程A所屬的對象可以處理。
子線程傳遞消息給主線程
publicclass Activity2extends Activityimplements OnClickListener{
Buttonbutton =null;
TextViewtext =null;
MyHandlermHandler =null;
Threadthread ;
@Override
protectedvoid onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity1);
button = (Button)findViewById(R.id.btn);
button.setOnClickListener(this);
text = (TextView)findViewById(R.id.content);
}
publicvoid onClick(View v) {
switch (v.getId()) {
case R.id.btn:
thread =new MyThread();
thread.start();
break;
}
}
privateclass MyHandlerextends Handler{
public MyHandler(Looper looper){
super(looper);
}
@Override
publicvoid handleMessage(Message msg) {//處理消息
text.setText(msg.obj.toString());
}
}
privateclass MyThreadextends Thread{
@Override
publicvoid run() {
Looper curLooper = Looper.myLooper();
Looper mainLooper = Looper.getMainLooper();
String msg ;
if(curLooper==null){
mHandler =new MyHandler(mainLooper);
msg ="curLooper is null";
}else{
mHandler =new MyHandler(curLooper);
msg ="This is curLooper";
}
mHandler.removeMessages(0);
Message m =mHandler.obtainMessage(1, 1, 1, msg);
mHandler.sendMessage(m);
}
}
}
說明:
Android會自動替主線程建立Message Queue。在這個子線程里並沒有建立Message Queue。所以,myLooper值為null,而mainLooper則指向主線程里的Looper。於是,執行到:
mHandler = new MyHandler (mainLooper);
此mHandler屬於主線程。
mHandler.sendMessage(m);
就將m消息存入到主線程的Message Queue里。mainLooper看到Message Queue里有訊息,就會作出處理,於是由主線程執行到mHandler的handleMessage()來處理消息。
用Android線程間通信的Message機制
在Android下面也有多線程的概念,在C/C++中,子線程可以是一個函數,一般都是一個帶有循環的函數,來處理某些數據,優先線程只是一個復雜的運算過程,所以可能不需要while循環,運算完成,函數結束,線程就銷毀。對於那些需要控制的線程,一般我們都是和互斥鎖相互關聯,從而來控制線程的進度,一般我們創建子線程,一種線程是很常見的,那就是帶有消息循環的線程。
消息循環是一個很有用的線程方式,曾經自己用C在Linux下面實現一個消息循環的機制,往消息隊列里添加數據,然后異步的等待消息的返回。當消息隊列為空的時候就會掛起線程,等待新的消息的加入。這是一個很通用的機制。
在Android,這里的線程分為有消息循環的線程和沒有消息循環的線程,有消息循環的線程一般都會有一個Looper,這個事android的新概念。我們的主線程(UI線程)就是一個消息循環的線程。針對這種消息循環的機制,我們引入一個新的機制Handle,我們有消息循環,就要往消息循環里面發送相應的消息,自定義消息一般都會有自己對應的處理,消息的發送和清除,消息的的處理,把這些都封裝在Handle里面,注意Handle只是針對那些有Looper的線程,不管是UI線程還是子線程,只要你有Looper,我就可以往你的消息隊列里面添加東西,並做相應的處理。
但是這里還有一點,就是只要是關於UI相關的東西,就不能放在子線程中,因為子線程是不能操作UI的,只能進行數據、系統等其他非UI的操作。
那么什么情況下面我們的子線程才能看做是一個有Looper的線程呢?我們如何得到它Looper的句柄呢?
Looper.myLooper();獲得當前的Looper
Looper.getMainLooper () 獲得UI線程的Lopper
我們看看Handle的初始化函數,如果沒有參數,那么他就默認使用的是當前的Looper,如果有Looper參數,就是用對應的線程的Looper。
如果一個線程中調用Looper.prepare(),那么系統就會自動的為該線程建立一個消息隊列,然后調用 Looper.loop();之后就進入了消息循環,這個之后就可以發消息、取消息、和處理消息。這個如何發送消息和如何處理消息可以再其他的線程中通過Handle來做,但前提是我們的Hanle知道這個子線程的Looper,但是你如果不是在子線程運行 Looper.myLooper(),一般是得不到子線程的looper的。
public void run() {
synchronized (mLock) {
Looper.prepare();
//do something
}
Looper.loop();
}
所以很多人都是這樣做的:我直接在子線程中新建handle,然后在子線程中發送消息,這樣的話就失去了我們多線程的意義了。
class myThread extends Thread{
private EHandler mHandler ;
public void run() {
Looper myLooper, mainLooper;
myLooper = Looper.myLooper ();
mainLooper = Looper.getMainLooper ();
String obj;
if (myLooper == null ){
mHandler = new EHandler(mainLooper);
obj = "current thread has no looper!" ;
}
else {
mHandler = new EHandler(myLooper);
obj = "This is from current thread." ;
}
mHandler .removeMessages(0);
Message m = mHandler .obtainMessage(1, 1, 1, obj);
mHandler .sendMessage(m);
}
}
可以讓其他的線程來控制我們的handle,可以把 private EHandler mHandler ;放在外面,這樣我們的發消息和處理消息都可以在外面來定義,這樣增加程序代碼的美觀,結構更加清晰。
對如任何的Handle,里面必須要重載一個函數
public void handleMessage(Message msg)
這個函數就是我們的消息處理,如何處理,這里完全取決於你,然后通過 obtainMessage和 sendMessage等來生成和發送消息, removeMessages(0)來清除消息隊列。Google真是太智慧了,這種框架的產生,我們寫代碼更加輕松了。
有的時候,我們的子線程想去改變UI了,這個時候千萬不要再子線程中去修改,獲得UI線程的Looper,然后發送消息即可。
我們看看Goole Music App的源代碼。
在MediaPlaybackActivity.java中,我們可以看一下再OnCreate中的有這樣的兩句:
mAlbumArtWorker = new Worker("album art worker");
mAlbumArtHandler = new AlbumArtHandler(mAlbumArtWorker.getLooper());
很明顯這兩句,是構建了一個子線程。並且這個子線程還是Looper的子線程,這里很牛逼的使用了 mAlbumArtWorker.getLooper()這個函數,因為我們知道,我們能夠得到子線程的Looper的途徑只有一個:就是在子線程中調用 Looper.myLooper (),並且這個函數還要在我們perpare之后調用才能得到正確的Looper,但是他這里用了一個這樣的什么東東 getLooper,不知道它是如何實現的?
這里有一個大概的思路,我們在子線程的的prepare之后調用 myLooper ()這個方法,然后保存在一個成員變量中,這個getLooper就返回這個東西,但是這里會碰到多線程的一個很突出的問題,同步。我們在父線程中調用 mAlbumArtWorker.getLooper(),但是想要這個返回正確的looper就必須要求我們的子線程運行了prepare,但是這個東西實在子線程運行的,我們如何保證呢?
我們看Google是如何實現的?
private class Worker implements Runnable {
private final Object mLock = new Object();
private Looper mLooper;
/**
* Creates a worker thread with the given name. The thread
* then runs a [email=%7B@link]{@link[/email] android.os.Looper}.
* @param name A name for the new thread
*/
Worker(String name) {
Thread t = new Thread(null, this, name);
t.setPriority(Thread.MIN_PRIORITY);
t.start();
synchronized (mLock) {
while (mLooper == null) {
try {
mLock.wait();
} catch (InterruptedException ex) {
}
}
}
}
public Looper getLooper() {
return mLooper;
}
public void run() {
synchronized (mLock) {
Looper.prepare();
mLooper = Looper.myLooper();
mLock.notifyAll();
}
Looper.loop();
}
public void quit() {
mLooper.quit();
}
}
我們知道,一個線程類的構造函數是在主線程中完成的,所以在我們的 Worker的構造函數中我們創佳一個線程,然后讓這個線程運行,這一這個線程的創建是指定一個 Runnabl,這里就是我們的Worker本身,在主線程調用 t.start();,這后,我們子線程已經創建,並且開始執行work的run方法。然后下面的代碼很藝術:
synchronized (mLock) {
while (mLooper == null) {
try {
mLock.wait();
} catch (InterruptedException ex) {
}
}
}
我們開始等待我們的子線程給mLooper賦值,如果不賦值我們就繼續等,然后我們的子線程在運行run方法之后,在給 mLooper賦值之后,通知worker夠着函數中的wait,然后我們的構造函數才能完成,所以我們說:
mAlbumArtWorker = new Worker("album art worker");
這句本身就是阻塞的,它創建了一個子線程,開啟了子線程,並且等待子線程給mLooper賦值,賦值完成之后,這個函數才返回,這樣才能保證我們的子線程的Looper的獲取絕對是正確的,這個構思很有創意。值得借鑒
Android中Handler的使用方法——在子線程中更新界面
本文主要介紹Android的Handler的使用方法。Handler可以發送Messsage和Runnable對象到與其相關聯的線程的消息隊列。每個Handler對象與創建它的線程相關聯,並且每個Handler對象只能與一個線程相關聯。
1. Handler一般有兩種用途:1)執行計划任務,你可以再預定的實現執行某些任務,可以模擬定時器。2)線程間通信。在Android的應用啟動時,會創建一個主線程,主線程會創建一個消息隊列來處理各種消息。當你創建子線程時,你可以再你的子線程中拿到父線程中創建的Handler對象,就可以通過該對象向父線程的消息隊列發送消息了。由於Android要求在UI線程中更新界面,因此,可以通過該方法在其它線程中更新界面。
◆ 通過Runnable在子線程中更新界面的例子
1.○ 在onCreate中創建Handler
public class HandlerTestApp extends Activity {
Handler mHandler;
TextView mText;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mHandler = new Handler();//創建Handler
mText = (TextView) findViewById(R.id.text0);//一個TextView
}
○ 構建Runnable對象,在runnable中更新界面,此處,我們修改了TextView的文字.此處需要說明的是,Runnable對象可以再主線程中創建,也可以再子線程中創建。我們此處是在子線程中創建的。
Runnable mRunnable0 = new Runnable()
{
@Override
public void run() {
mText.setText("This is Update from ohter thread, Mouse DOWN");
}
};
? ○ 創建子線程,在線程的run函數中,我們向主線程的消息隊列發送了一個runnable來更新界面。
private void updateUIByRunnable(){
new Thread()
{
//Message msg = mHandler.obtainMessage();
public void run()
{
//mText.setText("This is Update from ohter thread, Mouse DOWN");//這句將拋出異常
mHandler.post(mRunnable0);
}
}.start();
}
◆ 用Message在子線程中來更新界面
1. 用Message更新界面與Runnable更新界面類似,只是需要修改幾個地方。
○ 實現自己的Handler,對消息進行處理
private class MyHandler extends Handler
{
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch(msg.what)
{
case UPDATE://在收到消息時,對界面進行更新
mText.setText("This update by message");
break;
}
}
}
○ 在新的線程中發送消息
private void updateByMessage()
{
//匿名對象
new Thread()
{
public void run()
{
//mText.setText("This is Update from ohter thread, Mouse DOWN");
//UPDATE是一個自己定義的整數,代表了消息ID
Message msg = mHandler.obtainMessage(UPDATE);
mHandler.sendMessage(msg);
}
}.start();
}
---------------------------------------------------------------------------------------------華麗的分割線----------------------------------------------------------------------------------------------
android的消息處理有三個核心類:Looper,Handler和Message。其實還有一個Message Queue(消息隊列),但是MQ被封裝到Looper里面了,我們不會直接與MQ打交道,因此我沒將其作為核心類。下面一一介紹:
線程的魔法師 Looper
Looper的字面意思是“循環者”,它被設計用來使一個普通線程變成Looper線程。所謂Looper線程就是循環工作的線程。在程序開發中(尤其是GUI開發中),我們經常會需要一個線程不斷循環,一旦有新任務則執行,執行完繼續等待下一個任務,這就是Looper線程。使用Looper類創建Looper線程很簡單:
- <pre name="code" class="java">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。如果你還不清楚什么是ThreadLocal,請參考《理解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線程。
那么,我們如何往MQ上添加消息呢?下面有請Handler!(掌聲~~~)
異步處理大師 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后的效果如下圖:
可以看到,一個線程可以有多個Handler,但是只能有一個Looper!
Handler發送消息
有了handler之后,我們就可以使用 post(Runnable)
,postAtTime(Runnable, long)
,postDelayed(Runnable, long)
,sendEmptyMessage(int)
,sendMessage(Message)
,sendMessageAtTime(Message, long)
和sendMessageDelayed(Message, long)
這些方法向MQ上發送消息了。光看這些API你可能會覺得handler能發兩種消息,一種是Runnable對象,一種是message對象,這是直觀的理解,但其實post發出的Runnable對象最后都被封裝成message對象了,見源碼:
- <pre name="code" class="java"><span style="color:#000000;">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;
- }</span>
其他方法就不羅列了,總之通過handler發出的message有如下特點:
1.message.target為該handler對象,這確保了looper執行到該message時能找到處理它的handler,即loop()方法中的關鍵代碼
msg.target.dispatchMessage(msg);
2.post發出的message,其callback為Runnable對象
Handler處理消息
說完了消息的發送,再來看下handler如何處理消息。消息的處理是通過核心方法dispatchMessage(Message msg)與鈎子方法handleMessage(Message msg)完成的,見源碼
- // 處理消息,該方法由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) {
- }
可以看到,除了handleMessage(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。(過程如圖)
當然,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。
---------------------------------------------------------------------------------------------華麗的分割線----------------------------------------------------------------------------------------------
我們知道,Android應用程序是通過消息來驅動的,即在應用程序的主線程(UI線程)中有一個消息循環,負責處理消息隊列中的消息。我們也知道,Android應用程序是支持多線程的,即可以創建子線程來執行一些計算型的任務,那么,這些子線程能不能像應用程序的主線程一樣具有消息循環呢?這些子線程又能不能往應用程序的主線程中發送消息呢?本文將分析Android應用程序線程消息處理模型,為讀者解答這兩個問題
在開發Android應用程序中,有時候我們需要在應用程序中創建一些常駐的子線程來不定期地執行一些不需要與應用程序界面交互的計算型的任務。如果這些子線程具有消息循環,那么它們就能夠常駐在應用程序中不定期的執行一些計算型任務了:當我們需要用這些子線程來執行任務時,就往這個子線程的消息隊列中發送一個消息,然后就可以在子線程的消息循環中執行我們的計算型任務了。我們在前面一篇文章Android系統默認Home應用程序(Launcher)的啟動過程源代碼分析中,介紹Launcher的啟動過程時,在Step 15(LauncherModel.startLoader)中,Launcher就是通過往一個子線程的消息隊列中發送一個消息(sWorker.post(mLoaderTask)),然后子線程就會在它的消息循環中處理這個消息的時候執行從PackageManagerService中獲取系統中已安裝應用程序的信息列表的任務,即調用Step 16中的LoaderTask.run函數。
在開發Android應用程序中,有時候我們又需要在應用程序中創建一些子線程來執行一些需要與應用程序界面進交互的計算型任務。典型的應用場景是當我們要從網上下載文件時,為了不使主線程被阻塞,我們通常創建一個子線程來負責下載任務,同時,在下載的過程,將下載進度以百分比的形式在應用程序的界面上顯示出來,這樣就既不會阻塞主線程的運行,又能獲得良好的用戶體驗。但是,我們知道,Android應用程序的子線程是不可以操作主線程的UI的,那么,這個負責下載任務的子線程應該如何在應用程序界面上顯示下載的進度呢?如果我們能夠在子線程中往主線程的消息隊列中發送消息,那么問題就迎刃而解了,因為發往主線程消息隊列的消息最終是由主線程來處理的,在處理這個消息的時候,我們就可以在應用程序界面上顯示下載進度了。
上面提到的這兩種情況,Android系統都為我們提供了完善的解決方案,前者可以通過使用HandlerThread類來實現,而后者可以使用AsyncTask類來實現,本文就詳細這兩個類是如何實現的。不過,為了更好地理解HandlerThread類和AsyncTask類的實現,我們先來看看應用程序的主線程的消息循環模型是如何實現的。
1. 應用程序主線程消息循環模型
在前面一篇文章Android應用程序進程啟動過程的源代碼分析一文中,我們已經分析應用程序進程(主線程)的啟動過程了,這里主要是針對它的消息循環模型作一個總結。當運行在Android應用程序框架層中的ActivityManagerService決定要為當前啟動的應用程序創建一個主線程的時候,它會在ActivityManagerService中的startProcessLocked成員函數調用Process類的靜態成員函數start為當前應用程序創建一個主線程:
- public final class ActivityManagerService extends ActivityManagerNative
- implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
- ......
- private final void startProcessLocked(ProcessRecord app,
- String hostingType, String hostingNameStr) {
- ......
- try {
- int uid = app.info.uid;
- int[] gids = null;
- try {
- gids = mContext.getPackageManager().getPackageGids(
- app.info.packageName);
- } catch (PackageManager.NameNotFoundException e) {
- ......
- }
- ......
- int debugFlags = 0;
- ......
- int pid = Process.start("android.app.ActivityThread",
- mSimpleProcessManagement ? app.processName : null, uid, uid,
- gids, debugFlags, null);
- ......
- } catch (RuntimeException e) {
- ......
- }
- }
- ......
- }
這里我們主要關注Process.start函數的第一個參數“android.app.ActivityThread”,它表示要在當前新建的線程中加載android.app.ActivityThread類,並且調用這個類的靜態成員函數main作為應用程序的入口點。ActivityThread類定義在frameworks/base/core/java/android/app/ActivityThread.java文件中:
- public final class ActivityThread {
- ......
- public static final void main(String[] args) {
- ......
- Looper.prepareMainLooper();
- ......
- ActivityThread thread = new ActivityThread();
- thread.attach(false);
- ......
- Looper.loop();
- ......
- thread.detach();
- ......
- }
- ......
- }
在這個main函數里面,除了創建一個ActivityThread實例外,就是在進行消息循環了。
在進行消息循環之前,首先會通過Looper類的靜態成員函數prepareMainLooper為當前線程准備一個消息循環對象。Looper類定義在frameworks/base/core/java/android/os/Looper.java文件中:
- public class Looper {
- ......
- // sThreadLocal.get() will return null unless you've called prepare().
- private static final ThreadLocal sThreadLocal = new ThreadLocal();
- ......
- private static Looper mMainLooper = null;
- ......
- public static final void prepare() {
- if (sThreadLocal.get() != null) {
- throw new RuntimeException("Only one Looper may be created per thread");
- }
- sThreadLocal.set(new Looper());
- }
- ......
- public static final void prepareMainLooper() {
- prepare();
- setMainLooper(myLooper());
- ......
- }
- private synchronized static void setMainLooper(Looper looper) {
- mMainLooper = looper;
- }
- public synchronized static final Looper getMainLooper() {
- return mMainLooper;
- }
- ......
- public static final Looper myLooper() {
- return (Looper)sThreadLocal.get();
- }
- ......
- }
Looper類的靜態成員函數prepareMainLooper是專門應用程序的主線程調用的,應用程序的其它子線程都不應該調用這個函數來在本線程中創建消息循環對象,而應該調用prepare函數來在本線程中創建消息循環對象,下一節我們介紹一個線程類HandlerThread 時將會看到。
為什么要為應用程序的主線程專門准備一個創建消息循環對象的函數呢?這是為了讓其它地方能夠方便地通過Looper類的getMainLooper函數來獲得應用程序主線程中的消息循環對象。獲得應用程序主線程中的消息循環對象又有什么用呢?一般就是為了能夠向應用程序主線程發送消息了。
在prepareMainLooper函數中,首先會調用prepare函數在本線程中創建一個消息循環對象,然后將這個消息循環對象放在線程局部變量sThreadLocal中:
- sThreadLocal.set(new Looper());
接着再將這個消息循環對象通過調用setMainLooper函數來保存在Looper類的靜態成員變量mMainLooper中:
- mMainLooper = looper;
這樣,其它地方才可以調用getMainLooper函數來獲得應用程序主線程中的消息循環對象。
消息循環對象創建好之后,回到ActivityThread類的main函數中,接下來,就是要進入消息循環了:
- Looper.loop();
Looper類具體是如何通過loop函數進入消息循環以及處理消息隊列中的消息,可以參考前面一篇文章Android應用程序消息處理機制(Looper、Handler)分析,這里就不再分析了,我們只要知道ActivityThread類中的main函數執行了這一步之后,就為應用程序的主線程准備好消息循環就可以了。
2. 應用程序子線程消息循環模型
在Java框架中,如果我們想在當前應用程序中創建一個子線程,一般就是通過自己實現一個類,這個類繼承於Thread類,然后重載Thread類的run函數,把我們想要在這個子線程執行的任務都放在這個run函數里面實現。最后實例這個自定義的類,並且調用它的start函數,這樣一個子線程就創建好了,並且會調用這個自定義類的run函數。但是當這個run函數執行完成后,子線程也就結束了,它沒有消息循環的概念。
前面說過,有時候我們需要在應用程序中創建一些常駐的子線程來不定期地執行一些計算型任務,這時候就可以考慮使用Android系統提供的HandlerThread類了,它具有創建具有消息循環功能的子線程的作用。
HandlerThread類實現在frameworks/base/core/java/android/os/HandlerThread.java文件中,這里我們通過使用情景來有重點的分析它的實現。
在前面一篇文章Android系統默認Home應用程序(Launcher)的啟動過程源代碼分析中,我們分析了Launcher的啟動過程,其中在Step 15(LauncherModel.startLoader)和Step 16(LoaderTask.run)中,Launcher會通過創建一個HandlerThread類來實現在一個子線程加載系統中已經安裝的應用程序的任務:
- public class LauncherModel extends BroadcastReceiver {
- ......
- private LoaderTask mLoaderTask;
- private static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
- static {
- sWorkerThread.start();
- }
- private static final Handler sWorker = new Handler(sWorkerThread.getLooper());
- ......
- public void startLoader(Context context, boolean isLaunching) {
- ......
- synchronized (mLock) {
- ......
- // Don't bother to start the thread if we know it's not going to do anything
- if (mCallbacks != null && mCallbacks.get() != null) {
- ......
- mLoaderTask = new LoaderTask(context, isLaunching);
- sWorker.post(mLoaderTask);
- }
- }
- }
- ......
- private class LoaderTask implements Runnable {
- ......
- public void run() {
- ......
- keep_running: {
- ......
- // second step
- if (loadWorkspaceFirst) {
- ......
- loadAndBindAllApps();
- } else {
- ......
- }
- ......
- }
- ......
- }
- ......
- }
- ......
- }
在這個LauncherModel類中,首先創建了一個HandlerThread對象:
- private static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
接着調用它的start成員函數來啟動一個子線程:
- static {
- sWorkerThread.start();
- }
接着還通過這個HandlerThread對象的getLooper函數來獲得這個子線程中的消息循環對象,並且使用這個消息循環創建對象來創建一個Handler:
- private static final Handler sWorker = new Handler(sWorkerThread.getLooper());
有了這個Handler對象sWorker之后,我們就可以往這個子線程中發送消息,然后在處理這個消息的時候執行加載系統中已經安裝的應用程序的任務了,在startLoader函數中:
- mLoaderTask = new LoaderTask(context, isLaunching);
- sWorker.post(mLoaderTask);
這里的mLoaderTask是一個LoaderTask對象,它實現了Runnable接口,因此,可以把這個LoaderTask對象作為參數傳給sWorker.post函數。在sWorker.post函數里面,會把這個LoaderTask對象封裝成一個消息,並且放入這個子線程的消息隊列中去。當這個子線程的消息循環處理這個消息的時候,就會調用這個LoaderTask對象的run函數,因此,我們就可以在LoaderTask對象的run函數中通過調用loadAndBindAllApps來執行加載系統中已經安裝的應用程序的任務了。
了解了HanderThread類的使用方法之后,我們就可以重點地來分析它的實現了:
- public class HandlerThread extends Thread {
- ......
- private Looper mLooper;
- public HandlerThread(String name) {
- super(name);
- ......
- }
- ......
- public void run() {
- ......
- Looper.prepare();
- synchronized (this) {
- mLooper = Looper.myLooper();
- ......
- }
- ......
- Looper.loop();
- ......
- }
- public Looper getLooper() {
- ......
- return mLooper;
- }
- ......
- }
首先我們看到的是,Handler類繼承了Thread類,因此,通過它可以在應用程序中創建一個子線程,其次我們看到在它的run函數中,會進入一個消息循環中,因此,這個子線程可以常駐在應用程序中,直到它接收收到一個退出消息為止。
在run函數中,首先是調用Looper類的靜態成員函數prepare來准備一個消息循環對象:
- Looper.prepare();
然后通過Looper類的myLooper成員函數將這個子線程中的消息循環對象保存在HandlerThread類中的成員變量mLooper中:
- mLooper = Looper.myLooper();
這樣,其它地方就可以方便地通過它的getLooper函數來獲得這個消息循環對象了,有了這個消息循環對象后,就可以往這個子線程的消息隊列中發送消息,通知這個子線程執行特定的任務了。
最在這個run函數通過Looper類的loop函數進入消息循環中:
- Looper.loop();
這樣,一個具有消息循環的應用程序子線程就准備就緒了。
HandlerThread類的實現雖然非常簡單,當然這得益於Java提供的Thread類和Android自己本身提供的Looper類,但是它的想法卻非常周到,為應用程序開發人員提供了很大的方便。
3. 需要與UI交互的應用程序子線程消息模型
前面說過,我們開發應用程序的時候,經常中需要創建一個子線程來在后台執行一個特定的計算任務,而在這個任務計算的過程中,需要不斷地將計算進度或者計算結果展現在應用程序的界面中。典型的例子是從網上下載文件,為了不阻塞應用程序的主線程,我們開辟一個子線程來執行下載任務,子線程在下載的同時不斷地將下載進度在應用程序界面上顯示出來,這樣做出來程序就非常友好。由於子線程不能直接操作應用程序的UI,因此,這時候,我們就可以通過往應用程序的主線程中發送消息來通知應用程序主線程更新界面上的下載進度。因為類似的這種情景在實際開發中經常碰到,Android系統為開發人員提供了一個異步任務類(AsyncTask)來實現上面所說的功能,即它會在一個子線程中執行計算任務,同時通過主線程的消息循環來獲得更新應用程序界面的機會。
為了更好地分析AsyncTask的實現,我們先舉一個例子來說明它的用法。在前面一篇文章Android系統中的廣播(Broadcast)機制簡要介紹和學習計划中,我們開發了一個應用程序Broadcast,其中使用了AsyncTask來在一個線程在后台在執行計數任務,計數過程通過廣播(Broadcast)來將中間結果在應用程序界面上顯示出來。在這個例子中,使用廣播來在應用程序主線程和子線程中傳遞數據不是最優的方法,當時只是為了分析Android系統的廣播機制而有意為之的。在本節內容中,我們稍微這個例子作一個簡單的修改,就可以通過消息的方式來將計數過程的中間結果在應用程序界面上顯示出來。
為了區別Android系統中的廣播(Broadcast)機制簡要介紹和學習計划一文中使用的應用程序Broadcast,我們將本節中使用的應用程序命名為Counter。首先在Android源代碼工程中創建一個Android應用程序工程,名字就為Counter,放在packages/experimental目錄下。關於如何獲得Android源代碼工程,請參考在Ubuntu上下載、編譯和安裝Android最新源代碼一文;關於如何在Android源代碼工程中創建應用程序工程,請參考在Ubuntu上為Android系統內置Java應用程序測試Application Frameworks層的硬件服務一文。這個應用程序工程定義了一個名為shy.luo.counter的package,這個例子的源代碼主要就是實現在這個目錄下的Counter.java文件中:
- package shy.luo.counter;
- import android.app.Activity;
- import android.content.ComponentName;
- import android.content.Context;
- import android.content.Intent;
- import android.content.IntentFilter;
- import android.os.Bundle;
- import android.os.AsyncTask;
- import android.util.Log;
- import android.view.View;
- import android.view.View.OnClickListener;
- import android.widget.Button;
- import android.widget.TextView;
- public class Counter extends Activity implements OnClickListener {
- private final static String LOG_TAG = "shy.luo.counter.Counter";
- private Button startButton = null;
- private Button stopButton = null;
- private TextView counterText = null;
- private AsyncTask<Integer, Integer, Integer> task = null;
- private boolean stop = false;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- startButton = (Button)findViewById(R.id.button_start);
- stopButton = (Button)findViewById(R.id.button_stop);
- counterText = (TextView)findViewById(R.id.textview_counter);
- startButton.setOnClickListener(this);
- stopButton.setOnClickListener(this);
- startButton.setEnabled(true);
- stopButton.setEnabled(false);
- Log.i(LOG_TAG, "Main Activity Created.");
- }
- @Override
- public void onClick(View v) {
- if(v.equals(startButton)) {
- if(task == null) {
- task = new CounterTask();
- task.execute(0);
- startButton.setEnabled(false);
- stopButton.setEnabled(true);
- }
- } else if(v.equals(stopButton)) {
- if(task != null) {
- stop = true;
- task = null;
- startButton.setEnabled(true);
- stopButton.setEnabled(false);
- }
- }
- }
- class CounterTask extends AsyncTask<Integer, Integer, Integer> {
- @Override
- protected Integer doInBackground(Integer... vals) {
- Integer initCounter = vals[0];
- stop = false;
- while(!stop) {
- publishProgress(initCounter);
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- initCounter++;
- }
- return initCounter;
- }
- @Override
- protected void onProgressUpdate(Integer... values) {
- super.onProgressUpdate(values);
- String text = values[0].toString();
- counterText.setText(text);
- }
- @Override
- protected void onPostExecute(Integer val) {
- String text = val.toString();
- counterText.setText(text);
- }
- };
- }
這個計數器程序很簡單,它在界面上有兩個按鈕Start和Stop。點擊Start按鈕時,便會創建一個CounterTask實例task,然后調用它的execute函數就可以在應用程序中啟動一個子線程,並且通過調用這個CounterTask類的doInBackground函數來執行計數任務。在計數的過程中,會通過調用publishProgress函數來將中間結果傳遞到onProgressUpdate函數中去,在onProgressUpdate函數中,就可以把中間結果顯示在應用程序界面了。點擊Stop按鈕時,便會通過設置變量stop為true,這樣,CounterTask類的doInBackground函數便會退出循環,然后將結果返回到onPostExecute函數中去,在onPostExecute函數,會把最終計數結果顯示在用程序界面中。
在這個例子中,我們需要注意的是:
A. CounterTask類繼承於AsyncTask類,因此它也是一個異步任務類;
B. CounterTask類的doInBackground函數是在后台的子線程中運行的,這時候它不可以操作應用程序的界面;
C. CounterTask類的onProgressUpdate和onPostExecute兩個函數是應用程序的主線程中執行,它們可以操作應用程序的界面。
關於C這一點的實現原理,我們在后面會分析到,這里我們先完整地介紹這個例子,以便讀者可以參考做一下實驗。
接下來我們再看看應用程序的配置文件AndroidManifest.xml:
- <?xml version="1.0" encoding="utf-8"?>
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="shy.luo.counter"
- android:versionCode="1"
- android:versionName="1.0">
- <application android:icon="@drawable/icon" android:label="@string/app_name">
- <activity android:name=".Counter"
- android:label="@string/app_name">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
- </manifest>
這個配置文件很簡單,我們就不介紹了。
再來看應用程序的界面文件,它定義在res/layout/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"
- android:gravity="center">
- <LinearLayout
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:layout_marginBottom="10px"
- android:orientation="horizontal"
- android:gravity="center">
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginRight="4px"
- android:gravity="center"
- android:text="@string/counter">
- </TextView>
- <TextView
- android:id="@+id/textview_counter"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:text="0">
- </TextView>
- </LinearLayout>
- <LinearLayout
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:gravity="center">
- <Button
- android:id="@+id/button_start"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:text="@string/start">
- </Button>
- <Button
- android:id="@+id/button_stop"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:text="@string/stop" >
- </Button>
- </LinearLayout>
- </LinearLayout>
這個界面配置文件也很簡單,等一下我們在模擬器把這個應用程序啟動起來后,就可以看到它的截圖了。
應用程序用到的字符串資源文件位於res/values/strings.xml文件中:
- <?xml version="1.0" encoding="utf-8"?>
- <resources>
- <string name="app_name">Counter</string>
- <string name="counter">Counter: </string>
- <string name="start">Start Counter</string>
- <string name="stop">Stop Counter</string>
- </resources>
最后,我們還要在工程目錄下放置一個編譯腳本文件Android.mk:
- LOCAL_PATH:= $(call my-dir)
- include $(CLEAR_VARS)
- LOCAL_MODULE_TAGS := optional
- LOCAL_SRC_FILES := $(call all-subdir-java-files)
- LOCAL_PACKAGE_NAME := Counter
- include $(BUILD_PACKAGE)
接下來就要編譯了。有關如何單獨編譯Android源代碼工程的模塊,以及如何打包system.img,請參考如何單獨編譯Android源代碼中的模塊一文。
執行以下命令進行編譯和打包:
- USER-NAME@MACHINE-NAME:~/Android$ mmm packages/experimental/Counter
- USER-NAME@MACHINE-NAME:~/Android$ make snod
這樣,打包好的Android系統鏡像文件system.img就包含我們前面創建的Counter應用程序了。
再接下來,就是運行模擬器來運行我們的例子了。關於如何在Android源代碼工程中運行模擬器,請參考在Ubuntu上下載、編譯和安裝Android最新源代碼一文。
執行以下命令啟動模擬器:
- USER-NAME@MACHINE-NAME:~/Android$ emulator
最后我們就可以在Launcher中找到Counter應用程序圖標,把它啟動起來,點擊Start按鈕,就會看到應用程序界面上的計數器跑起來了:
這樣,使用AsyncTask的例子就介紹完了,下面,我們就要根據上面對AsyncTask的使用情況來重點分析它的實現了。
AsyncTask類定義在frameworks/base/core/java/android/os/AsyncTask.java文件中:
- public abstract class AsyncTask<Params, Progress, Result> {
- ......
- private static final BlockingQueue<Runnable> sWorkQueue =
- new LinkedBlockingQueue<Runnable>(10);
- private static final ThreadFactory sThreadFactory = new ThreadFactory() {
- private final AtomicInteger mCount = new AtomicInteger(1);
- public Thread newThread(Runnable r) {
- return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
- }
- };
- ......
- private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
- MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);
- private static final int MESSAGE_POST_RESULT = 0x1;
- private static final int MESSAGE_POST_PROGRESS = 0x2;
- private static final int MESSAGE_POST_CANCEL = 0x3;
- private static final InternalHandler sHandler = new InternalHandler();
- private final WorkerRunnable<Params, Result> mWorker;
- private final FutureTask<Result> mFuture;
- ......
- public AsyncTask() {
- mWorker = new WorkerRunnable<Params, Result>() {
- public Result call() throws Exception {
- ......
- return doInBackground(mParams);
- }
- };
- mFuture = new FutureTask<Result>(mWorker) {
- @Override
- protected void done() {
- Message message;
- Result result = null;
- try {
- result = get();
- } catch (InterruptedException e) {
- android.util.Log.w(LOG_TAG, e);
- } catch (ExecutionException e) {
- throw new RuntimeException("An error occured while executing doInBackground()",
- e.getCause());
- } catch (CancellationException e) {
- message = sHandler.obtainMessage(MESSAGE_POST_CANCEL,
- new AsyncTaskResult<Result>(AsyncTask.this, (Result[]) null));
- message.sendToTarget();
- return;
- } catch (Throwable t) {
- throw new RuntimeException("An error occured while executing "
- + "doInBackground()", t);
- }
- message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
- new AsyncTaskResult<Result>(AsyncTask.this, result));
- message.sendToTarget();
- }
- };
- }
- ......
- public final Result get() throws InterruptedException, ExecutionException {
- return mFuture.get();
- }
- ......
- public final AsyncTask<Params, Progress, Result> execute(Params... params) {
- ......
- mWorker.mParams = params;
- sExecutor.execute(mFuture);
- return this;
- }
- ......
- protected final void publishProgress(Progress... values) {
- sHandler.obtainMessage(MESSAGE_POST_PROGRESS,
- new AsyncTaskResult<Progress>(this, values)).sendToTarget();
- }
- private void finish(Result result) {
- ......
- onPostExecute(result);
- ......
- }
- ......
- private static class InternalHandler extends Handler {
- @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
- @Override
- public void handleMessage(Message msg) {
- AsyncTaskResult result = (AsyncTaskResult) msg.obj;
- switch (msg.what) {
- case MESSAGE_POST_RESULT:
- // There is only one result
- result.mTask.finish(result.mData[0]);
- break;
- case MESSAGE_POST_PROGRESS:
- result.mTask.onProgressUpdate(result.mData);
- break;
- case MESSAGE_POST_CANCEL:
- result.mTask.onCancelled();
- break;
- }
- }
- }
- private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
- Params[] mParams;
- }
- private static class AsyncTaskResult<Data> {
- final AsyncTask mTask;
- final Data[] mData;
- AsyncTaskResult(AsyncTask task, Data... data) {
- mTask = task;
- mData = data;
- }
- }
- }
從AsyncTask的實現可以看出,當我們第一次創建一個AsyncTask對象時,首先會執行下面靜態初始化代碼創建一個線程池sExecutor:
- private static final BlockingQueue<Runnable> sWorkQueue =
- new LinkedBlockingQueue<Runnable>(10);
- private static final ThreadFactory sThreadFactory = new ThreadFactory() {
- private final AtomicInteger mCount = new AtomicInteger(1);
- public Thread newThread(Runnable r) {
- return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
- }
- };
- ......
- private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
- MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);
這里的ThreadPoolExecutor是Java提供的多線程機制之一,這里用的構造函數原型為:
- ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
- BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory)
各個參數的意義如下:
corePoolSize -- 線程池的核心線程數量
maximumPoolSize -- 線程池的最大線程數量
keepAliveTime -- 若線程池的線程數數量大於核心線程數量,那么空閑時間超過keepAliveTime的線程將被回收
unit -- 參數keepAliveTime使用的時間單位
workerQueue -- 工作任務隊列
threadFactory -- 用來創建線程池中的線程
簡單來說,ThreadPoolExecutor的運行機制是這樣的:每一個工作任務用一個Runnable對象來表示,當我們要把一個工作任務交給這個線程池來執行的時候,就通過調用ThreadPoolExecutor的execute函數來把這個工作任務加入到線程池中去。此時,如果線程池中的線程數量小於corePoolSize,那么就會調用threadFactory接口來創建一個新的線程並且加入到線程池中去,再執行這個工作任務;如果線程池中的線程數量等於corePoolSize,但是工作任務隊列workerQueue未滿,則把這個工作任務加入到工作任務隊列中去等待執行;如果線程池中的線程數量大於corePoolSize,但是小於maximumPoolSize,並且工作任務隊列workerQueue已經滿了,那么就會調用threadFactory接口來創建一個新的線程並且加入到線程池中去,再執行這個工作任務;如果線程池中的線程量已經等於maximumPoolSize了,並且工作任務隊列workerQueue也已經滿了,這個工作任務就被拒絕執行了。
創建好了線程池后,再創建一個消息處理器:
- private static final InternalHandler sHandler = new InternalHandler();
注意,這行代碼是在應用程序的主線程中執行的,因此,這個消息處理器sHandler內部引用的消息循環對象looper是應用程序主線程的消息循環對象,消息處理器的實現機制具體可以參考前面一篇文章Android應用程序消息處理機制(Looper、Handler)分析。
AsyncTask類的靜態初始化代碼執行完成之后,才開始創建AsyncTask對象,即執行AsyncTask類的構造函數:
- public AsyncTask() {
- mWorker = new WorkerRunnable<Params, Result>() {
- public Result call() throws Exception {
- ......
- return doInBackground(mParams);
- }
- };
- mFuture = new FutureTask<Result>(mWorker) {
- @Override
- protected void done() {
- Message message;
- Result result = null;
- try {
- result = get();
- } catch (InterruptedException e) {
- android.util.Log.w(LOG_TAG, e);
- } catch (ExecutionException e) {
- throw new RuntimeException("An error occured while executing doInBackground()",
- e.getCause());
- } catch (CancellationException e) {
- message = sHandler.obtainMessage(MESSAGE_POST_CANCEL,
- new AsyncTaskResult<Result>(AsyncTask.this, (Result[]) null));
- message.sendToTarget();
- return;
- } catch (Throwable t) {
- throw new RuntimeException("An error occured while executing "
- + "doInBackground()", t);
- }
- message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
- new AsyncTaskResult<Result>(AsyncTask.this, result));
- message.sendToTarget();
- }
- };
- }
在AsyncTask類的構造函數里面,主要是創建了兩個對象,分別是一個WorkerRunnable對象mWorker和一個FutureTask對象mFuture。
WorkerRunnable類實現了Runnable接口,此外,它的內部成員變量mParams用於保存從AsyncTask對象的execute函數傳進來的參數列表:
- private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
- Params[] mParams;
- }
FutureTask類也實現了Runnable接口,所以它可以作為一個工作任務通過調用AsyncTask類的execute函數添加到sExecuto線程池中去:
- public final AsyncTask<Params, Progress, Result> execute(Params... params) {
- ......
- mWorker.mParams = params;
- sExecutor.execute(mFuture);
- return this;
- }
這里的FutureTask對象mFuture是用來封裝前面的WorkerRunnable對象mWorker。當mFuture加入到線程池中執行時,它調用的是mWorker對象的call函數:
- mWorker = new WorkerRunnable<Params, Result>() {
- public Result call() throws Exception {
- ......
- return doInBackground(mParams);
- }
- };
在call函數里面,會調用AsyncTask類的doInBackground函數來執行真正的任務,這個函數是要由AsyncTask的子類來實現的,注意,這個函數是在應用程序的子線程中執行的,它不可以操作應用程序的界面。
我們可以通過mFuture對象來操作當前執行的任務,例如查詢當前任務的狀態,它是正在執行中,還是完成了,還是被取消了,如果是完成了,還可以通過它獲得任務的執行結果,如果還沒有完成,可以取消任務的執行。
當工作任務mWorker執行完成的時候,mFuture對象中的done函數就會被被調用,根據任務的完成狀況,執行相應的操作,例如,如果是因為異常而完成時,就會拋異常,如果是正常完成,就會把任務執行結果封裝成一個AsyncTaskResult對象:
- private static class AsyncTaskResult<Data> {
- final AsyncTask mTask;
- final Data[] mData;
- AsyncTaskResult(AsyncTask task, Data... data) {
- mTask = task;
- mData = data;
- }
- }
其中,成員變量mData保存的是任務執行結果,而成員變量mTask指向前面我們創建的AsyncTask對象。
最后把這個AsyncTaskResult對象封裝成一個消息,並且通過消息處理器sHandler加入到應用程序主線程的消息隊列中:
- message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
- new AsyncTaskResult<Result>(AsyncTask.this, result));
- message.sendToTarget();
這個消息最終就會在InternalHandler類的handleMessage函數中處理了:
- private static class InternalHandler extends Handler {
- @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
- @Override
- public void handleMessage(Message msg) {
- AsyncTaskResult result = (AsyncTaskResult) msg.obj;
- switch (msg.what) {
- case MESSAGE_POST_RESULT:
- // There is only one result
- result.mTask.finish(result.mData[0]);
- break;
- ......
- }
- }
- }
在這個函數里面,最終會調用前面創建的這個AsyncTask對象的finish函數來進一步處理:
- private void finish(Result result) {
- ......
- onPostExecute(result);
- ......
- }
這個函數調用AsyncTask類的onPostExecute函數來進一步處理,AsyncTask類的onPostExecute函數一般是要由其子類來重載的,注意,這個函數是在應用程序的主線程中執行的,因此,它可以操作應用程序的界面。
在任務執行的過程當中,即執行doInBackground函數時候,可能通過調用publishProgress函數來將中間結果封裝成一個消息發送到應用程序主線程中的消息隊列中去:
- protected final void publishProgress(Progress... values) {
- sHandler.obtainMessage(MESSAGE_POST_PROGRESS,
- new AsyncTaskResult<Progress>(this, values)).sendToTarget();
- }
這個消息最終也是由InternalHandler類的handleMessage函數來處理的:
- private static class InternalHandler extends Handler {
- @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
- @Override
- public void handleMessage(Message msg) {
- AsyncTaskResult result = (AsyncTaskResult) msg.obj;
- switch (msg.what) {
- ......
- case MESSAGE_POST_PROGRESS:
- result.mTask.onProgressUpdate(result.mData);
- break;
- ......
- }
- }
- }
這里它調用前面創建的AsyncTask對象的onPorgressUpdate函數來進一步處理,這個函數一般是由AsyncTask的子類來實現的,注意,這個函數是在應用程序的主線程中執行的,因此,它和前面的onPostExecute函數一樣,可以操作應用程序的界面。
這樣,AsyncTask類的主要實現就介紹完了,結合前面開發的應用程序Counter來分析,會更好地理解它的實現原理。
至此,Android應用程序線程消息循環模型就分析完成了,理解它有利於我們在開發Android應用程序時,能夠充分利用多線程的並發性來提高應用程序的性能以及獲得良好的用戶體驗。
轉自:http://www.cnblogs.com/qingblog/archive/2012/06/27/2566021.html