参考:
如下的例子,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多线程中有如下的原则:
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由以下部分组成:
在Android 异步通信:手把手教你使用Handler消息传递机制(含实例Demo)有如下的一张图:
在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)
对于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是容纳任意数据的容器。生产线程发送消息给 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();
}
}
}
}