Android多線程——Handler
參考:
如下的例子,button的點擊事件中,在子線程中修改UI:
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Log.w("MainActivity", Thread.currentThread().getName());
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
textView.setText("Changed From Thread");
}
}).start();
}
});
此時Logcat會提示如下的錯誤:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
在Android多線程中有如下的原則:
- 不要阻塞UI線程
- 不要在UI線程之外訪問UI組件
1.Handler.sendXXXMessage()等方法
在上面的Activity中定義一個Handler,重寫handleMessage
Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg){
if(msg.what == 0x123){
text.setText("Task Done!!");
}
}
};
然後將工作線程的代碼改爲下面的樣子
mRunnable = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5000);//模擬耗時任務
} catch (InterruptedException e) {
e.printStackTrace();
}
mHandler.sendEmptyMessage(0x123);//關於發消息的方法有很多,比如sendMessage(Message msg),sendMessageDelayed(Message msg, long delayMills)等等,可按具體需求選擇,這裏不作擴展
}
};
一個線程只有一個
Looper
, 而一個Looper
持有一個MessageQueue
, 當調用Looper.prepare()
時,Looper
就與當前線程關聯起來了(在Activity
裏沒有顯示調用Looper.prepare()
是因爲系統自動在主線程裏幫我們調用了),而Handler
是與Looper
的線程是綁定的,查看Handler
類的源碼可以發現它幾個構造函數,其中有接收一個Looper
參數的,也有不接收Looper
參數的,從上面的代碼上看,我們沒有爲Handler
指定Looper
,那麼Handler
就默認更當前線程(即主線程)的Looper
關聯起來了,之所以囉嗦那麼多就是因爲這決定了Handler.handlerMessage(msg)
方法體裏的代碼到底在哪個線程裏執行,我們再梳理一下,Looper.prepare
調用決定了Looper
與哪個線程關聯,間接決定了與這個Looper
相關聯的Handler.handlerMessage(msg)
方法體裏的代碼執行的線程。(太囉嗦了)
現在回到上面的代碼,我們的Handler
是在主線程裏的定義的,所以也默認跟主線程的Looper
相關聯,即handlerMessage
方法的代碼會在UI
線程執行,因此更新TextView就不會報錯了。下面這張圖是弄清handlerMessage(msg)
方法體裏的代碼的執行線程的思路
2.Handler.post(Runnable)
只要將上面代碼中的
mHandler.sendEmptyMessage(0x123);
改成
mHandler.post(new Runnable() {
@Override
public void run() {
text.setText("Task Done!!");
}
});
Handler由以下部分組成:
- Handler
- Message
- Message Queue
- Looper
在Android 異步通信:手把手教你使用Handler消息傳遞機制(含實例Demo)有如下的一張圖:
Handler
在Activity中定義一個Handler
Handler允許你發送和處理與線程的MessageQueue關聯的Message和
Runnable
對象。每個Handler實例都與一個線程和該線程的消息隊列(message queue)相關聯。當創建一個新的Handler時,它被綁定到創建它的線程/消息隊列,它將messages和runnables傳遞給該消息隊列並執行
There are two main uses for a Handler: (1) to schedule messages and runnables to be executed at some point in the future; and (2) to enqueue an action to be performed on a different thread than your own.
Handler有兩個主要用途:(1)安排messages和runnables在將來的某個時刻執行; (2)在不同於自己的線程上執行的操作。
Handler
可以把一個Message
對象或者Runnable
對象壓入到消息隊列中,進而在UI線程中獲取Message
或者執行Runnable
對象,所以Handler
把壓入消息隊列有兩大體系,Post
和sendMessage
:
Post
:Post
允許把一個Runnable
對象入隊到消息隊列中。它的方法有:post(Runnable)
、postAtTime(Runnable,long)
、postDelayed(Runnable,long)
sendMessage
:sendMessage
允許把一個包含消息數據的Message
對象壓入到消息隊列中。它的方法有:sendEmptyMessage(int)
、sendMessage(Message)
、sendMessageAtTime(Message,long)
、sendMessageDelayed(Message,long)
Post
對於Handler
的Post
方式來說,它會傳遞一個Runnable
對象到消息隊列中,在這個Runnable
對象中,重寫run()
方法。一般在這個run()
方法中寫入需要在UI線程上的操作
btnMes1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 新啓動一個子線程
new Thread(new Runnable() {
@Override
public void run() {
// tvMessage.setText("...");
// 以上操作會報錯,無法再子線程中訪問UI組件,UI組件的屬性必須在UI線程中訪問
// 使用post方式修改UI組件tvMessage的Text屬性
handler.post(new Runnable() {
@Override
public void run() {
tvMessage.setText("使用Handler.post在工作線程中發送一段執行到消息隊列中,在主線程中執行。");
}
});
}
}).start();
}
});
Message
Message是容納任意數據的容器。生產線程發送消息給 Handler,Handler 將消息加入到消息隊列中。
如果對於一般的數據,Message
提供了getData()
和setData()
方法來獲取與設置數據,其中操作的數據是一個Bundle
對象
還有另外一種方式在Message
中傳遞對象,那就是使用Message
自帶的obj
屬性傳值,它是一個Object
類型,所以可以傳遞任意類型的對象,Message
自帶的有如下幾個屬性:
int arg1
:參數一,用於傳遞不復雜的數據,複雜數據使用setData()傳遞。int arg2
:參數二,用於傳遞不復雜的數據,複雜數據使用setData()傳遞。Object obj
:傳遞一個任意的對象。int what
:定義的消息碼,一般用於設定消息的標誌。
對於Message
對象,一般並不推薦直接使用它的構造方法得到,而是建議通過使用Message.obtain()
這個靜態的方法或者Handler.obtainMessage()
獲取
package com.bgxt.datatimepickerdemo;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import android.app.Activity;
import android.app.ProgressDialog;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
public class HandlerMessageActivity1 extends Activity {
private Button btnDown;
private ImageView ivImage;
private static String image_path = "http://ww4.sinaimg.cn/bmiddle/786013a5jw1e7akotp4bcj20c80i3aao.jpg";
private ProgressDialog dialog;
private static int IS_FINISH = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.asynctask_activity);
btnDown = (Button) findViewById(R.id.btnDown);
ivImage = (ImageView) findViewById(R.id.ivSinaImage);
dialog = new ProgressDialog(this);
dialog.setTitle("提示信息");
dialog.setMessage("正在下載,請稍後...");
dialog.setCancelable(false);
btnDown.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new MyThread()).start();
dialog.show();
}
});
}
private Handler handler = new Handler() {
// 在Handler中獲取消息,重寫handleMessage()方法
@Override
public void handleMessage(Message msg) {
// 判斷消息碼是否爲1
if(msg.what==IS_FINISH){
byte[] data=(byte[])msg.obj;
Bitmap bmp=BitmapFactory.decodeByteArray(data, 0, data.length);
ivImage.setImageBitmap(bmp);
dialog.dismiss();
}
}
};
public class MyThread implements Runnable {
@Override
public void run() {
HttpClient httpClient = new DefaultHttpClient();
HttpGet httpGet = new HttpGet(image_path);
HttpResponse httpResponse = null;
try {
httpResponse = httpClient.execute(httpGet);
if (httpResponse.getStatusLine().getStatusCode() == 200) {
byte[] data = EntityUtils.toByteArray(httpResponse
.getEntity());
// 獲取一個Message對象,設置what爲1
Message msg = Message.obtain();
msg.obj = data;
msg.what = IS_FINISH;
// 發送這個消息到消息隊列中
handler.sendMessage(msg);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}