Handler通信机制源码解读

工作中,我们可能直接使用我们需要的工具方法,但是不曾了解其中的原理内涵,这样并不能很好的让我们理解其运行机制,在复杂项目和疑难问题时无从入手。作为开发想要提高并设计架构,一是要先学习优秀的设计理念,再就是了解其内部原理,为自己在复杂使用场景和发生疑难问题时能够透过表象看到本质。
Handler,Message,looper 和 MessageQueue 构成了安卓的消息机制,handler创建后可以通过 sendMessage 将消息加入消息队列,然后 looper不断的将消息从 MessageQueue 中取出来,回调到 Hander 的 handleMessage方法,从而实现线程的通信。
从两种情况来说,第一在UI线程创建Handler,此时我们不需要手动开启looper,因为在应用启动时,在ActivityThread的main方法中就创建了一个当前主线程的looper,并开启了消息队列,消息队列是一个无限循环,为什么无限循环不会ANR?因为可以说,应用的整个生命周期就是运行在这个消息循环中的,安卓是由事件驱动的,Looper.loop不断的接收处理事件,每一个点击触摸或者Activity每一个生命周期都是在Looper.loop的控制之下的,looper.loop一旦结束,应用程序的生命周期也就结束了。我们可以想想什么情况下会发生ANR,第一,事件没有得到处理,第二,事件正在处理,但是没有及时完成,而对事件进行处理的就是looper,所以只能说事件的处理如果阻塞会导致ANR,而不能说looper的无限循环会ANR
另一种情况就是在子线程创建Handler,此时由于这个线程中没有默认开启的消息队列,所以我们需要手动调用looper.prepare(),并通过looper.loop开启消息
主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此loop的循环并不会对CPU性能有过多的消耗。

Handler源码部分解读:

初级程序员使用handler时有时会出现异常。Can’t create handler inside thread that has not called Looper.prepare()
为什么呢。
在core/java/android/os/Handler.java 中

 @hide
     */
    public Handler(@Nullable Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

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

在其初始化的结构体中,获取mLooper = Looper.myLooper();通过检测mLooper是否为空,为空则报上述异常。如果不为空, 再通过mQueue = mLooper.mQueue;获取消息队列,可以看到messageQuene对象是在Looper中获取的。
再往下看,Handler中提供获取主线程Handler的方法。

  /** @hide */
    @UnsupportedAppUsage
    @NonNull
    public static Handler getMain() {
        if (MAIN_THREAD_HANDLER == null) {
            MAIN_THREAD_HANDLER = new Handler(Looper.getMainLooper());
        }
        return MAIN_THREAD_HANDLER;
    }

之后,我们我们发现了我们常用的obtainMessage方法。为何建议使用obtainMessage而不是new Message()呢,从obtainMessage的注释中也可以看出来,其会从一个全局message pool中返回一个新的message,比新建一个实例更有效率。另外的比如obtainMessage(int what)和obtainMessage(int what, @Nullable Object obj)则同步设置了message.what和obj消息。

/**
     * Returns a new {@link android.os.Message Message} from the global message pool. More efficient than
     * creating and allocating new instances. The retrieved message has its handler set to this instance (Message.target == this).
     *  If you don't want that facility, just call Message.obtain() instead.
     */
    @NonNull
    public final Message obtainMessage()
    {
        return Message.obtain(this);
    }

然后是各种发送消息的方法,包括发送message消息的和post(runnable)的方法。可以看到无论是send还是post还是带时间参数的,最终都走到了queue.enqueueMessage。这个messagequeue在下边的小节会解读。
Handler中消息发送方法调用

Looper源码解读:

在Handler源码中我们看到其Looper是用Looper.myLooper获取的,这里looper相当于消息的传送带,不断的循环检测是否有消息发过来。
在Looper类文件的开头我们就看到了谷歌给我们的注释和一个例子,清楚的说明了普通现场默认是没有looper的,需要手动创建,然后创建的方法就是Looper.prepare(),然后下面的例子也清楚的解释了上节中报错信息如何防止,Can’t create handler inside thread that has not called Looper.prepare()。然后启动looper循环用Looper.loop(),以启动消息循环,直到loop被停止。

/**
  * Class used to run a message loop for a thread.  Threads by default do
  * not have a message loop associated with them; to create one, call
  * {@link #prepare} in the thread that is to run the loop, and then
  * {@link #loop} to have it process messages until the loop is stopped.
  *
  * <p>Most interaction with a message loop is through the
  * {@link Handler} class.
  *
  * <p>This is a typical example of the implementation of a Looper thread,
  * using the separation of {@link #prepare} and {@link #loop} to create an
  * initial Handler to communicate with the Looper.
  *
  * <pre>
  *  class LooperThread extends Thread {
  *      public Handler mHandler;
  *
  *      public void run() {
  *          Looper.prepare();
  *
  *          mHandler = new Handler() {
  *              public void handleMessage(Message msg) {
  *                  // process incoming messages here
  *              }
  *          };
  *
  *          Looper.loop();
  *      }
  *  }</pre>
  */

然后prepare的方法定义,可以看到sThreadLocal.get() != null的判断,这里可以看出,一个线程只能创建一个looper,但是如何保证的呢,代码如何实现一个线程一个looper的呢。这里就需要看代码中的sThreadLocal的get和set方法了。

/** Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
    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,当然会走到set那,然后看变量初始化
ThreadLocal sThreadLocal = new ThreadLocal()
通过网络查询我们知道,ThreadLocal类提供了线程局部 (thread-local) 变量,多线程数据不能共享。
而其set方法源码为

public void set(T value) {
//获取当前线程
        Thread t = Thread.currentThread();
//在ThreadLocal 中,以t来获取当前线程的一个ThreadLocalMap
        ThreadLocalMap map = getMap(t);
//如果ThreadLocal中有这个map,就把当前类和对应的值传进去
        if (map != null)
            map.set(this, value);
//如果没有这个map,就新建一个t线程的ThreadLocalMap,并将value值传进去
        else
            createMap(t, value);
    }

可见,是用hashmap类似的结构存储了当前线程id,和Looper的对应关系,保证一个线程一个looper, 关于ThreadLocal还有很多知识点,这里我们只简单带过,之后有时间新开一章详细解读。
然后根据上述threadlocal的set方法,这里获取这个map中此线程对应的唯一looper,这里就是Handler中获取looper的实现,保证了唯一性。

 /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

之后看Looper.loop的代码,这个是消息传送带的动力来源,如果不调用这个方法,消息是没法获取到的。

/**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    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;
        .......
         for (; ; ) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            ........
            long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
            try {
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } catch (Exception exception) {
            .........

从代码中可以看到,一个for(; ; ),代表开始了一个无线循环,然后从queue.next中获取消息,如果为空则返回。另注意queue.next后注释了 might block, 这里是不是类似socket方法中的read,读不到消息就阻塞在这,这个在下一节中寻找答案。之后我们发现msg.target.dispatchMessage(msg);通过msg对象中的target,分发了消息出去。
最后是looper的quit方法和quitSafely方法,二者区别是,前者立即停止当前消息队列中消息的处理,后者是待现存消息队列中的消息处理完后再推出消息循环。
然后怀着激动的心情,我们来看messageQueue的源码。

MessageQueue源码解读

首先把类的注释贴下,谷歌的注释最能完整体现这个类的意义了。

/**
 * Low-level class holding the list of messages to be dispatched by a
 * {@link Looper}.  Messages are not added directly to a MessageQueue,
 * but rather through {@link Handler} objects associated with the Looper.
 *
 * <p>You can retrieve the MessageQueue for the current thread with
 * {@link Looper#myQueue() Looper.myQueue()}.
 */
public final class MessageQueue {

看源码的开始部分,没那么好看,很多和native code交互的方法和变量,

@UnsupportedAppUsage
Message mMessages;
@UnsupportedAppUsage
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
private SparseArray<FileDescriptorRecord> mFileDescriptorRecords;
private IdleHandler[] mPendingIdleHandlers;
private boolean mQuitting;

// Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout.
private boolean mBlocked;
.....
private native static long nativeInit();
private native static void nativeDestroy(long ptr);
@UnsupportedAppUsage
private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
private native static void nativeWake(long ptr);
private native static boolean nativeIsPolling(long ptr);
private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);

这里我们应该可以不用去太详细了解其内涵,我们需要寻找上述小节中调用到的messagequeue的方法。

 @UnsupportedAppUsage
    Message next() {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        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;
                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.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        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;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }
...........
        }
    }

首先,在其 synchronized (this) 代码块中,保证messagequeue的队列不被多线程更改。然后获取下一条消息时,先获取当前时间final long now = SystemClock.uptimeMillis();我们可以想到前边handler中各种sendattime或者postdelay等消息也带着时间参数。
这里看到检测如果msg不为空,并且msg.target也不为空,这里又出现了target变量,什么东西,我们继续看。

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

一个do-while句式不断的寻找下一个消息msg.next,这里可以看出这是队列或链表的获取下一个的方法。注释显示,寻找下一个异步方法。如果msg.isAsynchronous()为真则退出循环。
之后就是now 和msg.when的比较,如果时间未到,则设一个超时时间。如果msg可以被处理,然后
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
这里又可以看出来,如果prevMsg不为空,就把下一个的指针指向当前处理msg的下一个消息,否则,这个类的mMessage直接设置为msg.next.可以看出这里的意思是在队列中删除这个msg,相当于出队列。然后这个出队的msg就不需要next节点信息了,置空,然后msg.markInUse,字面意思是标记在使用,至此,next()方法返回了消息。
之后是enqueueMessage,在Handler中我们看到,最终消息发送是走到这个方法的,所以这个方法也比较重要。

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

一开始又检测了msg.target为空,会报异常,又一次出现target,一会儿一定要找下是干啥的。
第二句就是 if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
有些同学应该出现过这个异常,即在handleMessage时,收到的消息再次发送,会报这个错误,就是因为这个标记。
然后在类的同步块中,检测是否当前线程looper在quit状态中,是的话则报异常并回收msg。
之后msg.when = when, 发送消息时传过来的时间参数放到了msg里。
然后如果当前mMessages变量为空或者延时时间为0,或者新插入的msg消息的延时时间小于当前消息的时间,则把新来的消息插入到当前消息的前面作为队列头部。

  if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;

否则,不断的循环在队列中查找延时时间大于新插入消息延时时间的节点。然后插入到队列,这里可以看出,此队列是按时间顺序排列的队列,时间最短的在队列最底部,时间长的在尾部,然后先进先出,即延时最少的消息先处理。
prev即

				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;

新消息插入队列如图所示,原P指针在最下方,现由于判断条件新加入消息的when为时间2,由prev保存原p节点,p节点= p.next, 然后找到msg2消息的延时时间小于Msg3,则插入到当前位置。

Message类源码

这里看出Message类继承了Parcelable方法,是android推荐的序列化方法,用在跨进程传输数据上,常见的又aidl中的序列化。而这里message需要不同的线程间切换,打到从一个线程传到另一个线程处理的目的。

 *
 * Defines a message containing a description and arbitrary data object that can be
 * sent to a {@link Handler}.  This object contains two extra int fields and an
 * extra object field that allow you to not do allocations in many cases.
 *
 * <p class="note">While the constructor of Message is public, the best way to get
 * one of these is to call {@link #obtain Message.obtain()} or one of the
 * {@link Handler#obtainMessage Handler.obtainMessage()} methods, which will pull
 * them from a pool of recycled objects.</p>
 */
public final class Message implements Parcelable

然后我们发现了令我们疑惑的
Handler target;
然后我们寻找Handler中的代码,发现所有的obtainMessage中已经将Handler.this参数传到Message, 如果不是obtainMessage,在最终发送消息时,即如下的enqueueMessage,也已经将msg.target 赋值this,即当前Handler。

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();

if (mAsynchronous) {
    msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);

}
其实Handler的整个源码还有待新的解读,但是目前我们能了解这些已经算是足够用了,下次有疑问的时候可以自己翻看源码,这样在我们以后的工作中,如果遇到什么异常或者其他相关问题,能够从原理上说出这种问题的原因,对我们的工作和个人水平会有很大的提高。

发布了17 篇原创文章 · 获赞 2 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/caizehui/article/details/103817523