Android 的消息机制(UI线程的Looper 为啥不会阻塞?答案在后面)

说道 Android 的消息机制,其实说的就是 Handler 的运行机制以及 Handler 所附带的 MessageQueue 和 Looper 的工程过程。

一、 Handler 的运行机制

当 Handler 创建的时候,会采用当前线程的 Looper 来构建消息循环系统,如果当前线程没有 Looper 则会报错,当然,开始是 UI 线程,所以不用担心。
当然,当Looper 被创建的时候, MessageQueue 也被创建好了,这样 Looper 和 MessageQueue 就可以跟 Handler 一起工作了。

这里,先做一下 Handler 的工作流程:
当我们使用 Handler 的send 或者 post 的时候,它会调用 MessageQueue 的 qnqueueMessage 方法,将这个 消息放到消息队列中,然后 Looper 发现有新消息到来时,就会处理这个消息了;然后 Handler 的 handleMessage 方法就会被调用。

注意 Looper 是运行在创建 Handler 所在的线程中的,这样一来,Handler 中的业务逻辑就会被切换到创建 Handler 所在的县城中去执行了

过程可以用下图表示(图片来源):
在这里插入图片描述
接下来继续深入它。

1.2、消息队列的工作原理

消息队列在 Android 中指的是 MessageQueue,它其实不是队列,里面实现的单链表结构,它主要包含两个操作:插入和读取。
插入对应 enqueueMessage ,读取则对应 next,其中 enqueueMessage 表示往队列中插入一条消息,而 next 则从队列中取出消息,并将消息从队列中删除。

其中,需要注意的是它的 next 方法:

    Message next() {
   		 ...
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
		...
            }
		...

可以发现 next 方法是一个无线循环的方法,如果消息队列没有消息,那么next 方法会一直阻塞在这里。当有新消息到来时,next 方法会返回这条消息并将它从单链表中删除。

1.3 Looper 的工作原理

Looper 在 Android 的消息机制中扮演者消息队列的角色,具体来说,你会不停地从 MessageQueue 中查看是否有新的消息,如果有则取出消息,如果没有,则一直阻塞在那里。

在 Looper 的构造方法中,会创建一个 MessageQueue ,然后将当前线程保存起来。

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

然后,Handler 的创建是需要 Looper 的,没有Looper 是会报错的,那 Looper 如何创建呢?
很简单,在线程中,通过 Looper.prepare() 为线程开启一个 Looper,接着使用 Looper.loop() 来开启消息循环模式,如下所示:

new Thread(){
    @Override
    public void run() {
        Looper.prepare();
        super.run();
        Looper.loop();
    }
};

当然,这个是普通线程的,如果是 UI 线程,还提供了 prepareMainLooper 方法,这个方法主要是给 ActivityThread 创建Looper 使用,由于主线程比较特殊,也可以使用 Looper.getMainLooper() 来获取主线程Looper。

比如常用在共用类中:

 public static Handler HANDLER = new Handler(Looper.getMainLooper());

1.3.1 Looper 的退出

Looper 也是可以退出的,有 quit() 和 quitSafely() 两个方法;quit() 会直接退出,二 quitSalely() 则是已有消息都处理完毕,才会退出。

如果是自己定义的 Looper ,则建议要执行退出操作,否则这个子线程就会一直处于等待状态。容易造成内存泄漏,当然主线程就不用我们操心了。

Looper 的重要方法是 loop 方法,只有调用了 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;

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

        boolean slowDeliveryDetected = false;

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

            // This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            final long traceTag = me.mTraceTag;
            long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
            long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
            if (thresholdOverride > 0) {
                slowDispatchThresholdMs = thresholdOverride;
                slowDeliveryThresholdMs = thresholdOverride;
            }
            final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
            final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

            final boolean needStartTime = logSlowDelivery || logSlowDispatch;
            final boolean needEndTime = logSlowDispatch;

            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }

            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            try {
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
         ... 

          ...

可以看到,只有 queue.next() 为null 时,才会退出这个循环;当执行了 quit 或者 quitSafely 来通知 Looper 退出,它就会调用 MessageQueue 来退出,这样 queue.next() 就会返回null,looper 也就退出了。
否则就会一直阻塞在这里,一直等到 next 有新的消息,则 msg.target.dispatchMessage(msg); 就会被执行,这样 Handler 的dispatchMessage 就会被执行了。可以看到 Handler 的 dispatchMessage 方法如下:

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

首先,先检查 msg.callback 是否为null,Message 的callback 是一个 RUnnable 对象,实际上就是 Hnadler 的post 方法所传递的 Runnable 。所以,如果我们使用 了 Handler.post ,则不会走 handleMessage(msg) ;
如果不是,则检查 mCallback 是否为null,mCalback 其实就是个接口:

    public interface Callback {
        /**
         * @param msg A {@link android.os.Message Message} object
         * @return True if no further handling is desired
         */
        public boolean handleMessage(Message msg);
    }

如果不为空,最终就会调用到我们熟悉的 handleMessage(msg) 方法了。

这样,Handler 的消息机制就分析完了。

这里,面试官就会问了,既然 looper 这里是个无线循环,为啥不会阻塞UI线程?

为了回答这个问题,首先,我们先去到主线程的消息队列

二、主线程的消息队列

Android 的主线程就是 ActivityThread,主线程的入口为 main 方法:

public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

      .... 

        Looper.prepareMainLooper();

        // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
        // It will be in the format "seq=114"
        long startSeq = 0;
        if (args != null) {
            for (int i = args.length - 1; i >= 0; --i) {
                if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                    startSeq = Long.parseLong(
                            args[i].substring(PROC_START_SEQ_IDENT.length()));
                }
            }
        }
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

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

可以看到,main() 方法会通过 Looper.prepareMainLooper() 来创建 Looper 和 MessageQueue,然后创建了 ActivityThread线程,最后通过 Looper.loop() 开启循环。

而它的 Handler 就是 ActivityThread.H ,里面对应着 Activity 的启动等消息:
在这里插入图片描述

那这还是不能说明UI线程为啥不会阻塞啊?!!

别急,除了这个 ActivityThread 这个线程,其实还有个 ApplicationThread 线程,它里面创建了 Binder 方法,当 ApplicationThread 与 AMS 进行进程间通信完成后,就会通过 ApplicationThread 会向 H 发送消息,H 收到消息之后,就会将 ApplicationThread 中的逻辑切换到 ActivityThread 中取执行,即切换到主线程去执行了。
比如 Activity 的启动最后就通过 ApplicationThread 发送 LUANCHER_ACTIVITY 给 H 去启动的。

所以,UI线程并不会因为looper而阻塞,但如果我们主线程中做了一些耗时操作,导致导致 MessageQueue 的消息过多,等待执行,造成 ANR。

自此,我们的 Android 消息机制就分析完了。

参考 Android 艺术开发 第10章

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章