1 前言
『Android线程消息机制』是本文所要讨论的内容,在此之前我们需要先简单介绍下(之后会详细说明)线程消息机制中的四个主要成员,它们分别是Looper、Handler、Message和MessageQueue:
【Looper】是消息循环处理器,它负责从MessageQueue(消息队列)中提取一个Message对象进行处理。
【Handler】是消息发送者,它负责将Message发送到MessageQueue中等候被处理。
【Message】是消息载体,其内部保存了由我们定义的要被处理的业务逻辑以及相关的数据。另外还有一个【MessagePool(消息池)】专门用于回收利用空闲的Message对象,我们会连同Message一起介绍。
【MessageQueue】是消息队列,Handler将Message发送到消息队列中,消息队列负责维护这些待处理的Message。
接下来让我们来看看Looper、Handler、Message和MessageQueue之间是如何协同工作构建起消息循环机制的,具体步骤如图1所示。
Handler从MessagePool中获取一个空闲的Message对象进行业务封装,然后发送到MessageQueue,发送的消息并不会马上被执行,而是在MessageQueue中等待。
MessageQueue负责维护Message,当Looper要从MessageQueue中提取一个Message的时候,MessageQueue会按照一定规则弹出一个Message。
Looper进行死循环操作,从MessageQueue中不断提取Message进行业务处理,然后回收Message对象放入MessagePool中。
2 简单示例
在开发中经常会遇到多线程操作,最常见的情况是子线程要修改主线程(也叫做UI线程)中的UI(例如子线程进行下载任务,更新主线程的进度条),这时候子线程必须通过线程消息机制来向主线程发送更新UI的消息,主线程在接收到更新消息之后才更新UI,而不能通过子线程直接去操作主线程里的UI(例如直接操作ProgressBar)。
下面是一个简单的示例,Activity里有一个ProgressBar,我们先开一个线程模拟耗时操作(比如下载文件),由于子线程是不能够直接修改主线程UI的,所以我们必须通过消息机制来完成进度条的更新操作,具体的代码如下:
public class MainActivity extends ActionBarActivity {
private ProgressBar mPbTest;
//定义一个Handler内部类,并且重写handleMessage(Message msg)方法更新进度条,
//handleMessage(Message msg)是在主线程中被调用的,所以可以修改UI的。
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
mPbTest.setProgress(msg.arg1);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mPbTest = (ProgressBar) findViewById(R.id.pbTest);
}
@Override
protected void onStart() {
super.onStart();
//开启子线程模拟耗时操作,但是子线程是不能直接更新UI线程的进度条的,所以需要用Handler
new Thread() {
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
Message msg = mHandler.obtainMessage();
msg.arg1 = i;
mHandler.sendMessage(msg);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
}
这是一个很常用的异步更新UI的方式,但是这种方式存在着内存泄露的风险,在文章的最后我们再来说明为什么会有这个问题,并且给出一个解决方案,不过接下来我们先具体介绍一下线程消息机制的用法。
3 用法详解
前面看了一个简单示例,接下来我们将更加详细的介绍是如何利用线程消息机制,包括创建具有消息处理能力的线程、定义业务逻辑、包装消息、发送消息、利用线程空闲时间以及常用方法介绍等。
3.1 创建Handler线程
所谓的线程消息机制,其实就是在线程里建立一个死循环不断从消息队列中提取消息进行处理,这个死循环就是由Looper来创建的,我们把具有Looper对象的线程称作【Handler线程】,也就是具有消息处理能力的线程。如果我们想要把一个线程转换成Handler线程,也就是说在线程里创建一个Looper的话,可以按照如下代码来实现:
public class MyHandlerThread extends Thread {
@Override
public void run() {
// 创建一个Handler线程。
Looper.prepare();
// 其他初始化操作必须在loop()之前完成,比如初始化Handler。
// 启动Looper,进入死循环不断处理消息队列中的消息。
Looper.loop();
}
/**
* 以不安全的方式停止Handler线程。
*/
public void quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
}
}
/**
* 以安全的方式停止Handler线程。
*/
public void quitSafely() {
Looper looper = getLooper();
if (looper != null) {
looper.quitSafely();
}
}
}
通过上面两行代码,我们就在线程里面创建了一个Looper对象,同时也创建了一个MessageQueue,从现在开始该线程就具有了消息处理的能力,我们可以同过Handler往消息队列中发送Message了。当我们需要停止Handler线程的时候,本质上是停止Looper的循环,所以我们的quit()
和quitSafely()
方法实际上就是拿到Handler线程的Looper对象并停止它。关于Handler线程的安全停止和不安全停止我们会在1.4 Looper源码分析中简单介绍,在1.7 MessageQueue源码分析进行深入介绍。
3.2 定义业务逻辑
我们可以定义要通过线程消息机制来处理的业务逻辑,例如更新UI。通过Handler定义业务逻辑的方式有三种,下面将对这三种方式逐个介绍。
3.2.1 继承Handler,并重写handleMessage(Message msg)
方法
这应该是最常见的方式了,Handler内部对handleMessage(Message msg)
有个空实现,也就是什么事情都不做,我们通过继承Handler并重写Handler的handleMessage(Message msg)
来定义我们的业务逻辑,通常我们会在Activity内部这么写:
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch(msg.what) {
case 1:
// 业务逻辑1
break;
case 2:
// 业务逻辑2
break;
}
}
};
3.2.2 实现Handler.Callback
接口
如果我们不想通过继承方式来定义业务逻辑,我们可以选择实现Handler.Callback接口,并且将它作为参数通过Handler的构造器传递给Handler,Handler.Callback接口内部就一个需要实现的方法,也叫handleMessage(Message msg),并且它带有一个boolean类型的返回值,当我们既继承了Handler并重写了Handler.handleMessage(Message msg)方法,又实现了Handler.Callback接口的时候,如果返回true,只会执行Handler.Callback.handleMessage(Message msg)方法,如果返回false,则会先执行Handler.Callback.handleMessage(Message msg)然后执行Handler.handleMessage(Message msg)。实现代码如下:
public class MainActivity extends ActionBarActivity implements Handler.Callback {
private Handler mHandler;
@Override
public boolean handleMessage(Message msg) {
switch(msg.what) {
case 1:
// 业务逻辑1
break;
case 2:
// 业务逻辑2
break;
}
return true;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler = new Handler(this);
}
}
3.2.3 实现Runnable
接口
除了前面两种方式,还有第三种方式来定义我们的业务逻辑,就是实现Runnable接口,我们可以使用Handler.postXXX()
方法来发送Runnable
,Runnable
会被包装成一个Message对象。需要注意的是,虽然我们实现的是Runnable,但是这并不会启动一个新的线程来处理事务,最后执行的时候只是简单的调用Runnable.run()
方法而已。实现代码如下:
Handler handler = new Handler();
handler.post(new Runnable() {
@Override
public void run() {
// 定义业务逻辑
}
});
既然我们有三种方式来定义业务逻辑,那选择哪一种最合适呢?对于前两中方式,本质上是一样的,所以我们把它们归类为同一种。从实现方式上我们可以看出,handleMessage(Message msg)
方法内部我们经常会根据Message.what
来处理不同的业务逻辑,而Runnable方式则只会做一件事情,当然你也可以为每一个业务逻辑定义一个Runnable,但是这和前一种比起来就麻烦多了。两者的使用各有千秋,你可以根据自己的喜好来使用。
3.3 包装消息
定义完业务逻辑之后,我们需要将我们的业务逻辑和相关数据包装成一个Message对象,Message是一个消息介质,它负责保存我们的业务逻辑和相关的数据,并在消息队列中等待被处理。
在获取Message对象时,有些人会使用Message msg = new Message()
方式来创建一个新的Message对象,这样的做法是不推荐的,因为这样会创建出很多的Message对象,而GC并不能每次在Message没用的时候及时回收它,就造成资源的浪费。正确获取Message对象的方法是通过Handler.obtainMessage(...)
方法从消息池中申请一个空闲的Message对象,Android线程消息机制会维护一个消息池,该消息池会将没用的Message对象回收利用,这样就可以减少资源消耗。以下是一段简单的示例代码:
Handler handler = new Handler();
Message msg = handler.obtainMessage();
msg.what = UPDATE_PROGRESS;
msg.arg1 = progress;
Message为我们提供了几个使用的属性用于识别不同的Message以及携带不同的数据:
what
该属性用于标识不同的Message对象,因为在消息队列中经常会同时存在多个Message,在提取并处理消息的时候我们必须有个标识来识别不同的Message,以便做不同的业务操作。
arg1
该属性用于存储简单的整形数据,例如你可以用它来存储下载进度值。
arg2
同上。
obj
该属性用于存储一个对象,你可以用它来保存任何的对象,例如一个Java Bean。
public void setData(Bundle data)
如果你要存储的数据比较复杂,则你可以考虑用Bundle来保存数据。
通过上面的介绍,我们可以知道Message作为消息介质,负责保存数据,但是我们并没有看到它是如何保存我们的业务逻辑的,实际上对业务逻辑的保存是在发送消息的时候才做的,而且内部自动完成,并不需要我们做任何操作,在源码分析时我们会加以说明。
3.4 发送消息
完成了消息的包装,接下来就是使用Handler.sendXXXX(...)
方法来发送Message到消息队列中,这一过程中会自动将业务逻辑和Message绑定在一起。如果你选择使用Runnable来实现业务逻辑,则不需要你去获取任何Message对象,直接通过Handler.postXXX(Runnable...)
方法就可以发送消息到消息队列中了,该方法会自动将Runnable包装成Message。
3.4.1 使用Handler发送Message
Handler handler = new Handler();
Message message = handler.obtainMessage();
message.what = 1;
message.arg1 = 100;
handler.sendMessage(message);
3.4.2 使用Handler发送Runnable
Handler handler = new Handler();
handler.post(new Runnable() {
@Override
public void run() {
// 定义业务逻辑
}
});
3.5 利用空闲时间
前面介绍了Android线程消息机制的基本用法,接下来介绍一个比较不常用的功能。首先,我们要知道的是当消息队列中没有任何可以处理的消息的时候,线程会被阻塞,这个时候线程处于空闲状态,我们可以利用这个空闲时间做一些业务操作。
在MessageQueue内部为我们提供了一个叫IdleHandler
的接口,该接口要求我们必须实现一个叫boolean queueIdle()
的方法,我们可以在该方法内部定义业务逻辑,当该方法返回true的时候,该IdleHandler会被一直保留在消息队列中,每次消息队列进入阻塞状态的时候都会触发该IdleHandler
,如果返回false
则该IdleHandler
只会被执行一次,然后从队列中移除。以下是示例代码:
MessageQueue queue = Looper.myQueue();
queue.addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
System.out.println("我很闲");
return false;
}
});
注意:每一个在消息队列中的IdleHandler只会在队列阻塞前被执行一次。
3.6 HandlerThread
Android SDK为我们提供了一个名为HandlerThread
的Handler线程类,我们可以使用它快速创建一个Handler线程,下面给出了代码示例,我们把前面自己实现Handler线程的代码也加进去用于对比:
/**
* 继承HandlerThread创建自己的Handler线程。
*/
public class MyHandlerThreadExtHandlerThread extends HandlerThread {
public MyHandlerThread(String name) {
super(name);
}
public MyHandlerThread(String name, int priority) {
super(name, priority);
}
@Override
protected void onLooperPrepared() {
// 在Looper执行死循环之前做一些准