Android 消息机制 Handler 原理解析
几乎所有的发送消息的方法最后都调用了 sendMessageAtTime 方法,在这个发放中判断了是否存在MessageQueue对象之后调用了 enqueueMessage 方法,在这个方法中,把 msg.targt 设置为此Handler对象,注意,这里是用于之后在MessageQueue里面消费消息时找到对应的处理此消息的Handler,然后调用MessageQueue的 enqueueMessage() 方法,此方法主要是用于把消息放入MessageQueue里面,即入队操作。
1 boolean enqueueMessage (Message msg, long when) {
2 if (msg.target == null ) {
3 throw new IllegalArgumentException( “Message must have a target.” );
4 }
5 if (msg.isInUse()) {
6 throw new IllegalStateException(msg + ” This message is already in use.” );
7 }
8
9 synchronized ( this ) {
10 if (mQuitting) {
11 IllegalStateException e = new IllegalStateException(
12 msg.target + ” sending message to a Handler on a dead thread” );
13 Log.w(TAG, e.getMessage(), e);
14 msg.recycle();
15 return false ;
16 }
17
18 msg.markInUse();
19 msg.when = when;
20 Message p = mMessages;
21 boolean needWake;
22 if (p == null || when == 0 || when < p.when) {
23 // New head, wake up the event queue if blocked.
24 msg.next = p;
25 mMessages = msg;
26 needWake = mBlocked;
27 } else {
28
29
30 // and the message is the earliest asynchronous message in the queue.
31 needWake = mBlocked && p.target == null && msg.isAsynchronous();
32 Message prev;
33 for (;;) {
34 prev = p;
35 p = p.next;
36 if (p == null || when < p.when) {
37 break ;
38 }
39 if (needWake && p.isAsynchronous()) {
40 needWake = false ;
41 }
42 }
43 msg.next = p; // invariant: p == prev.next
44 prev.next = msg;
45 }
46
47 // We can assume mPtr != 0 because mQuitting is false.
48 if (needWake) {
49 nativeWake(mPtr);
50 }
51 }
52 return true ;
53
}
Message消息通过MessageQueue的 enqueueMessage 方法进行入队操作,在此方法中先判断 msg.target 是否存在,当前消息是否在使用中,是否收到退出信息等的检测,全部正常才进行入队,从上面代码我们可以看到,MessageQueue是通过类似于链表的形式存储Message消息的,且是通过消费时间进行排序的,通过前面我们可以知道,入队用于排序的时间是Message的消费时间,即 System.currentTime()+delayMillis ;循环遍历消息队列,找到 msg.when 应该插入的地方,插入链表,完成消息的入队操作。 这里还需要注意的是 nativeWake(mPtr) ;方法,这个方法是用通过JNI实现的,即在底层通过C实现的,然后通过JNI调用,在底层中维持了一个 mWakeEventFd 文件,这个文件是专门用于进程和线程之间通信的,并且通过 epoll 监控。 这里在入队完成之后会调用此方法,通过此方法会向 MwakeEventFD 文件写入一个 uint64_t ,这个是用于唤醒消息循环线程的,在后面消息取出的时候进行详细说明。
通过上面的方法就完成了消息发送过程,接下来则是消息循环过程,当消息的消费时间到了之后取出相应的消息进行消费。通过上面的时候我们知道在线程中使用完Handler之后需要调用Looper.loop()完成整个过程,在主线程不需要我们操作是因为ActivityThread里面已经调用过了,在 loop() 里面做的就是消息的阻塞等待。
1 public static void loop () {
2 final Looper me = myLooper();
3 if (me == null ) {
4 throw new RuntimeException( “No Looper; Looper.prepare() wasn’t called on this thread.” );
5 }
6 final MessageQueue queue = me.mQueue;
7
8
9 // and keep track of what that identity token actually is.
10 Binder.clearCallingIdentity();
11 final long ident = Binder.clearCallingIdentity();
12
13 for (;;) {
14 Message msg = queue.next(); // might block
15 if (msg == null ) {
16 // No message indicates that the message queue is quitting.
17 return ;
18 }
19
20
21 final Printer logging = me.mLogging;
22 if (logging != null ) {
23 logging.println( “>>>>> Dispatching to “ + msg.target + ” “ +
24 msg.callback + “: “ + msg.what);
25 }
26
27 final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
28
29 final long traceTag = me.mTraceTag;
30 if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
31 Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
32 }
33 final long start = (slowDispatchThresholdMs == 0 ) ? 0 : SystemClock.uptimeMillis();
34 final long end;
35 try {
36 msg.target.dispatchMessage(msg);
37 end = (slowDispatchThresholdMs == 0 ) ? 0 : SystemClock.uptimeMillis();
38 } finally {
39 if (traceTag != 0 ) {
40 Trace.traceEnd(traceTag);
41 }
42 }
43 if (slowDispatchThresholdMs > 0 ) {
44 final long time = end – start;
45 if (time > slowDispatchThresholdMs) {
46 Slog.w(TAG, “Dispatch took “ + time + “ms on “
47 + Thread.currentThread().getName() + “, h=” +
48 msg.target + ” cb=” + msg.callback + ” msg=” + msg.what);
49 }
50 }
51
52 if (logging != null ) {
53 logging.println( “<<<<< Finished to " + msg.target + ” “ + msg.callback);
54 }
55
56 // Make sure that during the course of dispatching the
57 // identity of the thread wasn’t corrupted.
58 final long newIdent = Binder.clearCallingIdentity();
59 if (ident != newIdent) {
60 Log.wtf(TAG, “Thread identity changed from 0x”
61 + Long.toHexString(ident) + ” to 0x”
62 + Long.toHexString(newIdent) + ” while dispatching to “
63 + msg.target.getClass().getName() + ” “
64 + msg.callback + ” what=” + msg.what);
65 }
66
67 msg.recycleUnchecked();
68 }
69
}
• 从代码中我们可以看出,这是个无限循环方法等待获取可以处理的消息,在循环里面,通过 do-while 找到一个不为空且是asynchronous的消息,找到之后检测这个消息是否到了执行时间,如果不到的话,通过 Math.min(msg.when – now, Integer.MAX_VALUE) ;设置等待时间,如果这个消息找到了,且到了执行时间了,那么就取出此消息,并把链表中此消息删除,把这个消息通过msg.markInUse();标志为使用中,把这个找到的消息返回给Looper的loop方法,进行消息分发。 如果到了链表尾部还是没有符合的消息,则进入阻塞等待过程。
• 另外在此方法中还得注意 nativePollOnce(ptr, nextPollTimeoutMillis) ;这个方法也是JNI调用C++实现的。 之前说到在通过Handler发送消息的时候,会向mWakeEventFd文件写一个 uint_64 ,而 nativePollOnce 方法则阻塞监听mWakeEventFd文件以及唤醒消息循环线程的。 当有消息发送的时候就会写入一个 uint_64 ,而 nativePollOnce 里面则是一直监听这个文件,当有写入操作发生时,就会唤醒 epoll_ wait 即消息循环线程,线程就会去取出队列里面的消息去执行操作,当队列里面没有消息的时候,又会继续等待,当下次有信息写入的时候则再次唤醒线程去取出队列消息。 这样就完成一次消息循环。
• 继续看上面的取出消息之后的处理,上面说到取出消息之后loop()方法是调用了msg.target.dispatchMessage方法进行消息处理,而msg.target是之前设置的目标Handler,现在去Handler里面看看dispatchMessage的处理:
1 public void dispatchMessage (Message msg) {
2 if (msg.callback != null ) {
3 handleCallback(msg);
4 } else {
5 if (mCallback != null ) {
6 if (mCallback.handleMessage(msg)) {
7 return ;
8 }
9 }
10 handleMessage(msg);
11 }
12
}
这里就是如何处理信息了,如果为Message设置了callback的话则直接 message.callback.run() ; 处理消息,如果有设置 Handler的callback ,也进行分发; 如果都没有的话,那就直接调用 handleMessage(msg) ;还记得我们最开是的时候写的简单使用Handler的例子么,里面重写了一个方法,就是handleMessage(msg);所以到这里就清楚了,前面发出的消息就是在这里进行处理的。 另外还有很多经常用的方法都是通过包装的Handler来进行的,比如:
1. Activity.runOnUiThread(Runnable action)
1 public final void runOnUiThread (Runnable action) {
2 if (Thread.currentThread() != mUiThread) {
3 mHandler.post(action);
4 } else {
5 action.run();
6 }
7
}
这个是Activity的方法,用于在切换到主线程运行,可以看到内部实现是判断当前线程是否是主线程,如果是的话直接运行,不是的话用 mHandler.post 发送出去,而mHandler是申明在主线程的Handler,经过发送之后再主线程的Handler里面完成调用。
2. View.post()
1 public boolean post (Runnable action) {
2 final AttachInfo attachInfo = mAttachInfo;
3 if (attachInfo != null ) {
4 return attachInfo.mHandler.post(action);
5 }
6
7
8 // Assume that the runnable will be successfully placed after attach.
9 getRunQueue().post(action);
10 return true ;
11
}
View.pos t() 用于异步更新view,也是找到主线程Handler,然后通过Handler发送消息,在主线程更新Handler。另外还有很多经典方法都是用Handler更新数据的,最典型的是Android官方提供的异步工具AsyncTask,用它可以进行异步请求或者异步下载数据,下载图片等等,AsyncTask就是用Handler实现的多线程异步工具,开启子线程进行数据请求,然后通过Handler发送数据消息,最好在主线程里面处理收到的消息,完成异步请求。