Handler,Message,MessageQueue,Looper简析
在Android种常用的线程间通信方式之一是通过Handler,做过android开发的都知道,在Android种对ui的操作都要放在主线程或者说是ui此案成中,当然不在主线程也能操作,具体在应用开始onResume 之前,这时还未检查是否是ui线程。具体涉及到android源码,本例不深入。
相关类
android.os.Handler 发送消息和处理消息都需要通过它,它会与一个Looper相关联。 android.os.Looper 通常说是轮询器,负责消息分发的,一个Looper只能与一个线程关联
android.os.MessageQueue 消息队列,存放message,每个looper和线程最多只有一个消息队列 android.os.Message 存放一些数据并传递给相应的消费线程
Handler 简单示例
public class MainActivity extends AppCompatActivity {
private TextView mTvTip;
private Button mBtnDownLoad;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
// what为0表示下载完成
case 0:
mTvTip.setText("下载完成。");
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTvTip = (TextView) findViewById(R.id.tv_tip);
mBtnDownLoad = (Button) findViewById(R.id.btn_download);
mBtnDownLoad.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
// 睡眠5秒钟模拟下载过程
SystemClock.sleep(1000 * 5);
Message msg = mHandler.obtainMessage();
// 下载完成,将msg的what值设定为0
msg.what = 0;
msg.sendToTarget();
}
}).start();
}
});
}
![Handler.png](http://upload-images.jianshu.io/upload_images/1394860-de75c82fca47a09b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
}
Handler 的模式
从上面我们的示例代码可以看出,子线程将一个msg传递到主线程去处理,就相当于一个生产者消费者模式,子线程生产,主线程消费。如下图所示:
1、插入,生产者线程(对于我们上面的示例就是子线程)通过消费者线程(对于我们上面的示例就是UI线程)上的Handler将msg插入到消息队列里。 2、检索,运行在消费者线程的Looper会按顺序检索MessageQueue里的消息。 3、分发,消费者线程上的Handler负责处理msg,一个线程可以有多个Handler来处理msg,Looper会将相应的msg分发给对应的Handler来处理。
子线程使用Handler
之前我们的例子是在UI线程里实例化Handler的,所以UI线程可以接收到消息,那如何发送消息到子线程呢?我们试试在子线程里实例化Handler:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new DownloadThread().start();
}
private class DownloadThread extends Thread {
public Handler mHandler;
@Override
public void run() {
mHandler = new Handler();
}
}
}
结果报错,java.lang.runtimeException:can’t create handler inside thread that has not called Looper.prepare()
错误显示在实例化Handler之前需要先调用Looper.prepare()。我们来看一下Looper.prepare()的部分源码:
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
// 检查线程局部变量是否已经保存了一个Looper
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
// 创建一个Looper对象并放到当前线程局部变量中
sThreadLocal.set(new Looper(quitAllowed));
}
通过源码我们能发现prepare()方法就是将一个Looper对象放到当前线程的局部变量中,从它抛出的异常信息我们也能验证了之前说的一个线程只能有一个Looper对象。这和我们在实例化Handler对象需要先调用prepare()有什么关系呢?不难猜出是实例化Handler的时候需要用到当前线程的Looper对象,我们暂且相信是这样的,后面验证
我们修改一下代码再试试:
public class MainActivity extends AppCompatActivity {
private Button mBtnDownload;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBtnDownload = (Button) findViewById(R.id.btn_download);
final DownloadThread downloadThread = new DownloadThread();
downloadThread.start();
mBtnDownload.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Message msg = downloadThread.mHandler.obtainMessage();
msg.what = 0;
msg.sendToTarget();
}
});
}
private class DownloadThread extends Thread {
public Handler mHandler;
@Override
public void run() {
// 先实例化一个Looper放到当前线程的局部变量
Looper.prepare();
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
Log.e("fiend", "接收到UI线程发过来的信息了");
break;
}
}
};
// 增加调用这个方法来开始消息轮询
Looper.loop();
}
}
}
停止Looper
上面我讲了子线程如何接收UI线程发送过来的消息。但是这里有个问题。如何结束这个子线程? 我们继续查看Looper的源码,看一下是否有相关代码是关于如何退出死循环的。发现了这两个方法:
public void quit() {
mQueue.quit(false);
}
public void quitSafely() {
mQueue.quit(true);
}
从方法的名字我们能知道的是一个是退出,一个是安全退出,通过它们的注释我们也能知道,它们就是退出Looper的方法,至于quit()方法是一开始就有的方法,但可能不安全,quitSafely()方法则是API 18(Android 4.3)后才有的方法,它能安全退出Looper.这样我们就能在子线程做完所有工作后调用Looper.myLooper().quit() 方法,这样子线程就能正常结束了。 Handler对象实例化过程 前面我们留有一个疑问,为什么实例化Handler的时候需要先实例化一个Looper才行,我们来一探究竟。
public Handler() {
this(null, false);
}
public Handler(Callback callback, boolean async) {
...// 省略部分代码
// 获取当前线程的Looper
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
...// 省略部分代码
}
从上面的构造方法中我们看到之前在没调用Looper.prepare()就实例化Handler所抛出的异常信息,这也解释了为什么要先实例化Looper后再实例化Handler。 到这里我们应该会有个疑问,刚开始我们从子线程发消息到主线程的例子中,我们实例化Handler的时候并没有调用Looper.prepare()
UI线程的Looper
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
从名字大概就能猜到,这是创建主Looper的方法,而且抛出的异常信息也能证明这一点。我们来看看是在哪里调用的这个方法:
通过查找调用的地方发现,在程序入口main()方法里调用这个方法,所以这就解释了为什么我们在UI线程实例化Handler的时候没调用Looper.prepare()方法也没有报错,因为早就已经在入口处创建了UI线程的Looper了,而且还是调用的Looper.prepareMainLooper()方法,当然在这个方法里我们也看到它,它还是调用的prepare(false)创建的 注意,我们在子线程创建Looper的时候是直接调用的prepare()方法,并没有带参数,我们来看一下它的代码:
public static void prepare() {
prepare(true);
}
原来方法里也是带参数调用的,但是UI线程带的参数是false,而别的线程带的参数是true,从形参的命名我们就能知道是说能否退出Looper的意思,之前我们说了,可能通过调用Looper.myLooper().quit()方法退出Looper,所以这里我们能够知道,UI线程的Looper是不允许退出的。
MessageQueue
说了这么久,好像还没看到MessageQueue在哪里实例化的,我们来看看,在Looper实例化的时候代码是这样的:
// 方法一
public static void prepare() {
prepare(true);
}
// 方法二
private static void prepare(boolean quitAllowed) {
// 检查线程局部变量是否已经保存了一个Looper
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
// 创建一个Looper对象并放到当前线程局部变量中
sThreadLocal.set(new Looper(quitAllowed));
}
// 方法三
private Looper(boolean quitAllowed) {
// 实例化MesssageQueue
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
方法一和方法二在之前已经解释过了,我们看方法二最后一行代码,实例化了一个Looper对象放到当前线程的局部变量中,方法三是Looper的构造方法,在构造方法里我们看到了MessageQueue的实例化代码。之前我们说了一个线程只能有一个Looper,而MessageQueue是在Looper的构造方法中实例化的,所在一个线程同样只能有一个MessageQueue。
Message
最后我们来看看Message,我们想要传递的内容就是放在Message里,然后通过MessageQueue最终传递到消费者线程去处理。Message里可以放数据(data)或者任务(task),每个Message只能放一种内容,不能同时存在数据和任务。
总结
基本将Handler、Message、MessageQueue、Looper协作运转的过程简单走了一遍,但是还是有些东西没讲,如消息的生命周期、消息队列的空闲时间、自定义callback、移除消息、消息处理过程跟踪等。但是这个简析基本理清了他们之间的关系以及工作原理,在使用上的话应该不会存在大问题了。 ⤧ Next post View事件分发简要分析 ⤧ Previous post 回调方法与顺序