Android的消息机制-Handler

为了更好的理解 Looper 的工作原理,我们需要对 ThreadLocal 进行了解,如果对 ThreadLocal 没有了解的童鞋,可以参看 ThreadLocal 原理

概述

一句话原理:共享内存

Handler 作为日常开发的必备,不可避免就要涉及这方面的知识。从开发者角度来说,Handler 是 Android 消息机制的上层接口,使得开发的时只需与 Handler 交互即可。Handler 使用也很简单,能够轻松将一个任务切换到 Handler 所在的线程中执行

很多人认为Handler的作用就是更新UI,的确没错,但是更新UI仅仅是Handler的一个特殊的使用场景。具体来说,就是有时候需要在子线程做一些耗时操作,比如说访问网络或者耗时的I/O操作,当这些耗时操作完成时,程序的UI进行相应的改变。由于安卓开发规范的限制,我们不能在子线程中访问UI控件,因为UI的控件是线程非安全的,这个时候通过Handler就可以将更新UI的操作切换到主线程中执行。

Android 的消息机制主要是指 Handler 的运行机制。事实上,Handler,Looper,MessageQueue 是一套运行体制而出现的,MessageQueue 是一个消息队列,以队列形式提供插入和删除,主要用于消息的存储,内部实现是采用单链表的形式来组织 Message。Looper 用于处理消息,Looper 内部会以无限循环去查是否有新的 Message ,有则处理,没有就等待。需要特殊说明的是,Looper 内部是使用 ThreadLocal 实现的,由于ThreadLocal 可以在每一个线程中互不干扰的存取数据,所以通过ThreadLocal 就可以轻松获取每个线程的 Looper。

Message :android.os.Message是定义一个Messge包含必要的描述和属性数据,并且此对象可以被发送给android.os.Handler处理。属性字段:arg1、arg2、what、obj、replyTo等;其中arg1和arg2是用来存放整型数据的;what是用来保存消息标示的;obj是Object类型的任意对象;replyTo是消息管理器,会关联到一个handler,handler就是处理其中的消息。通常对Message对象不是直接new出来的,只要调用handler中的obtainMessage方法来直接获得Message对象。

需要特殊说明的是,线程是默认没有 Looper 的,如果需要使用 Handler 就必须为线程创建Looper,但是APP 的主线程中,即 ActivityThread ,在主线程被创建的时候就会初始化 Looper。另外,在没有Looper 的线程创建 Handler 也会失败。

Handler 工作过程
Hnadler 的工作流程

使用案例

话不多说,上例子:

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
public class MainActivity extends AppCompatActivity {

private TextView textView;
private String TAG = "MainActivity";
private int i = 0;

Handler mHandler = new Handler(){
/**
* handleMessage接收消息后进行相应的处理
* @param msg
*/
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if(msg.what==1){
textView.setText(msg.arg1+"");
}
}
};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.textView);
}
public void onClick(View v){
++i;
//创建新的线程
new Thread(){
@Override
public void run() {
super.run();
doSendMsg();
}
}.start();
}

/**
* 在子线程中做耗时操作,完成之后,通知Handler更新UI
*/
private void doSendMsg(){
try {
Thread.sleep(1000);//模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
Message message = Message.obtain();
message.arg1 = i;
message.what = 1;
mHandler.sendMessage(message);
}

}

原理分析

代码版本 : Android API25

Message

生成一个 Message

前面讲过 Message 通常不是 new 出来的,而是通过调用 Handler 的obtainMessage() 得到一个新的 Message:

1
2
3
4
public final Message obtainMessage()
{
return Message.obtain(this);
}

而 Handler 就调用 Message 的 obtain(Handler h) 方法:

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
public final class Message implements Parcelable {

public int what; // 让接收者知道这是什么
public int arg1; // 存储 int 用于传递过去
public int arg2;
public Object obj; // 传递一个对象过去
public Messenger replyTo; // 可以发送对此消息的回复。具体如何使用取决于发送者和接收者
public int sendingUid = -1;

/*package*/ static final int FLAG_IN_USE = 1 << 0; // 标记消息被使用
/*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1; // 标记消息是异步的
/*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;
/*package*/ int flags;
/*package*/ long when; // Message 的执行时间 重要!!!
/*package*/ Bundle data;
/*package*/ Handler target; // target 负责处理该消息
/*package*/ Runnable callback; // Runnable 类型的 callback
/*package*/ Message next; // 下一条消息,因为消息队列是链式存储的

private static final Object sPoolSync = new Object(); // 控制并发访问
private static Message sPool; // 回收池的头结点
private static int sPoolSize = 0;
private static final int MAX_POOL_SIZE = 50;

private static boolean gCheckRecycle = true;

/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool; // 将头结点赋值给Message
sPool = m.next; // 将头结点更新为下一个节点
m.next = null; // 断掉以前头节点与当前节点联系
m.flags = 0; // clear in-use flag
sPoolSize--; // 将回收池的数量减一
return m;
}
}
return new Message();
}

public static Message obtain(Handler h) {
Message m = obtain();
m.target = h; // 注意,这里将 Handler 保存在了 target 中,后面会调用target来处理这个Message
return m;
}

// ...
}

到了最后就是Message 的 obtain() 方法,从 global pool 返回一个新的Message实例。 允许我们在许多情况下避免分配新对象。而这个 global pool 呢,其实就是一个单链表,从头结点取一个 Message ,如果没有就 new 一个 Message。而这个单链表呢,就是讲无用需要回收的 Message 组织起来的。the global pool 其实就是使用静态常量组织了一些无用了的 Message,组织的数据结构就是单链表。

看源码就知道,重载了多个obtain 方法,其实就是把上述可选参数配置一下,然后调用 obtain() 得到一个Message。

回收 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
public void recycle() {
if (isInUse()) { // isInUse() return ((flags & FLAG_IN_USE) == FLAG_IN_USE);
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return; // 在用就不回收
}
recycleUnchecked(); // 直接回收
}

/**
* Recycles a Message that may be in-use.
* Used internally by the MessageQueue and Looper when disposing of queued Messages.
*/
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE; // 设置为没有使用
what = 0; // 抹去数据
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;

synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool; // 将当前 Message 的下一个设置为以前的头结点
sPool = this; // 更新头结点为当前节点
sPoolSize++;
} // 如果到达MAX_POOL_SIZE数量,这个Message就会因为没有与引用链相连而被GC回收
}
}

回收还是很简单的,就是先检验是否在使用,如果不是,抹去上面的数据,将 Message 加到我称为 回收池 的链表里。需要注意的是,这里如果回收池数量到了上限,这个Message就会因为没有与引用链相连而被GC回收。

MessageQueue

MassageQueue 叫做消息队列,通过一个单链表的数据结构来维护消息列表,而且单链表在插入和删除上比较有优势。MessageQueue 主要包含两个操作:插入 和 读取。插入读取对应的方法分别是enqueueMessage(Message msg, long when) 和 next()。

工作流程

这里的工作流程先是讲一下例子里面的工作流程,其次补充一些必要的流程,如回收等。

UI线程 Looper 的创建

我们知道 UI 线程是在 ActivityThread 中创建的,这个函数就是整个 APP 的入口。接下来就是 ActivityThread 中的 main:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) {
...
Looper.prepareMainLooper(); // 1. 创建UI线程的 Looper

ActivityThread thread = new ActivityThread();
thread.attach(false);

if (sMainThreadHandler == null) {
// UI 线程的Handle
// getHandler() 得到的是 ActivityThread.H (extends Handler)
sMainThreadHandler = thread.getHandler();
}

// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop(); //2. 执行消息循环

throw new RuntimeException("Main thread loop unexpectedly exited");
}

看到没有,通过1,2步的配置,这时候 UI 线程中 Looper 其实已经跑起来了,在程序中就已经可以使用 Handler 了。而子线程却默认没有配置

需要注意的 UI 线程使用的 prepareMainLooper() 来准备 Looper,但是这个方法虽然是 public 的,但是这是专门为 UI 线程量身定做的,我们绝对不可以使用,我们准备Looper可以使用 Looper 的 prepare()就好。

prepareMainLooper()

接下来我们来一步一步分析 (Looper 源码:)

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
public static void prepareMainLooper() {
prepare(false); // new 一个 Looper 放到该线程的ThreadLocalMap中
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
// 将 UI 线程的 Looper 放到静态常量 sMainLooper 中,那么随时随地都可以new 出主线程的 Handler
sMainLooper = myLooper(); // myLooper() return sThreadLocal.get();
}
}

public static void prepare() {
prepare(true); // 子线程中默认是可以销毁消息队列的
}

private static void prepare(boolean quitAllowed) { // True if the message queue can be quit.
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
// sThreadLocal 是在类定义的时候就初始化了的 static final ThreadLocal<Looper>
sThreadLocal.set(new Looper(quitAllowed));
}

public static @Nullable Looper myLooper() {
return sThreadLocal.get(); // 得到当前线程的 Looper
}

很简单,结合注释,应该都懂了,就是 new 了一个 Looper ,放到了主线程的 ThreadLocalMap 中。那 new 的时候干了什么呢?

1
2
3
4
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}

原来是这样,在 new Looper 的时候就创建了 UI 线程的消息队列,并且指定不可以删除。

1
2
3
4
5
6
7
// True if the message queue can be quit.
private final boolean mQuitAllowed;

MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}

队列是否可以删除,一直向下传递,最后原来是保存在这里。但是这里还有一个 Native 方法,是干嘛的呢?笔者认为是得到了这个对象所在线程的引用。

到这里,ActivityThread 中的第一步就算是完成了。由于 sMainThreadHandler 的后期使用涉及复杂,就留到后面讲解,这里用 例子中的 Handler 代替讲解,最后执行的 Handler 是一样的,但是 ActivityThread.H(sMainThreadHandler )封装了其他东西。

创建 Handler

看着例子中的 Handler 是直接 new 出来的,那我们看一下 Handler 的无参构造方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Handler {
public Handler() {
this(null, false);
}

public Handler(Callback callback, boolean async) {
...
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;
}
}

我们看到 Handler 在构造方法中,通过 Looper.myLooper() 得到当前线程(UI 线程)的Looper,并且保存到本地 final 变量 mLooper 中。

要知道,消息队列被封装在 Looper 中,而每一个 Looper 又会关联一个线程(Looper 通过 ThreadLocal 封装),最终等于每一个消息队列都会关联一个线程。同时,由上代码可知,每个 Handler 也都会关联一个消息队列。在这里需要注意,Looper 和 MessageQueue 并没有与 Handler关联,而是Handler 与 Looper 和 MessageQueue 建立联系。

Looper.loop()

创建了Looper之后,就要执行消息循环了,我们知道,通过 Handler 来 Post 消息给消息队列,那么怎么处理呢?那就是最开始第二步的 Looper.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
36
37
38
public static void loop() {
final Looper me = myLooper(); // 获取当前线程的 Looper
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue; // 1. 获取消息队列

// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();

for (;;) { // 2. 消息循环
Message msg = queue.next(); // might block可能阻塞 3. 获取消息
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}

final long traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
msg.target.dispatchMessage(msg); // 4. 处理消息
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}

// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();

msg.recycleUnchecked(); // 回收
}
}

我们可以看到 loop 方法中实际上就是建立一个死循环,然后通过从消息队列中逐个取出消息,最后进行处理,至到取到 null 值才退出。这里并没有任何的阻塞,那我消息取完了就退出了么?不,原理请看 MassageQueue 的 next() :

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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
// 注意,以下的代码都在循环体里面
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}

nativePollOnce(ptr, nextPollTimeoutMillis); // 重要!!!

synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis(); // 从开机到现在的毫秒数(手机睡眠的时间不包括在内)
Message prevMsg = null; // 前驱结点
Message msg = mMessages; // 头结点

// 如果没有 Handler,就在列表中找下一个同步的 Message 来执行。
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}

if (msg != null) { // 如果头结点不为空
if (now < msg.when) { // 如果还没有到执行时间
// Next message is not ready. Set a timeout to wake up when it is ready.
// 设置唤醒时间,距离多久执行 与 int 最大值 2^31 - 1 作比较
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 注意这时候已经到执行时间了
// Got a message.
mBlocked = false;
// 从消息队列中取出这个 Message
if (prevMsg != null) { // 如果前驱结点不为 null,
prevMsg.next = msg.next; // 请参看 next() 图 1
} else {
mMessages = msg.next; // 将头结点后移
}
msg.next = null; // 断掉要处理的 Message 与 消息队列的联系
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse(); // 标记为使用
return msg;
}
} else { // 头结点为空
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}

// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}

if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
} //同步代码块结束

// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler

boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}

if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}

// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;

// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}

// Disposes of the underlying message queue.
// Must only be called on the looper thread or the finalizer.
private void dispose() {
if (mPtr != 0) {
nativeDestroy(mPtr);
mPtr = 0;
}
}

代码虽然很长,但是我们还是得看啊,其实就是取出单链表(我们前面已说过,MessageQueue其实是一个单链表结构)中的头结点,然后修改对应指针,再返回取到的头结点而已。因为这里采用的是无限循环,所以可能会有个疑问:该循环会不会特别消耗CPU资源?其实并不会,如果messageQueue有消息,自然是继续取消息;如果已经没有消息了,此时该线程便会阻塞在该next()方法的 nativePollOnce() 方法中,主线程便会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生(设置的nextPollTimeoutMillis到了)时,才通过往pipe管道写端写入数据来唤醒主线程工作。这里涉及到的是Linux的pipe/epoll机制,epoll机制是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。

next() 图 1

总结:
通过 Looper.loop() 来创建 Looper 对象( 消息队列封装在 Looper 对象中 ),并且保存在 sThreadLocal 中,然后通过 Looper.loop() 来执行消息循环。

说了取,当然接着就是分发了。我们调用msg.target.dispatchMessage(msg) 来执行 Message 。在创建 Message 的过程中,传过来的 Handler 的引用就被保存在了Message中(最上面有代码和讲解)。接下来看一下 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
final Callback mCallback;

public interface Callback {
public boolean handleMessage(Message msg);
}

public void handleMessage(Message msg) {
}

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

private static void handleCallback(Message message) {
message.callback.run(); // 此时是在 Handler 所在线程中执行
}

这里面也特别简单,大家都知道当我们在 Handler post()消息的时候是使用 Runnable,而 sendMessage() 是自己构建的 Message。这里首先判断 Message 类型,如果是 Runnable ,就调用run运行,如果不是,先判断创建 Handler 的时候是否设置回调,设置了就调用回调中的处理方法 handleMessage(msg),如果没有就使用默认的 handleMessage(msg),这时候的这个方法大多数时候都会被重写,就像例子一样。

这里我们可以看到,在分发消息时三个方法的优先级分别如下:

  • Message的回调方法优先级最高,即message.callback.run();
  • Handler的回调方法优先级次之,即Handler.mCallback.handleMessage(msg);
  • Handler的默认方法优先级最低,即Handler.handleMessage(msg)。
    Handler 工作原理.png

使用 Handler 来 sendMessage(Message msg)

既然提到 post() 和 sendMessage(),那么下面就讲解一下它是如何将一个 Message 加到队列中的。

现讲解 sendMessage() 吧,例子就是最上面的例子,那我们接着看 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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
* Pushes a message onto the end of the message queue after all pending messages
* before the current time. It will be received in {@link #handleMessage},
* in the thread attached to this handler.
*
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0); // 默认延时为 0
}

public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) { // 校验延时,因为延时只能 >= 0
delayMillis = 0;
}
// SystemClock.uptimeMillis() 从开机到现在的毫秒数(手机睡眠的时间不包括在内)
// SystemClock.uptimeMillis() + delayMillis算出来就是更新时间
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);

}

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
// 这个值是通过从Looper 赋值过来的,就意味着是持有和Looper 中相同的引用
// Looper 中的修改的话mQueue,这个值也会被修改
// 当调用 Looper.quit() 的时候,这个值就被置空了的。
MessageQueue queue = mQueue;
if (queue == null) { // 因为需要发送到 MassageQueue中,所以不能为空
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) {
msg.target = this; // 保存 Message 的 target
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}

一直往下调用了三个方法才到底,还好都简单,来看一下。将设置的时延转化成应该被执行的时间,拿到关联的消息队列,随后保存 Message 的 target,然后调用消息队列的 enqueueMessage(msg, uptimeMillis),将这个Message 加入队列。那怎么加的么?接下来就是 MessageQueue 的 enqueueMessage(Message msg, long when)

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
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) { // 一个新的 Message 是不会 InUse 的,在回收的时候设置为没在使用的
throw new IllegalStateException(msg + " This message is already in use.");
}

synchronized (this) { // 获得自身的同步锁
if (mQuitting) { // MessageQueue 正在退出
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle(); // 将Message实例回收
return false;
}

msg.markInUse(); // flags |= 1 标记正在使用
msg.when = when; // 设置要插入 Message 的执行时间
Message p = mMessages; // 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 {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous(); //记得看上面的英文注释 默认为 false
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); // 唤醒线程的 Native 方法
}
}
return true;
}

看 enqueueMessage 的实现,它的主要操作就是单链表的插入操作,并且这链表是以执行时间 when 作为顺序的。需要重提的是,这时候的 when 已经转换成了距离开机到执行的毫秒数。

  1. 校验。是检测target是否存在,因为Message. targe 是用来处理这个 Message 的,所以一定要有target,其次判断当前 Message 是否正在被使用,然后验证当前 MessageQueue 是不是已经被 quit 了(mQuitting),验证通过过后就是正式的插入操作。
  2. 配置。设置 Message 该有的属性,msg.markInUse(); msg.when = when;
  3. 这个方法比较巧妙,将几种具体情况用一份代码解决了,但是都可以归结为:在头结点之前插入结点。看到这里应该注意到,如果插入的是需要非延时 Message,并且线程阻塞了,就会调用 nativeWake(mPtr) 唤醒线程。

    • 当还没有链表的时候(p == 0)
    • 消息的执行时间 比 里面的消息还要早(when == 0 || when < p.when)
  4. 在链表中间或最后插入。循环遍历链表,拿到链表合适的 Message,然后再将新 Message 插入。由于还没有到消息的处理时间,就不会唤醒线程。此插入过程如下:

使用 Handler 来 post()

先上一个使用的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private Handler mHandler;//全局变量
@Override
protected void onCreate(Bundle savedInstanceState) {
mHandler = new Handler();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);//在子线程有一段耗时操作,比如请求网络
mHandler.post(new Runnable() {
@Override
public void run() {
mTestTV.setText("This is post");//更新UI
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}

例子很简单,就不讲解了,接下来就来看一下 Handler 怎么 post 的吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* Causes the Runnable r to be added to the message queue.
* The runnable will be run on the thread to which this handler is
* attached.
*
* @param r The Runnable that will be executed.
*
* @return Returns true if the Runnable was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}

private static Message getPostMessage(Runnable r) {
Message m = Message.obtain(); // 得到一个新的 Message
m.callback = r; // 是使用 post 的 Runnable 才会保存有这个参数
return m;
}

眼尖的童鞋估计看到就要easy了。在 post 一开始就调用了 sendMessageDelayed(Message msg, long delayMillis),是不是很眼熟,对的,前面sendMessage(Message msg)一开始也是调用这个方法。这就是非延时消息的插入。后面具体如何插入请参看上一条。

同样的,如果是使用 postDelayed(Runnable r, long delayMillis) 呢?使用例子请参看 面试题:Handler机制,Handler除了线程通信还有什么作用 中的作用二的第二种实现方式。转回来看 Handler 的 postDelayed :

1
2
3
4
public final boolean postDelayed(Runnable r, long delayMillis)
{
return sendMessageDelayed(getPostMessage(r), delayMillis);
}

原来啊,大家都用的一套,还是调用的 sendMessageDelayed 方法,只不过延迟时延不再为默认的 0. 这时候就是延迟消息的插入。

工作原理时间图.jpg

退出消息循环

退出消息循环有两种方式,分别是Looper 的 quit()quitSafely()

1
2
3
4
5
6
7
public void quit() {
mQueue.quit(false);
}

public void quitSafely() {
mQueue.quit(true);
}

这还是很简单,直接调用了 MassageQueue 的 qiut(boolean safe) :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void quit(boolean safe) {
if (!mQuitAllowed) { // 主线程设置不允许退出,其他子线程默认设置的true
throw new IllegalStateException("Main thread not allowed to quit.");
}

synchronized (this) {
if (mQuitting) { // 默认为 false,表示是否正在退出消息队列
return;
}
mQuitting = true; // 全局唯一一次赋值,表示正在退出消息队列

if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}

// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}

首先判断是否是主线程,因为主线程的消息队列不允许退出,然后判断当前线程是否正在退出。值得注意的是,mQuitting 是 MassageQueue 的成员变量,是拥有默认值的,默认值是 false。如果不是正在退出消息队列,则将其标志为 正在退出,注意,全局就只有这里修改了 mQuitting 的值。然后判断根据要求是否安全移除 MessageQueue。

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
private void removeAllMessagesLocked() {
Message p = mMessages; // 头结点
while (p != null) {
Message n = p.next; // 下一个结点
p.recycleUnchecked(); // 前面讲过,就是将数据抹去,放入回收的那个链表
p = n;
}
mMessages = null; // 将MessageQueue 中保存的头结点设置为 null
}

private void removeAllFutureMessagesLocked() {
final long now = SystemClock.uptimeMillis(); // 从开机到现在的毫秒数(手机睡眠的时间不包括在内);
Message p = mMessages; // 头结点
if (p != null) {
// 如果头结点设置的延迟时间 > 大于当前时间
// 注意 这里比较的时候都是用的从开机到现在的毫秒数
// 这里意味着还没有到设置的执行时间
if (p.when > now) {
removeAllMessagesLocked(); // 直接移除全部还未到执行时间的 Message
} else {
Message n;
for (;;) {
n = p.next; // 拿到下一个节点
if (n == null) { // 结束标志:没有下一个
return;
}
if (n.when > now) { // 还没有到设置的执行时间,退出循环
break;
}
p = n;
}
// 这时候得到的节点 p 是没有到设置的执行时间的前一个节点
// n 是没有到设置的执行时间的节点
p.next = null;
do {
p = n;
n = p.next;
p.recycleUnchecked();
} while (n != null);
}
}
}

removeAllMessagesLocked()直接将MessageQueue 中的 Message 全部回收掉。无论是延迟消息(延迟消息是指通过sendMessageDelayed或通过postDelayed等方法发送的需要延迟执行的消息)还是非延迟消息(delayMillis == 0)。

removeAllFutureMessagesLocked() 方法呢,就是只会清空MessageQueue消息池中所有的延迟消息,并将消息池中所有的非延迟消息派发出去让Handler去处理,在这个方法中,表现为直接return ,然后因为队列有Message,所以相应的 dispatchMessage(msg) 会调用。

quitSafely相比于quit方法安全之处在于清空消息之前会派发所有的非延迟消息。

面试题

handler发消息给子线程,looper怎么启动?

发消息就是把消息塞进去消息队列,looper在应用起来的时候已经就启动了,一直在轮询取消息队列的消息。


为什么在子线程中创建Handler会抛异常?

首先看如下代码:

1
2
3
4
5
6
7
8
new Thread(){
Handler handler = null;

@Override
public void run() {
handler = new Handler();
}
}.start();

前面说过,Looper 对象是 ThreadLocal 的,即每个线程都有自己的 Looper,这个 Looper 可以为空。但是,当你在子线程中创建 Handler 对象时,如果 Looper 为空,那就会抛出异常。源码解释一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Handler {
...
public Handler(Callback callback, boolean async) {
...
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;
}
...
}

从上述程序中,我们可以看到,当 mLooper 对象为空的时候,抛出了异常。这是因为该线程中的 Looper 对象还没有创建,因此 sThreadLocal.get() 会返回 null。Handler 的原理就是要与 MassageQueue 建立关联,并且将消息投递给MassageQueue,如果连 MassageQueue 都没有,那么 Handler 就没有存在的必要,而 MassageQueue 又被封装在 Looper 中,因此,创建 Handler 时 Looper 一定不能为空。解决方法:

1
2
3
4
5
6
7
8
9
new Thread(){
Handler handler = null;
@Override
public void run() {
Looper.prepare(); // 1. 为当前线程创建 Looper,并会绑定到 ThreadLocal 中
handler = new Handler();
Looper.loop(); // 2. 启动消息循环
}
}.start();

在UI线程为什么可以直接使用呢,就是因为在 ActivityThread中默认帮你执行了 1,2步了的。


Handler为什么loop是死循环。

在android中如果主线程(UI线程)中进行耗时操作会引发ANR(Application Not Responding)异常,产生ANR的原因一般有两种:

  1. 当前的事件没有机会得到处理(即主线程正在处理前一个事件,没有及时的完成或者looper被某种原因阻塞住了)

  2. 当前的事件正在处理,但没有及时完成

比如onCreate()中进行了耗时操作,导致点击、触摸等不响应,就会产生ANR。为了避免ANR异常,android使用了Handler消息处理机制,让耗时操作在子线程运行,需要UI线程进行处理的操作给UI线程发送消息。

我们知道Handler发消息给UI线程就可以处理消息,UI线程维护着一个Looper和一个消息队列,Looper不停的拿消息队列的消息去分发处理。到这里问题来了:如果这么做的话,UI线程岂不是要一直死循环轮询消息队列拿消息?死循环不是造成ANR吗?

是的,确实是死循环,但是 ANR 还得另说。

ActivityThread 的 main()

1
2
3
4
5
6
7
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
...
Looper.loop();

throw new RuntimeException("Main thread loop unexpectedly exited");
}

Looper.loop()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;

// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();

for (;;) {
Message msg = queue.next(); // might block
...
msg.target.dispatchMessage(msg);
...
msg.recycleUnchecked();
}
}

Looper.loop()是在死循环处理消息,如果main方法中没有looper进行循环,那么主线程一运行完毕就会退出,那才不正常了呢!?

所以,ActivityThread的main方法主要就是做消息循环,一旦退出消息循环,那么你的应用也就退出了。

那么问题重新问一遍:那为什么这个死循环不会造成ANR异常呢?

其实原因是显然的,我们知道Android是由事件驱动的,Looper.loop() 不断地接收事件、处理事件,每一个点击触摸或者说Activity的生命周期都是运行在 Looper.loop() 的控制之下,如果它停止了,应用也就停止了。只能是某一个消息或者说对消息的处理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞消息。换言之,消息队列为空的时候会阻塞主线程,而处理消息的时候不可以阻塞,这时候的阻塞 5 s就会 ANR。

也就说我们的代码其实就是在这个循环里面去执行的,当然不会阻塞了。

而且主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此loop的循环并不会对CPU性能有过多的消耗。

总结:Looer.loop()方法可能会引起主线程的阻塞,但只要它的消息循环没有被阻塞,能一直处理事件就不会产生ANR异常。

Handler 机制 很多细节需要关注:如线程如何建立和退出消息循环等等)

这里就是让你回答 Handler 的工作原理。

关于Handler,在任何地方new Handler 都是什么线程下?

这个需要分类讨论:

  1. 像最开始那样呢,直接new 出来,不带Looper 参数,那么就在创建 Looper 的线程下。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public Handler() {
    this(null, false);
    }
    public Handler(Callback callback) {
    this(callback, false);
    }
    public Handler(boolean async) {
    this(null, async);
    }
    public Handler(Callback callback, boolean async) {
    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;
    }
  2. 在任何地方 new 出指定线程的 Handler。例如主线程,看案例:

1
2
3
4
5
6
7
new Thread(new Runnable() {
@Override
public void run() {
// 在子线程中实例化Handler同样是可以的,只要在构造函数的参数中传入主线程的Looper即可
Handler handler = new Handler(Looper.getMainLooper());
}
}).start();

前面讲过,UI线程调用的 prepare 函数不一样,多保存了UI线程的 Looper 到 Looper.sMainLooper 中的。其目的是在任何地方都可以实例化 UI 线程的 Handler。

而这时候调用的构造方法是

1
2
3
4
5
6
7
8
9
10
11
12
13
public Handler(Looper looper) {
this(looper, null, false);
}
public Handler(Looper looper, Callback callback) {
this(looper, callback, false);
}

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

其实就是与传入的 Looper绑定,换言之,如果我传入的是其他线程的Looper,我同样也可以实例化其他线程的 Handler。

请解释下在单线程模型中Message、Handler、Message Queue、Looper之间的关系

讲到Handler,肯定离不开Looper、MessageQueue、Message这三者和Handler之间的关系,下面简略地带过,详细自己可以参看上面源码

  1. Handler
    将要执行的Message或者Runnable到消息队列。

  2. Looper
    每一个线程只有一个Looper,每个线程在初始化Looper之后,然后Looper会维护好该线程的消息队列,用来存放Handler发送的Message,并处理消息队列出队的Message。它的特点是它跟它的线程是绑定的,处理消息也是在Looper所在的线程去处理,所以当我们在主线程创建Handler时,它就会跟主线程唯一的Looper绑定,从而我们使用Handler在子线程发消息时,最终也是在主线程处理,达到了异步的效果。

那么就会有人问,为什么我们使用Handler的时候从来都不需要创建Looper呢?这是因为在主线程中,ActivityThread默认会把Looper初始化好,prepare以后,当前线程就会变成一个Looper线程。

  1. MessageQueue
    MessageQueue是一个消息队列,用来存放Handler发送的消息。每个线程最多只有一个MessageQueue。MessageQueue通常都是由Looper来管理,而主线程创建时,会创建一个默认的Looper对象,而Looper对象的创建,将自动创建一个MessageQueue。其他非主线程,不会自动创建Looper。

  2. Message
    消息对象,就是MessageQueue里面存放的对象,一个MessageQueu可以包括多个Message。当我们需要发送一个Message时,我们一般不建议使用new Message()的形式来创建,更推荐使用Message.obtain()来获取Message实例,因为在Message类里面定义了一个消息池,当消息池里存在未使用的消息时,便返回,如果没有未使用的消息,则通过new的方式创建返回,所以使用Message.obtain()的方式来获取实例可以大大减少当有大量Message对象而产生的垃圾回收问题。

Handler机制,Handler除了线程通信还有什么作用

Handler的主要用途 :

  • 推送未来某个时间点将要执行的Message或者Runnable到消息队列。
  • 在子线程把需要在另一个线程执行的操作加入到消息队列中去。

1. 推送未来某个时间点将要执行的Message或者Runnable到消息队列

实例:通过Handler配合Message或者Runnable实现倒计时

  • 方法一,通过Handler + 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
    public class MainActivity extends AppCompatActivity {
    private ActivityMainBinding mBinding;

    private Handler mHandler ;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);

    //设置监听事件
    mBinding.clickBtn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    //通过Handler + Message的方式实现倒计时
    for (int i = 1; i <= 10; i++) {
    Message message = Message.obtain(mHandler);
    message.what = 10 - i;
    mHandler.sendMessageDelayed(message, 1000 * i); //通过延迟发送消息,每隔一秒发送一条消息
    }
    }
    });

    mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
    super.handleMessage(msg);
    mBinding.time.setText(msg.what + ""); //在handleMessage中处理消息队列中的消息
    }
    };
    }
    }

这里用到了DataBiding,可能没用过的同学看起来有点奇怪,但其实反而简略了代码,有一定基础的同学看起来都不会有太大压力。通过这个小程序,笔者希望大家可以了解到Handler的一个作用就是,在主线程中,可以通过Handler来处理一些有顺序的操作,让它们在固定的时间点被执行。

  • 方法二,通过Handler + Runnable的方式实现倒计时。代码如下:
    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 class MainActivity extends AppCompatActivity {
    private ActivityMainBinding mBinding;
    private Handler mHandler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);

    //设置监听事件
    mBinding.clickBtn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    for (int i = 1; i <= 10; i++) {
    final int fadedSecond = i;
    //每延迟一秒,发送一个Runnable对象
    mHandler.postDelayed(new Runnable() {
    @Override
    public void run() {
    mBinding.time.setText((10 - fadedSecond) + "");
    }
    }, 1000 * i);
    }
    }
    });
    }
    }

方法二也是通过代码让大家加深Handler处理有序事件的用途,之所以分开Runnable和Message两种方法来实现,是因为很多人都搞不清楚为什么Handler可以推送Runnable和Message两种对象。

其实,无论Handler将Runnable还是Message加入MessageQueue,最终都只是将Message加入到MessageQueue。Handler的post Runnable对象这个方法只是对post Message进行了一层封装,即将Runnable 放到的Message 中的 mCallback 存储起来,值得注意的是,如果直接将Message 加入MessageQueue的话,那么mCallback将为null,所以最终我们都是通过Handler推送了一个Message罢了,至于为什么会分开两种方法,只是为了更方便开发者根据不同需要进行调用。下面再来看看Handler的第二个主要用途。

2. 在子线程把需要在另一个线程执行的操作加入到消息队列中去

实例:通过Handler + Message来实现子线程加载图片,在UI线程显示图片

效果图如下

代码如下(布局代码也不放出来了)

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
public class ThreadActivity extends AppCompatActivity implements View.OnClickListener {
private ActivityThreadBinding mBinding = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_thread);
// 设置点击事件
mBinding.clickBtn.setOnClickListener(this);
mBinding.resetBtn.setOnClickListener(this);
}

@Override
public void onClick(View v) {
switch (v.getId()) {
// 响应load按钮
case R.id.clickBtn:
// 开启一个线程
new Thread(new Runnable() {
@Override
public void run() {
// 在Runnable中进行网络读取操作,返回bitmap
final Bitmap bitmap = loadPicFromInternet();
// 在子线程中实例化Handler同样是可以的,只要在构造函数的参数中传入主线程的Looper即可
Handler handler = new Handler(Looper.getMainLooper());
// 通过Handler的post Runnable到UI线程的MessageQueue中去即可
handler.post(new Runnable() {
@Override
public void run() {
// 在MessageQueue出队该Runnable时进行的操作
mBinding.photo.setImageBitmap(bitmap);
}
});
}
}).start();
break;
case R.id.resetBtn:
mBinding.photo.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.mipmap.default_pic));
break;
}
}

/***
* HttpUrlConnection加载图片,很简单
* @return
*/
public Bitmap loadPicFromInternet() {
Bitmap bitmap = null;
int respondCode = 0;
InputStream is = null;
try {
URL url = new URL("https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=1421494343,3838991329&fm=23&gp=0.jpg");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(10 * 1000);
connection.setReadTimeout(5 * 1000);
connection.connect();
respondCode = connection.getResponseCode();
if (respondCode == 200) {
is = connection.getInputStream();
bitmap = BitmapFactory.decodeStream(is);
}
} catch (MalformedURLException e) {
e.printStackTrace();
Toast.makeText(getApplicationContext(), "访问失败", Toast.LENGTH_SHORT).show();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return bitmap;
}
}

很简单,其实最主要的就是当我们需要在主线程执行一些操作的时候,就可以直接使用这种方式,这种方式有着直接发送 Message 不可实现的先天优势。

handler机制组成,handler机制每一部分的源码包括looper中的loop方法、threadlocal概念、dispatchmessage方法源码,runnable封装message等

上面讲解的还记得住么?

请解释下在单线程模型中Message、Handler、Message Queue、Looper之间的关系

  1. Android的单线程模型

当一个程序第一次启动时,Android会同时启动一个对应的主线程(Main Thread),主线程主要负责处理与UI相关的事件,如:用户的按键事件,用户接触屏幕的事件以及屏幕绘图事件,并把相关的事件分发到对应的组件进行处理。所以主线程通常又被叫做UI线程。

在开发Android 应用时必须遵守单线程模型的原则:Android UI操作并不是线程安全的并且这些操作必须在UI线程中执行。

如果在非UI线程中直接操作UI线程,会抛出异常,这与普通的java程序不同。因为 ViewRootImpl 对 UI 操作做了验证,这个验证工作是由 ViewRootImpl 的 checkThread 方法来完成的:

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线程负责事件的监听和绘图,因此,必须保证UI线程能够随时响应用户的需求,UI线程里的操作应该向中断事件那样短小,费时的操作(如网络连接)需要另开线程,否则,如果UI线程超过5s没有响应用户请求,会弹出对话框提醒用户终止应用程序。顺便说一下 ANR 默认情况下,在android中Activity的最长执行时间是5秒,BroadcastReceiver的最长执行时间则是10秒。

如果在新开的线程那为什么系统不对 UI 控件的访问加上锁机制呢?缺点有两个:首先加上锁机制会让 UI 访问的逻辑变得复杂;其次锁机制会降低 UI 访问的效率,因为锁机制会阻塞某些线程的执行。鉴于这两个缺点,最简单且高效的方法就是采用单线程模型来处理UI操作,对于开发者来说也不是很麻烦,只需要通过 Handler 切换一下 UI 访问的执行线程就好。
中需要对UI进行设定,就可能违反单线程模型,因此android采用一种复杂的Message Queue机制保证线程间通信。

  1. 后面就是分析的那一套了。Message Queue 、 Handler 、 Looper

Handler、Thread和HandlerThread的差别

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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;

public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}

/**
* Constructs a HandlerThread.
* @param name
* @param priority The priority to run the thread at. The value supplied must be from
* {@link android.os.Process} and not from java.lang.Thread.
*/
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}

/**
* Call back method that can be explicitly overridden if needed to execute some
* setup before Looper loops.
*/
protected void onLooperPrepared() {
}

@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}

/**
* This method returns the Looper associated with this thread. If this thread not been started
* or for any reason is isAlive() returns false, this method will return null. If this thread
* has been started, this method will block until the looper has been initialized.
* @return The looper.
*/
public Looper getLooper() {
if (!isAlive()) {
return null;
}

// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}

/**
* Quits the handler thread's looper.
* <p>
* Causes the handler thread's looper to terminate without processing any
* more messages in the message queue.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p><p class="note">
* Using this method may be unsafe because some messages may not be delivered
* before the looper terminates. Consider using {@link #quitSafely} instead to ensure
* that all pending work is completed in an orderly manner.
* </p>
*
* @return True if the looper looper has been asked to quit or false if the
* thread had not yet started running.
*
* @see #quitSafely
*/
public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}

/**
* Quits the handler thread's looper safely.
* <p>
* Causes the handler thread's looper to terminate as soon as all remaining messages
* in the message queue that are already due to be delivered have been handled.
* Pending delayed messages with due times in the future will not be delivered.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p><p>
* If the thread has not been started or has finished (that is if
* {@link #getLooper} returns null), then false is returned.
* Otherwise the looper is asked to quit and true is returned.
* </p>
*
* @return True if the looper looper has been asked to quit or false if the
* thread had not yet started running.
*/
public boolean quitSafely() {
Looper looper = getLooper();
if (looper != null) {
looper.quitSafely();
return true;
}
return false;
}


public int getThreadId() {
return mTid;
}
}

由于 HandlerThread 实在是代码少得变态,我就直接将全部源码贴上来了。

直接看一下run() 方法可能就明白了,就是将 Looper 启动起来,就等于主线程一样,可以直接使用 Handler 了,没必要 Looper.prepare() 再 Looper.loop() 了。需要知道这根本不是 handler ,而是封装了 Looper 的 Thread,方便了子线程与子线程通信。

还有一点
Handler: 它的消息处理方式是阻塞式的,必须一条一条的处理。耗时操作 不应该用handler处理。

HandlerThread:继承自Thread,它有个Looper,在这里可以执行耗时操作

什么是 IdleHandler?有什么用?怎么用

https://mp.weixin.qq.com/s/KpeBqIEYeOzt_frANoGuSg
这里还没写,努力更新中…

Handler消息机制,postDelayed会造成线程阻塞吗?对内存有什么影响?

  1. Handler消息机制
  2. postDelayed会造成线程阻塞吗:

还有印象么,上面讲解的 postDelayed。postDelayed只是在 post 的时候加了延时,最后这个延时讲被转换成执行时间存在每一个 Message 中。而在 loop() 中调用 next() 的死循环是阻塞式的,只有在下个消息到达或者有事务发生(设置的nextPollTimeoutMillis到了)时,才通过往pipe管道写端写入数据来唤醒线程工作。

也就是说如果当前消息队列中消息全部为 延时Message(全部没到执行时间),而这个 Message 的执行时间又比MessageQueue 中所有消息执行时间早,那么在loop循环取next 的时候就会因为最早这一个Message(刚刚postDelayed的Message)还没到执行时间而阻塞。

  1. 对内存有什么影响
    ThreadLocal内存解释
    Looper 是通过 ThreadLocal 存储在线程中的,而MessageQueue 是封装在 Looper 中的。

参考文章:

Android 官方文档 :Handler
MessageQueue
Looper)
《 Android 开发艺术探索 》
《 Android 开发进阶 从小工到专家 》
深入理解Android中的Handler机制
一步一步分析Android的Handler机制
【从源码看Android】03Android MessageQueue消息循环处理机制(epoll实现)