在Java里面线程间通信的方式有好些方式,在Android里我们常用的方式之一是通过Handler,做过点Android开发的都知道,在Android里对界面上的一些操作都需要放在主线程或者说UI线程中,其实不在主线程中也能操作,当然有特殊要求,具体请自行百度。总之,通过Handler我们能比较方便的在子线程和主线程间通信。
android.os.Handler
发送消息和处理消息都需要通过它。它会与一个Looper相关联。
android.os.Looper
通常我们叫轮询器,负责消息分发的。一个Looper只能和一个线程关联。
android.os.MessageQueue
消息队列,放消息的,每个Looper和线程最多只会有一个消息队列。
android.os.Message
它可以存放一些数据并传递给相应消费线程。
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();
}
});
}
}
代码很简单,界面就一个TextView
和一个Button
,给按钮设定了点击监听,点击的时候起了个线程去下载老师的艺术片儿,下载完后在界面上用文字通知咱们去鉴赏。
从上面我们的示例代码可以看出,子线程将一个msg传递到主线程去处理,就相当于一个生产者消费者模式,子线程生产,主线程消费。如下图所示:
1、插入,生产者线程(对于我们上面的示例就是子线程)通过消费者线程(对于我们上面的示例就是UI线程)上的Handler将msg插入到消息队列里。
2、检索,运行在消费者线程的Looper会按顺序检索MessageQueue里的消息。
3、分发,消费者线程上的Handler负责处理msg,一个线程可以有多个Handler来处理msg,Looper会将相应的msg分发给对应的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();
}
}
}
按之前的猜想,直接在子线程里实例化Handler,结果报错如下:
错误显示在实例化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;
}
}
};
}
}
}
代码很简单,点击按钮的时候向子线程发送消息,如果子线程收到消息会在Logcat里打印出一条log,但是点击按钮后并没有看到打印的log,这,这就很尴尬了。。。
通过我们写的代码,能够知道已经向子线程发送消息了,为毛子线程没反应呢,通过上面的生产者消费者模式的图我们可以知道,消息的检索是由Looper来做的,是不是Looper哪里出了问题,导致它没有检索到我们发送过来的消息?我们的代码只是调用了Looper.prepare()方法,而这个方法只是创建了一个Looper对象放到当前线程的局部变量中,是不是Looper还有个开关来控制它开始检索消息?我们来看看Looper的源码先,找了一阵后我们在Looper源码里发现了这么个方法:
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
...// 省略部分代码
for (;;) {
...// 省略部分代码
msg.target.dispatchMessage(msg);
...// 省略部分代码
}
}
从loop()方法的注释我们就能知道这个方法是开启消息轮询的,主要是有个for的死循环一直调用target来分发消息,从Message的源码可以知道target就是Handler对象。我们再次修改一下代码:
private class DownloadThread extends Thread {
public Handler mHandler;
@Override
public void run() {
Looper.prepare();
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
Log.e("fiend", "接收到UI线程发过来的信息了");
break;
}
}
};
// 增加调用这个方法来开始消息轮询
Looper.loop();
}
}
在实例化Handler后我们调用了Looper.loop()方法来开始消息的轮询,结果输出如下:
终于正常收到UI线程发过来的消息了。
上面我讲了子线程如何接收UI线程发送过来的消息。但是这里有个问题。如何结束这个子线程?
就像我们上面的下载线程,当苍老师的艺术片下载完了后,其实我们就已经不需要这个子线程了,但是从之前Looper的源码里我们知道它是个死循环在轮询消息,这样就导致我们的子线程无法正常结束了,这,这就有点浪费资源了。。。
我们继续查看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的时候需要先实例化一个Looper才行,我们来一探究竟。
Handler部分源码如下:
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()
啊,为毛没有报错?这,这就有点尴尬了…
继续查看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
在哪里实例化的,我们来看看,在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
里,然后通过MessageQueue
最终传递到消费者线程去处理。Message
里可以放数据(data)或者任务(task),每个Message
只能放一种内容,不能同时存在数据和任务。
数据消息(Data message):
——数据消息可以携带这些信息。通过handleMessage()
处理。
任务消息(Task message):
——任务消息就是传递一个java.lang.Runnable
对象到消费者线程执行。
实例化Message
有几种方法:
Message m = new Message();
Message m = Message.obtain();
Message m = Message.obtain(Handler h);
Message m = Message.obtain(Handler h, int what);
Message m = Message.obtain(Handler h, int what, Object o);
Message m = Message.obtain(Handler h, int what, int arg1, int arg2);
Message m = Message.obtain(Handler h, int what, int arg1, int arg2,Object o);
Message m = Message.obtain(Handler h, Runnable task);
Message m = Message.obtain(Message originalMsg);
调用工厂方法好点,因为它会从Message池里获取,不需要new一个新的,池里没有才会new,所以性能稍微好点吧。
消息都是通过Handler
插入到消息队列的,数据消息是通过前缀为send
的方法插入的,任务消息是通过前缀为post
的方法插入的。
boolean sendMessage(Message msg)
boolean sendMessageAtFrontOfQueue(Message msg)
boolean sendMessageAtTime(Message msg, long uptimeMillis)
boolean sendMessageDelayed(Message msg, long delayMillis)
boolean sendEmptyMessage(int what)
boolean sendEmptyMessageAtTime(int what, long uptimeMillis)
boolean sendEmptyMessageDelayed(int what, long delayMillis)
boolean post(Runnable r)
boolean postAtFrontOfQueue(Runnable r)
boolean postAtTime(Runnable r, Object token, long uptimeMillis)
boolean postAtTime(Runnable r, long uptimeMillis)
boolean postDelayed(Runnable r, long delayMillis)
MessageQueue
里只能放Message
对象,那是如何传递Runnable
的呢?我们来看一下源码:
// handler调用post方法发送任务
public final boolean post(Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
// 将Runnable包装成message
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
通过发送任务的方法源码我们知道,其实最终加入消息队列的还是消息对象,只是将任务进行了包装。
基本将Handler
、Message
、MessageQueue
、Looper
协作运转的过程简单走了一遍,但是还是有些东西没讲,如消息的生命周期、消息队列的空闲时间、自定义callback
、移除消息、消息处理过程跟踪等。但是这个简析基本理清了他们之间的关系以及工作原理,在使用上的话应该不会存在大问题了。