Android Handler 机制及线程间通信

本文主要分析 Handler机制和源码,线程切换的原理,下面是大致的目录:

  • 子线程可以更新UI吗,为什么
  • 常用的更新子线程更新切换方式
  • Handler 源码解析
  • handler.post原理
  • runOnUiThread原理
  • 主线程一直死循环取消息,为什么没有卡死
  • 子线程间怎么发送消息

在onCreate()中开启子线程更新UI 有问题吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MainActivity extends Activity {
private TextView mName;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

mName = (TextView) findViewById(R.id.tv_name);
mName.setText("我是在UI线程更新UI");
new Thread(new Runnable() {
public void run() {
mName.setText("我是在非UI线程更新UI");
}
}).start();
}
}

这样可以看到更新成功了,且没有抛出异常. 子线程能直接更新UI ? 其实是不能。

为什么不能在子线程更新UI?

因为Android的UI控件不是线程安全的,多个线程并发访问可能会导致UI 控件处于不可预期的状态,那既然这样,为什么不加锁呢?
缺点: 加锁会让UI访问的逻辑变的复杂,也会降低UI 的访问效率,因为锁的机制会阻塞某些线程的执行. 所有就采用单线程的方式来更新UI

1
2
3
4
5
6
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}

但是上面为什么可以更新UI呢?

因为执行速度, 因为 ViewRootImpl 这个时候还没创建,这是在调用了 onResume 之后才创建的, ViewRootImpl关于UI 的操作都会 checkThread 如果不在主线程就会抛出异常。所以上面更新的时候还没执行到 checkThread 方法。

提到异步处理消息,我们常用的子线程更新UI 的方法有哪些呢?

  • Handler.sendMessage()
  • Handler的post()方法。
  • Activity的runOnUiThread()方法。
  • View.post(Runnable r)方法。

作为一个Android 开发,我们肯定会想到 Handler ,下面是一个最简单的但是不太规范的示例,这样我们就可以在子线程中做了处理然后更新 UI 了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

new Thread(new Runnable() {
@Override
public void run() {

Message msg = handler.obtainMessage();
handler.sendMessage(msg);
}
}).start();
}

Handler 可以理解为处理器

首先看下构造方法 我们最常用的 new handler方法做了什么

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
    public Handler() {
this(null, false);
}
//调用2个参数的构造方法


public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
// 为了代码整洁,省略了部分内容
。。。。
}

mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}


我们再看下其他不同参数个数的构造方法


public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}

根据上面的构造方法可以看出来, 我们在创建Handler 时,如果不指定 callback 时,会默认为空, 如果没有指定 Looper 时,系统会自动 通过 Looper.myLooper() 帮我们指定当前线程的 Looper 。 如果 looper 对象为空,就会抛出异常。

我们看下 Looper.myLooper() 是怎么实现的呢

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}


再看get方法怎么实现的

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();
}

根据方法和资料我们可以知道, ThreadLocal 是所属与线程的,使用ThreadLocal创建的变量只能被当前线程访问,其他线程则无法访问和修改。

根据经验,有get 肯定有set 的地方,我们看下 Looper 中的 ThreadLocal 的set 的地方

1
2
3
4
5
6
7
8
9
10
public static void prepare() {
prepare(true);
}

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));
}

根据上面可以看到, 一个线程中最多只能有一个 Looper ,在想使用到 Looper 的线程 中调用 prepare 方法就可以创建出 Looper了, 在 new handler 时就不会出现 looper 为空的情况了

但是有没有发现,我们在主线程就是没有调用 prepare 方法呀, 使用的时候也没有报错。这又是怎么回事呢?

通过看App 启动流程的代码可以发现, 在 ActivityThread 类中的 main 方法 这个类中的main方法就是整个App 的主线程的执行入口

在这个方法中,通过调用 Looper.prepareMainLooper() 去初始化了主线程的 looper

1
2
3
4
5
6
7
8
9
10
11


public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}

可以看到 prepare(false); 创建了一个不可以退出的 looper。

然后 main 方法中还通过调用 Looper.loop() 开启主线程的循环。

Looper.loop() 死循环,处理消息

直接看loop方法到底做了什么

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 static void loop() {

final Looper me = myLooper();
//有looper才能循环吧,获取当前线程的 looper
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
// 获取当前looper 中的 MessageQueue
final MessageQueue queue = me.mQueue;

。。。

for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}


。。。

try {
msg.target.dispatchMessage(msg);
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
。。。

msg.recycleUnchecked();
}
}

中间省略部分源码, 跟着我们上面的分析,因为主线程在 App 初始化时在程序的住入口已经初始化过Looper 和开启了 loop 循环, 内容就是 从当前线程也就是主线程 的looper 关联的 MessageQueue 里不停的死循环 取出 Message 消息, 如果有消息就调用 msg.target.dispatchMessage(msg); 分发消息。

msg.target.dispatchMessage(msg);

处理消息。消息分发 但是这个 msg.target 又是什么东西呢

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
经过看源码 msg.target 的赋值是在这做的 Message 类中

public static Message obtain(Handler h, int what,
int arg1, int arg2, Object obj) {
Message m = obtain();
m.target = h;
m.what = what;
m.arg1 = arg1;
m.arg2 = arg2;
m.obj = obj;

return m;
}



public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}

msg.target其实就是初始化的handler,然后调用Handler的dispatchMessage();

从消息池中取出消息,如果没有的话就直接new一个Message对象,所以我们在写项目创建Message对象的时候尽量用handle.obtainMessage(),不要直接new Message(),复用会比较好。

知道 msg.target 就是 handler 了,那看下 handler 的 dispatchMessage 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}

先判断msg.的callback是否为空,接着再判断handler的mCallBack是否为空,
如果都为空,所以执行handleMessage(),这里面是一个空方法,需要我们重写。
上面那个 msg.callback 和 mCallback 怎么用呢?请接着往下看

handler.post 方法

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
36
37
38
39
40
41
42
43
44
45
  
handler.post(new Runnable() {
@Override
public void run() {

}
});


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;
}

最后把 Runnable 的callback 赋值成 Message 的 callback
再结合上面的 dispatchMessage 方法 , msg.callback 就不是空了


private static void handleCallback(Message message) {
message.callback.run();
}

调用 callback 的 run 方法。 handler 的 post 方法就这这么简单,
把一个可以可以执行的 runnable 放到 msg 的 callback 中抛到主线程,
调用 run 方法执行,然后这个 run 方法中的内容就是在主线程执行的了。



接下来看 创建Handler 的另一种方式,可以有个 callback 的写法, 这个时候
handler 中的 mCallback 就不为空了, 就可以执行了,直接执行 callback 中的方法即可
这个也是在当前线程(主线程)执行的。
Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
return false;
}
});

MessageQueue

MessageQueue的 next 方法做了什么操作呢

1
2
3
4
5
6
7
8
9
10
11
12
    Message next() {
...
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//请注意这个方法 这个
nativePollOnce(ptr, nextPollTimeoutMillis);
...
}
...
}

也是死循环取消息 同一线程在同一时间只能处理一个消息,同一线程代码执行是不具有并发性,所以需要队列来保存消息和安排每个消息的处理顺序。

多个其他线程往UI线程发送消息,UI线程必须把这些消息保持到一个列表(它同一时间不能处理那么多任务),然后挨个拿出来处理,每一个Looper线程都会维护这样一个队列,而且仅此一个,这个队列的消息只能由该线程处理。

Message

Message 就没有太多可以说的,它就是一个消息的载体,用来保存消息的。

消息发送

常用的方法

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81

public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}


public final boolean sendEmptyMessage(int what)
{
return sendEmptyMessageDelayed(what, 0);
}


最后都会调用到

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}

接着看 queue.enqueueMessage 是怎么处理的呢



boolean enqueueMessage(Message msg, long when) {
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.");
}

synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}

msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
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;
}

主要目的就是把 Message 添加到 MessageQueue 中
如果消息在此时queue 中没有消息,就加入队列后 nativeWake 唤醒,通知取消息,
如果队列中有消息,就加入进去。

主线程一直 loop 死循环为什么没有卡死

主线程的死循环一直运行是不是特别消耗 CPU 资源呢? 其实不然,这里就涉及到 Linux pipe/epoll机制,简单说就是在主线程的 MessageQueue 没有消息时,便阻塞在 loop 的 queue.next() 中的 nativePollOnce() 方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。

这里采用的 epoll 机制,是一种 IO 多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步 I/O ,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量 CPU 资源。

其实这也是整个 Android 系统的做法,App 启动,然后就进入死循环,如果没有消息,就阻塞在哪些,AMS, WMS 等等 会通过binder抛过来一些消息,然后执行 onCreate 之类的方法,Activity 的生命周期的方法都是 msg,有消息过来就执行

runOnUiThread

1
2
3
4
5
6
7
8
9

@Override
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}

可以看到代码 如果当前线程是主线程, 直接调用 Runnable 的run 方法,去执行, 如果不是主线程, 则调用 mHandler 的 post 方法 ,上面我们已经分析过 post 方法的原理了。 上面这个 mHandler 就是 Activity 的 Handler 也就是主线程的 Handler ,发送到主线程执行这个 Runnable。

子线程间怎么发送消息

根据上面的分析,我们在子线程中要使用handler 发送消息的话, 需要 手动在子线程的Handler 创建之前,调用 Looper.prepare 创建一个looper 来跟当前线程关联, 然后在创建完成 handler之后 调用 Looper.loop() 开启消息循环, 然后其他线程就可以通过这个线程创建出的 handler 往这个线程发送消息了。

总结

Handler 机制是现在各个公司面试必问的问题,掌握 Handler 原理对我们日常开发工作也是非常有帮助的。代码量也不大,比较好懂,作为一个 Android 开发工程师非常有必要掌握这些知识点。 欢迎交流学习。

参考文章

Android异步消息处理机制完全解析

Android 源码

请联系我!