在安卓程序中,经常会有一些耗时的操作例如下载,网络访问等,如果将这些放在主线程执行,会很耗时,这样可能会导致一个异常 叫ANR异常(Application Not Responding)将会阻塞UI线程,从而会导致程序无响应。因此我们会将一些耗时操作放在子线程进行,但是由于android的UI操作并不是线程安全的,因此如果多个线程同时操作UI的话,会导致线程安全问题,因此android制订了一条规则,只允许UI线程(即主线程)进行UI操作,那么我们如何知道子线程何时操作完成呢?例如子线程下载好图片以后通知主线程进行视图更新。
Message简介
子线程任务完成以后需要发送消息给主线程,这个消息就是Message的一个实例,定义Message时需要定义三个实例变量
1、what int型消息代码,用来描述消息
2、obj 随消息发送的用户指定对象
3、target 处理该消息的Handler
创建Message的方法
1、new一个就行
2、使用Handler.obtainMessage(...)该方法可以使我们从公共循环池中获取到一个Message实例,效率会提高
因此我们使用该方法会好些
MessageQueue简介
MessageQueue(消息队列)用来存放Message,采用先进先出的方式发出Message,
Looper简介
Looper叫做消息循环,他会不断的检查 MessageQueue上是否有新消息,然后抓取消息,完成指定的任务。每个线程有且只有一个Looper对象,用来管理MessageQueue。主线程也有Looper,它会自动创建,主线程的所有工作都是由他的Looper完成的。Looper主要有两个方法
1、Looper.prepare 用以启用Looper
2、Looper.loop 让Looper开始工作,从消息队列中抓取处理消息
Handler简介
要处理消息以及消息指定的任务时就需要用到Handler,她可以发出新消息到MessageQueue上,也可以读取Looper从MessageQueue上获取的消息。一个Handler仅与一个Looper相关联,一个Message也仅与一个目标Hander相关联。
Handler发送消息的方法:
Looper获取到消息后,会交由消息的目标(即消息的target属性)处理,消息一般是在Handler.handleMessage(...)方法中处理
关系图示
在简单了解过这些知识后,我们写一个小程序,利用了Handler与Message。该应用的主界面仅有一个ImageView故代码不再给出,主要功能是开启一个子线程模拟下载图片,然后下载完成后在UI线程上进行更新。
主要代码
声明handler,模拟下载到的图片的资源Id数组
<span style="font-size:18px;"><pre name="code" class="java"><span style="font-size:18px;"><span style="white-space:pre"> </span>private ImageView mImageView; // 定义一个Handler private Handler mHandler; private int mIndex; // 存放照片资源id的数组 int[] imageIds = new int[] { R.drawable.pic1, R.drawable.pic2, R.drawable.pic3, R.drawable.pic4 };</span></span>
模拟下载图片的方法
<span style="font-size:18px;">// 模拟下载的操作,随机生成一个数字 public int virtualDown() { Random ran = new Random(); int value = ran.nextInt(5); return value; }</span>
使用TimerTask开启一个下载的子线程,获取到消息,并发送
<span style="font-size:18px;"><span style="font-size:18px;">// 开启一个TimerTask模拟下载的子线程 new Timer().schedule(new TimerTask() { @Override public void run() { Bundle bundle = new Bundle(); bundle.putInt("value", virtualDown()); // 获取到消息 // Message msg = new Message(); Message msg = mHandler.obtainMessage(); msg.what = 0x1233; msg.setData(bundle); // 发送消息,并将生成的数字传递过去 mHandler.sendMessage(msg); } }, 0, 2000); }</span></span>
生成handle实例并在handleMessage中处理消息
<span style="font-size:18px;"><span style="white-space:pre"> </span>mHandler = new Handler() { @Override // 处理消息的方法 public void handleMessage(Message msg) { // what属性是消息的标识,用来区分每个消息 if (msg.what == 0x1233) { mIndex = msg.getData().getInt("value"); // 设置图片的显示,这是在主线程进行的 switch (mIndex) { case 0: mImageView.setImageResource(imageIds[0]); break; case 1: mImageView.setImageResource(imageIds[1]); break; case 2: mImageView.setImageResource(imageIds[2]); break; case 3: mImageView.setImageResource(imageIds[3]); break; } } } };</span>
运行程序我们就可以看到主界面的图片每隔两秒就会变化一次了,当然图片是事先准备好的,并非下载的,过段时间博主会实现真正的下载。
在上面的程序中为什么没有见到Looper呢?这是因为主线程在启动过程中自动创建了Looper。而子线程默认是不带Looper的,所以我们就需要自己创建Looper,这时候就需要用到上述的两个方法,Looper.prepare和Looper.loop启动Looper了,官方的API Demo的示例为:
class LooperThread extends Thread { public Handler mHandler; public void run() { Looper.prepare(); mHandler = new Handler() { public void handleMessage(Message msg) { // process incoming messages here } }; Looper.loop(); } }