Android的消息传递机制是另一种形式的“事件处理”,这种机制主要是为了解决Android应用的多线程问题——Android平台只允许UI线程修改Activity里的组件,这样就会导致新启动的线程无法动态改变界面组件的属性值。但实际开发中,需要让新的线程能够改变界面组件的属性值,这就需要借助于Handler的消息传递机制来实现了。
Handler类简介:
Handler类主要作用:
- 在新启动的线程中发送消息
- 在主线程中获取、处理消息
通过回调的方法——重写Handler类中处理消息的方法来让主线程能适时地出口i新启动线程所发送的消息。,当新启动的线程发送消息时,消息会发送到与之关联的MessageQueue,而Handler会不断地从MessageQueue中获取并处理消息——这将导致Handler类中处理消息的方法被回调。
Handler类中包含如下用于发送、处理消息的方法:
- void handleMessage(Message msg):处理消息的方法。通常被重写。
- final boolean hasMessages(int what):检查消息队列中是否包含what属性为指定值的消息。
- final boolean hasMessages(int what,Object object):检查消息队列中是否包含what属性为指定值且object属性为指定对象的消息。
- 多个重载的Message obtainMessage():获取消息。
- sendEmptyMessage(int what):发送空消息。
- final boolean sendEmptyMessageDelayed(int what,long delayMillis):指定多少毫秒后发送空消息。
- final boolean sendMessage(Message msg ):立即发送消息。
- final boolean sendMessageDelayed(Message msg,long delayMillis):指定多少毫秒后发送消息。
下面的一个小例子可以通过新线程来周期性地改变ImageView所显示的图片。
public class MainActivity extends Activity {
//定义周期性改变的图片的ID
int[] imagesId = new int[]{
R.drawable.a,
R.drawable.b,
R.drawable.c,
R.drawable.d,
R.drawable.e
};
int currentImageId = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final ImageView show = (ImageView) findViewById(R.id.show);
final Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
//判断该消息是否是本程序发送的
if(msg.what == 0x123){
//动态地改变显示的图片的ID
show.setImageResource(imagesId[currentImageId++]);
if(currentImageId>4){
currentImageId = 0;
}
}
}
};
//定义一个计时器,让该计时器周期性地执行指定任务
new Timer().schedule(new TimerTask() {
@Override
public void run() {
// TODO Auto-generated method stub
//新启动的线程无法访问Activity里的组件
//所以需要Handler发送消息
Message msg = new Message();
msg.what = 0x123;
//发送消息
mHandler.sendMessage(msg);
}
}, 0, 800);
}
}
当新线程发送消息时,Handler 的handleMessage(Message msg)方法被自动回调,来改变Activity中组件的属性。
与Handler一起工作的几个组件(为了更好地理解Handler的工作原理):
- Handler:把消息发送给Looper管理的MessageQueue,并负责处理Lopper分给它的消息。
- Message:Handler接收和处理的消息对象。
- Looper:每个线程只能有一个Looper。它的loop方法负责读取MessageQueue中的消息,并将消息分给对应的Handler处理。
- MessageQueue:消息队列,先进先出方式管理Message,程序创建Looper对象时会在它的构造器中创建MessageQueue对象。源码如下:
private Looper(){
mQueue = new MessageQueue();
mRn = true;
mThread = Thread.currentThread();
}
通过源码可知,无法通过构造器来创建Looper对象,程序在初始化Looper时会创建一个与之关联的MessageQueue,这个MessageQueue负责管理消息。
如果要Handler正常工作,就要在当前线程中必须有一个Looper对象:
- 主UI线程中,系统已经初始化了一个Looper对象,因此程序直接创建Handler即可,然后就可通过Handler来发送消息、处理消息。
- 我们自己启动的子线程,必须自己创建一个Looper对象,并启动它。创建Looper对象调用它的prepare()方法即可(prepare()方法保证了每一个线程最多只有一个Looper对象),然后调用Looper静态loop()方法来启动它(loop()使用死循环,不断从MessageQueue中取出消息,传递给对应的Handler处理)。
在线程中使用Handler的步骤:
- 调用Looper的prepare()方法创建Looper对象,同时会有与之匹配的MessageQueue被创建
- 创建Handler子类实例,重写handlerMessage()方法,负责处理来自其他线程的消息。
- 调用Looper的loop()方法启动Looper。
例:利用新线程计算质数
public class MainActivity extends Activity {
//2~UPPER_NUM范围内的质数
static final String UPPER_NUM = "upper";
EditText etNum;
//定义一个线程类用于计算质数
CalThread calThread;
class CalThread extends Thread{
public Handler handler;
public void run(){
//Looper的prepare方法用于创建Looper对象,同时创建与之对应的MessageQueue
Looper.prepare();
handler = new Handler(){
//重写处理消息的方法
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
if(msg.what==0x123){
//获取从2到upper的质数
int upper = msg.getData().getInt(UPPER_NUM);
List<Integer> nums = new ArrayList<Integer>();
outer:
for(int i = 2;i<upper;i++){
for(int j = 2;j<Math.sqrt(i);j++){
if(i!=2&&i%j==0){
continue outer;
}
}
nums.add(i);
}
//把得到的质数显示出来
Toast.makeText(MainActivity.this, nums.toString(),Toast.LENGTH_LONG).show();
}
}
};
//启动Looper
Looper.loop();
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
etNum = (EditText) findViewById(R.id.etNum);
calThread = new CalThread();
calThread.start();
}
//
public void cal(View v){
//创建消息
Message msg = new Message();
msg.what = 0x123;
Bundle bundle = new Bundle();
bundle.putInt(UPPER_NUM, Integer.parseInt(etNum.getText().toString()));
msg.setData(bundle);
//向新线程中的Handler发送消息
calThread.handler.sendMessage(msg);
}
}
运行该程序,根据输入的数据计算该范围内的质数都会交给新线程,前台UI不受影响。