1、简述
由于安卓中为了确保界面的更新同步,限制了只有UI(主)线程可以更新界面,但是UI线程不能执行耗时操作,相关的文件读取、数据加载和网络请求等操作,需要放置在子线程进行处理,但处理完成后怎样将子线程的处理结果及时的发送至主线程呢?
此时就需要引入Handler的运行机制,通过Hnadler的创建、发送和处理方式,实现跨线程的数据通信。
其实Handelr主要用于线程间通信的,但在安卓开发中一般用于子线程发送至主线程的操作。
2、Handelr的简单使用
2.1子线程发送至主线程
private final int TEST_HANDLER_NUM = 100;
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case TEST_HANDLER_NUM:
Log.i(TAG, "handleMessage: 接收到处理结果");
break;
}
}
};
new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(5 * 1000);
Message message = mHandler.obtainMessage();
message.what = TEST_HANDLER_NUM;
mHandler.sendMessage(message);
Log.i(TAG, "run: 消息处理完成,开始发送");
}
}).start();
2.2 主线程发送至子线程
//在子线程中创建Handler
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
subHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case TEST_SUB_HANDLER_NUM:
Log.i(TAG, "handleMessage: 子线程接收到消息");
break;
}
}
};
Looper.loop();
}
}).start();
//主线程发送消息
findViewById(R.id.TestA).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
subHandler.sendEmptyMessageAtTime(TEST_SUB_HANDLER_NUM,5*1000);
Log.i(TAG, "onCreate: 发送消息至子线程");
}
2.3 采用post形式
UIHandler.post(new Runnable() {
@Override
public void run() {
Log.i(TAG, "run: "+currentThread().getName());
mTextView.setText("hahha");
}
});
2.4 小结
Handler的使用中,除了上述使用的sendMessage/sendEmptyMessageAtTime,还包括post/postAtTime等,但是通过查看源码可以发现,其最终调用的还是sendMessage相关的方法,因此本章中主要是对sendMessage相关的源码分析。
3. Handler运行机制
3.1执行流程
针对上图中相关的类简介如下:
Handler相关的流程包括如下:创建Handler、发送消息、存储消息、取出消息、处理消息五个过程,其中包含的类如下:
-
Handler:负责发送和处理消息;
-
Looper:负责不断的轮询消息队列,并将消息 分发至Handler处理;
-
MessageQueue:存储消息的队列;
-
Message:消息载体;
3.1.1创建Handler
在创建Handler时,其执行的构造方法如下:
public Handler(Callback callback, boolean async) {
...
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
...
}
在创建Handler时,通过myLooper()获取到Looper对象,否则会抛出RuntimeException异常,而myLooper()的获取方式为通过ThreadLocal的get()方法。
既然时通过ge()方式获取,那么是什么时候调用的set()方法呢?
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
查看2.2 主线程发送至子线程,在创建Handler前,先调用了Looper.prepare()方法,然后调用了 Looper.loop()方法,至于为何需要调用这个两个方法?2.1部分为何不需要调用?
为了回答以上问题,需要查看prepare()和loop()到底是处理了什么?
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
调用prepare方法后,先通过ThreadLocal.get()查看是否为空,如果不为空则抛出异常,表明一个线程中只能绑定一个Looper对象,如果为空,则新建一个Looper对象,并通过set方法赋值给ThreadLocal。
而loop()是消息的轮询机制,该部分在下面消息的轮询中会详细讲解。
为何2.1部分子线程发送消息至主线程,不需要调用prepare()方法?
查看源码注释,可以看出,在应用启动时,就调用了getMainLooper() 方法,为主线程创建了Looper对象。
/**
* Returns the application's main looper, which lives in the main thread of the application.
*/
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
其中ThreadLocal用于线程的存储类,主要包含set和get方法,其内部维护这一个Map集合,其中key是当前的线程,value为需要存储的值。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
3.1.2 发送消息
如第2部分所示为Handler的简单使用,无论是发送Message 还是Runable,但最终运行至sendMessageAtTime中,如下代码所示:
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
//设置消息的tagrget
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
执行至Handler的enqueueMessage后,开始调用MessageQueue 的enqueueMessage,开始消息的存储操作。
3.1.3 存储消息
MessageQueue主要用于消息的存取,通过next()获取消息信息,enqueueMessage()存储消息,本节主要讲解消息的添加,消息的分发处理在下面讲解。
Handler中的enqueueMessage处理:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
//在发送至MessageQueue 前,将当前的Handler赋值给Message 的target变量
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
MessageQueue 中的enqueueMessage处理:
boolean enqueueMessage(Message msg, long when) {
//1、消息属性的判断
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
//2、更改消息属性
msg.markInUse();
msg.when = when;
//上次添加的消息信息
Message p = mMessages;
boolean needWake;
//3、查看消息是否为空,上次消息是否添加成功
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
3.1.4 消息分发
3.1.4.1 消息轮询
在2.2 主线程发送至子线程,在创建Handler后,需要手动调用 Looper.loop()方法,如果注释该代码,会发现及时发送了消息,但是在handleMessage接收不到消息的回调信息,下面看下loop()主要执行了那些操作?
public static void loop() {
//1、 查看当前线程的Looper是否为空
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
//2、获取当前线程对应的 MessageQueue
final MessageQueue queue = me.mQueue;
....
//3、死循环获取消息信息
for (;;) {
Message msg = queue.next();
//4、如果去除的消息为null ,则跳出循环
if (msg == null) {
return;
}
...
//5、根据消息的target分发至对应的dispatchMessage方法中
msg.target.dispatchMessage(msg);
...
}
}
3.1.4.2 消息获取
以上完成消息的遍历取出和分发操作,其中消息的取出通过MessageQueue 的next()方法实现。
Message next(){
...
//1、死循环遍历
for (;;) {
...
synchronized (this) {
//2、获取当前时间戳;初始化上次消息;当前消息
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// 3、查找消息队列中的下一个消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
// 4、判断当前消息是否准备完成
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
//5、消息属性变更和发送消息
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
}
}
...
}
}
3.1.4.3 消息类型
在上述讨论中一直围绕着Message的处理来梳理,下面查看一下Message的类,其中最常用的属性如下,对于what、arg1、arg2和obj在Handler的应用处理中使用较多,而target属性用于标注消息,在消息的存储和分发中便于标注是基于那个Handler 进行处理的,这是由于针对一个Looper对象,我们可以创建多个Handler实例。
public final class Message implements Parcelable {
public int what;
public int arg1;
public int arg2;
public Object obj;
...
Handler target;
...
}
3.1.5 消息分发
在3.1.4.1 消息轮询中,完成消息的分发处理,分发中通过Message的target判断所属的Handler,其实target就是Handler类型。
public void dispatchMessage(Message msg) {
//1、msg是否为包含callback(Runable形式)
if (msg.callback != null) {
handleCallback(msg);
} else {
//2、是否重写了Callback方法
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//3、如果上述都为空,默认执行至重写的 handleMessage中,也就是常见的创建Handler的形式
handleMessage(msg);
}
}
3.2 常见问题分析
1、Handler是怎么实现切换线程的?
2、handler.sendMessage()与handler.post()的区别?
3、MessageQueue是怎么增删消息的?
4、一个线程可以有几个Handler?几个Looper?几个MessageQueue?
5、A Handler发送的消息为什么不会跑到B Handler的handleMessage()方法中?
6、简述ThreadLoacal的原理?
7、Handler引起的内存泄漏?
8、在UI中创建的Handler,通过post方式发送的消息在run方法中可以进行UI更新吗?
1、Handler是怎么实现切换线程的?
- 创建Handler:在创建时需要先获取该线程(一般为UI线程)对应的Looper信息;
- 发送消息:在子线程数据处理完成后,发送消息,在发送消息前将该Handler作为target,将消息发送至MessageQueue中;
- 消息处理:通过Looper的loop方法,不断获取消息信息,根据消息属性中的target分发消息,从而完成了子线程至主线程的消息处理。
2、handler.sendMessage()与handler.post()的区别?
查看Handler可以发现,最终发送至MessageQueue的方法都是Handler中的enqueueMessage(),但是对于Message的处理,在分发时,会将消息的结果返回至其callback 中(如果不为空)。
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
3、MessageQueue是怎么增删消息的?
增加:通过enqueueMessage方法,通过对消息添加时间和之前消息的判断,将新添加的消息放置在栈顶
msg.next = p; // invariant: p == prev.next
prev.next = msg;
删除:通过next方法取出消息,在loop()中进行消息的分发处理,在next返回消息前对消息属性进行修改
// MessageQueue中的next()方法
msg.next = null;
msg.markInUse();
// Message中的markInUse()方法,表明消息已经使用
void markInUse() {
flags |= FLAG_IN_USE;
}
4、一个线程可以有几个Handler?几个Looper?几个MessageQueue?
多个Handler?
**多个:**上述描述中,知道之所以消息可以准确的分发处理,是由于通过Message的target方法在添加和分发时标注Handler,实现准确的分发,因此同一个线程可以创建多个Handler对象,例如常见的UI线程中:
private Handler mHandler= new Handler(Looper.myLooper()){...}
几个Looper?
一个:在2.2中主线程发送至子线程中,需要先调用Looper.prepare()方法,否则会抛出异常,那如果一个线程中包含多个Looper会怎样?
查看源码可以发现,如果包含多个Looper对象会抛出异常。
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
几个MessageQueue?
一个:在调用Looper.prepare()时,会判断该线程的Looper是否为空,只有为空的情况才会调用Looper的构造方法,创建MessageQueue,因此只有一个。
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
5、A Handler发送的消息为什么不会跑到B Handler的handleMessage()方法中?
参考问题1和4可知,一个线程中包含多个Handelr对象,而在消息的添加分发时,通过Message的target(Handler)标注。
6、简述ThreadLoacal的原理?
在3.1中新建Handler时,已对ThreadLoacal简述,通过set和get方法,其内部维护这一个Map集合,其中key是当前的线程,value为需要存储的值。
7、Handler引起的内存泄漏?
可参考Android中使用Handler为何造成内存泄漏?中相关的介绍,主要是由于在消息处理时,当前Activty已经关闭,但是仍有消息处理,导致当前Activty无法正常消耗出现内存泄漏的情况,解决方案:
- Activity销毁时及时清理消息队列;
- 自定义静态Handler类+软引用。
参考:Android Handler之原理解析
8、在UI中创建的Handler,通过post方式发送的消息在run方法中可以进行UI更新吗?
可以:通过查看源码可以发现,其最终调用的还是sendMessage相关的方法,但是如果传入的Runnable形式,其内部会对其进行了封住成Message类。
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
在消息处理结束后,其回调至Runnable 的run方法中,需要注意的是这里的仍然是在UI线程中,因为我们创建的Handler是在UI线程中,且Handler将Runnable内部封住成Message的形式,在消息分发时首先检测callback是否为空,如下所示:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
...
}
}
如果不为空则调用handleCallback方法,但是该方法实际上是调用message.callback.run(),而message.callbac是我们在添加消息时赋值的Runable,所以最终调用的是Runable中的run()方法,因此还是回到UI线程中,可以更新UI界面。
private static void handleCallback(Message message) {
message.callback.run();
}