深入理解Handler

摘要

1.Android提供Handler的原因?

答:Android规定访问UI只能通过主线程,如果子线程访问UI,程序会抛出异常;ViewRootImpl在checkThread方法中做了判断。

2.系统为什么不允许在子线程中去访问UI?

答:因为Android的UI控件不是线程安全的,多线程并发访问可能会导致UI控件处于不可预期的状态,为什么不加锁?因为加锁机制会让UI访问逻辑变得复杂;其次锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。所以Android采用了高效的单线程模型来处理UI操作。

ThreadLocal的工作原理

ThreadLocal是一个线程内部的数据存储类,通过它可以在指定线程存储数据,数据存储后,只能在指定的线程可以获取到存储的数据,对于其他线程则无法获取到数据。

ThreadLocal使用场景

1.当数据是以线程作为作用域并且不同线程有不同副本的时候,就可以考虑使用;

2.复杂逻辑下的对象传递。

ThreadLocal和锁的比较

ThreadLocal解决的是同一个线程内的资源共享问题,而synchronized解决的是多个线程间的资源共享问题。

ThreadLocal原理

不同线程访问同一个ThreadLoacl的get方法,ThreadLocal的get方法会从各自的线程中取出一个数组,然后再从数组中根据当前ThreadLocal的索引去查找对应的Value值。

1.ThreadLocal set方法

JDK中与Android SDK的ThreadLocal有些许区别,我们以SDK中的为例:

1
2
3
4
5
6
7
8
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}

Values是Thread类内部专门用来存储线程的ThreadLocal数据的,它内部有一个数组private Object[] table,ThreadLocal的值就存在这个table数组中。如果values的值为null,那么就需要对其进行初始化然后再将ThreadLocal的值进行存储。

查看Values#put()方法可以总结出ThreadLocal数据的存储规则:ThreadLocal的值在table数组中的存储位置总是ThreadLocal的索引+1的位置。

2.ThreadLocal get方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public T get() {
// Optimized for the fast path.
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values != null) {
Object[] table = values.table;
int index = hash & values.mask;
if (this.reference == table[index]) {
return (T) table[index + 1];
}
} else {
values = initializeValues(currentThread);
}
return (T) values.getAfterMiss(this);
}

计算索引index位置,value=table[index+1]。

MessageQueue的工作原理

  1. MessageQueue通过单链表的数据结构来维护消息队列;

  2. MessageQueue#enqueueMessage(Message msg, long when):往消息队列插入一条消息;

  3. MessageQueue#next()是一个无限循环的方法,如果消息队列中没有消息,那么next方法会一直阻塞在这里;当有新消息到来时,next方法会返回这条消息并将其从单链表中移除;

  4. MessageQueue#quit(boolean safe)设置清空消息。

Looper的工作原理

  1. Looper#prepare()方法给Thread设置Looper;

  2. Looper#prepareMainLooper()方法给主线程创建Looper,本质还是调用Looper#prepare()方法;

  3. Looper#myLooper()方法得到当前Thread的Looper;

  4. Looper#getMainLooper()方法得到主线程的Looper;

  5. Looper#loop()是一个无限循环方法,除非退出Looper会让MessageQueue#next()返回的消息为空,否则调用MessageQueue#next()方法获取新消息是一个阻塞操作,当没有消息时,next方法会一直阻塞,这也导致了loop方法一直阻塞。如果MessageQueue的next方法返回了新消息,Looper就会调用msg.target.dispatchMessage(msg)处理这条消息;

  6. Looper#quit()和Looper#quitSafely()方法最终调用MessageQueue#quit(boolean safe)方法来退出,二者的区别是:前者会直接退出Looper,后者只是设定一个退出标记,然后把消息队列中的已有消息处理完毕后才安全地退出。Looper退出之后,通过Handler发送的消息就会失败,这个时候Handler的send方法会返回false。在子线程中,如果手动为其创建了Looper,那么在所有的事情完成以后应该调用quit方法来终止消息循环,否则这个子线程就会一直处于等待的状态,而如果退出Looper以后,这个线程就会立刻终止,因此建议在不需要的时候终止Looper。

Handler的工作原理

  1. 可以指定一个特殊的Looper来构造Handler;

  2. Handler#enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)方法将此Handler赋值给msg.target;

  3. Looper#loop()方法最终将消息交给msg.target处理,即调用Handler#dispatchMessage(Message msg)方法;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
// msg.callback是一个Runnable,
// 是Handler#post..()方法所传递的Runnable参数
handleCallback(msg);
} else {
// 如果在构造Handler时传了Callback参数,
// 则调用Callback的handleMessage(msg)
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
// 调用Handler的handleMessage方法来处理消息,
// Handler子类需重写handlerMessage(msg)方法
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
public interface Callback {
public boolean handleMessage(Message msg);
}
// 默认空实现
public void handleMessage(Message msg) {
}

主线程消息循环

1.Android的入口方法为ActivityThread#main(),在main方法中系统会通过Looper.prepareMainLooper()来创建主线程的Looper以及MessageQueue,并通过Looper.loop()来开启主线程的消息循环。

2.Looper.loop()是一个无限循环方法,如果主线程的Looper终止,则应用程序会抛出异常。那么既然主线程卡在这里了,Activity为什么还能启动?点击一个按钮仍然可以响应?

答:startActivity的时候,会向AMS(ActivityManagerService)发一个跨进程请求(AMS运行在系统进程中),之后AMS启动对应的Activity;AMS也需要调用App中Activity的生命周期方法(不同进程不可直接调用),AMS会发送跨进程请求,然后由App的ActivityThread中的ApplicationThread会来处理,ApplicationThread会通过主线程线程的Handler将执行逻辑切换到主线程。四大组件的生命周期都是以消息的形式通过UI线程的Handler发送,由UI线程的Looper执行的。

Android提供的在子线程中进行UI操作的几种方式

Handler#post()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class MainActivity extends Activity {
private Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handler = new Handler();
new Thread(new Runnable() {
@Override
public void run() {
handler.post(new Runnable() {
@Override
public void run() {
// 在这里进行UI操作
}
});
}
}).start();
}
}

Activity#runOnUiThread(Runnable action)

1
2
3
4
5
6
7
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}

如果当前的线程不等于UI线程(主线程),就去调用Handler的post()方法,否则就直接调用Runnable对象的run()方法。

View#post()

1
2
3
4
5
6
7
8
9
10
11
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().post(action);
return true;
}

先看下面这张图,图画的是Android中Activity启动的部分流程图:

Android之Activity启动的部分流程图

很明显可以看出:

1.在onResume()之后调用(不包含onResume()方法的调用),attachInfo不为null,使用attachInfo.mHandler,attachInfo的mHandler是在Activity调用onResume()时创建的Handler对象,属于主线程Handler。

2.在onResume()之前调用(包含onResume()方法本身),attachInfo为null,调用getRunQueue().post(action);会将Runnable加入View自己维护的HandlerActionQueue中;HandlerActionQueue内部维护一个HandlerAction[]数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class HandlerActionQueue {
private HandlerAction[] mActions;
private int mCount;
public void post(Runnable action) {
postDelayed(action, 0);
}
public void postDelayed(Runnable action, long delayMillis) {
final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
synchronized (this) {
if (mActions == null) {
mActions = new HandlerAction[4];
}
mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
mCount++;
}
}
// 处理数组中的Runnable
public void executeActions(Handler handler) {
synchronized (this) {
final HandlerAction[] actions = mActions;
for (int i = 0, count = mCount; i < count; i++) {
final HandlerAction handlerAction = actions[i];
handler.postDelayed(handlerAction.action, handlerAction.delay);
}
mActions = null;
mCount = 0;
}
}
....
}

那么executeActions()方法什么时候被执行?参数handler又哪个Handler?

搜索发现在ViewRootImpl#performTraversals()方法中调用了executeActions()方法。传入的参数Handler是mAttachInfo.mHandler,同1(attachInfo的mHandler是在Activity调用onResume()时创建的Handler对象,属于主线程Handler。)

所以假设我们在Activity#onCreate()中通过View.post一个Runnable对象,最终还是在第一个performTraversals()方法执行的时候,加入到了MainLooper的MessageQueue里。

参考

  1. Android异步消息处理机制完全解析
  2. 深入理解之 Android Handler
  3. Androd开发艺术探索 第10章 Android的消息机制 读书笔记
  4. Android应用启动优化:一种DelayLoad的实现和原理(下篇)
  5. 《Android开发艺术探索》
  6. Android中MotionEvent的来源和ViewRootImpl