Android的消息机制分析

栏目: 后端 · 发布时间: 5年前

内容简介:众所周知,Android系统是以消息来驱动的系统,说到消息,我们会立马想到Handler、MessageQueue和Looper,事实上,android中的消息机制就和这三者紧密联系的,这三者是相互关联,谁都离不开谁。接下来我们就讲一下消息机制,以及需要重点注意的几点内容。在开始讲消息之前,我们有必要说说应用程序的启动,因为启动的时候干了很多和消息相关的事,有个大致的印象。如下:后面的就是view和window的操作的,这里省略不讲,看到了吧,一切皆消息

众所周知,Android系统是以消息来驱动的系统,说到消息,我们会立马想到Handler、MessageQueue和Looper,事实上,android中的消息机制就和这三者紧密联系的,这三者是相互关联,谁都离不开谁。接下来我们就讲一下消息机制,以及需要重点注意的几点内容。

二、简述应用程序的启动

在开始讲消息之前,我们有必要说说应用程序的启动,因为启动的时候干了很多和消息相关的事,有个大致的印象。如下:

  • 应用程序的入口是在ActivityThread的main()函数,初始化mainLooper,然后创建消息循环队列。
  • 然后去创建一个ActivityThread,在初始化的时候会去创建内部Handler H 和用于消息通信的ApplicationThread,也就是binder。
  • Binder收到远程AMS传递过来的消息,Handler会将消息enqueu到消息队列中去,UI线程从消息队列中取到消息,开始loop。

后面的就是view和window的操作的,这里省略不讲,看到了吧,一切皆消息

三、消息机制分析

好了,接下来就进入正题,我们先理解一个问题。为啥非UI线程不支持页面的刷新,为啥用handler就可以。

3.1、为啥非UI线程不能刷新界面

因为Android中的UI控件不是线程安全的,如果并发的去更新UI会造成不可预期的后果,那么为啥不像线程一样用加锁机制呢,两点:

  • 复杂度问题:对控件加锁,ui逻辑将变得很复杂,因为我们需要去管理这个锁。
  • 效率问题:锁机制将会降低ui的访问效率,因为锁都是会阻塞线程的执行。

鉴于以上的缺点,采用了单线程来处理UI操作,动态去切换handler就ok了。

3.2、消息机制的整体框架

下面是三者之间工作流程

Android的消息机制分析
  • 在thread1中,Handler发送消息到thread2的MQ,实质上就是enqueueMessage到MQ,这里有post和send方法,post的底层也是用send来实现的。
  • MQ一直处于监听状态,当有消息到来的时候,会调用handler中的looper,注意这个looper是运行在thread1里面。
  • Looper开始分发消息,由于Looper在Handler线程中,所以Handler的业务逻辑就切换到创建Handler所在的线程中去执行了。

小结一下:所以想象一下ui线程更新ui的场景,我们在ui线程中创建Handler,当在子线程中处理完耗时操作的时候需要更新ui,这时候就交给handler去loop,实质上就是交给创建handler的ui线程,这样我们更新ui就是在主线程了,这就好理解了。

3.3、MessageQueue的工作原理

消息队列,从字面上看是队列,不过它实质上就是单链表的数据结构,因为MQ会进行大量的删除和插入操作,单链表在这一块的效率很有优势。我们主要看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 {
                // 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;
    }
复制代码

这里的操作实质上就是单链表的插入操作。接下来看看next方法

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

                // 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;
        }
    }
复制代码

这里是读取消息的操作,它是一个无限循环。如果消息队列中有消息,next方法会返回这条消息,并且从单链表中删除这条消息,当没有消息的时候消息队列会一直处于等待状态。

小结:所谓的消息发送和读取,归根到底就是单链表的删除和插入。所以同志们,这下知道理解和掌握常用数据结构的重要性了吧,即便强如google程序员也需要写如此简单的算法滴。

3.4、Looper的工作原理

我们知道Handler的工作离不开Looper,没有Looper会报错,如下:

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;
复制代码

相信开发这都遇到过上面的异常吧,就是在只有Handler没有Looper。那么怎样在Handler中初始化looper呢,其实非常简单:

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));
    }
复制代码

执行一下prepare方法就可以了,这里面new了一个Looper,这就是为该Handler所在的线程生成一个Looper,接下来就是开始消息循环了。

/**
     * 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;

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

        // Allow overriding a threshold with a system prop. e.g.
        // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
        final int thresholdOverride =
                SystemProperties.getInt("log.looper."
                        + Process.myUid() + "."
                        + Thread.currentThread().getName()
                        + ".slow", 0);

        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);
                }
            }
            if (logSlowDelivery) {
                if (slowDeliveryDetected) {
                    if ((dispatchStart - msg.when) <= 10) {
                        Slog.w(TAG, "Drained");
                        slowDeliveryDetected = false;
                    }
                } else {
                    if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                            msg)) {
                        // Once we write a slow delivery log, suppress until the queue drains.
                        slowDeliveryDetected = true;
                    }
                }
            }
            if (logSlowDispatch) {
                showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
            }

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycleUnchecked();
        }
    }
复制代码

loop方法是Looper中最核心的方法了,也就是消息循环,这里同样是死循环,当有消息的时候就调用next方法,把消息交给Handler分发出去,我们来看看分发过程:

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);
                }
            }
复制代码

上面有个msg.target.dispatchMessage(msg);,msg.target就是Handler,哦哦,这就是核心了,这里就是把msg交给了handler,也就是说处理消息的事转交给handler了,也就是创建Handler的线程会去执行这些业务。

3.4.1 为啥消息loop的时候一直阻塞却不卡死

这个问题是个重点经典的问题啊,很多面试官也喜欢问这个的。这里做个总结

  • 1.Android应用程序的消息处理机制由消息循环、消息发送和消息处理三个部分组成的。

  • 2.Android应用程序的主线程在进入消息循环过程前,会在内部创建一个 Linux 管道(Pipe),这个管道的作用是使得Android应用程序主线程在消息队列为空时可以进入空闲等待状态,并且使得当应用程序的消息队列有消息需要处理时唤醒应用程序的主线程。

  • 3.Android应用程序的主线程进入空闲等待状态的方式实际上就是在管道的读端等待管道中有新的内容可读,具体来说就是是通过Linux系统的Epoll机制中的epoll_wait函数进行的。

  • 4.当往Android应用程序的消息队列中加入新的消息时,会同时往管道中的写端写入内容,通过这种方式就可以唤醒正在等待消息到来的应用程序主线程。

  • 5.当应用程序主线程在进入空闲等待前,会认为当前线程处理空闲状态,于是就会调用那些已经注册了的IdleHandler接口,使得应用程序有机会在空闲的时候处理一些事情。

上面是总结的比较好的,在这里我在更加简明一点吧:

  • 消息循环采用pipe的epoll机制,epoll一直处于读端,也即是等着消息。
  • Android系统中所有的操作都会去触发epoll的写操作,有写就会去唤醒,所以就不会卡死了,有的人说,如果系统中没有操作呢,这就没有意义了,既然你对手机不做任何操作,那么卡死不卡死对你的来说也是不可感知了,也就不存在卡死的说法了。

3.5 Handler的工作原理

Handler的作用就是发送和处理消息,主要涉及post,sendMessage和handleMessage函数

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }
复制代码

可以很清晰的看到,消息的发送就是放MQ中添加了一条消息而已,MQ收到消息后开始loo,然后调用dispatchMsg方法去分发 、

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

到这里就是开始handleMessage了,然后我们会在自定类中去重写这个方法去处理具体的业务。

ok所有的内容我们都过了一遍,了解这些也就基本了解消息的处理机制了,对我们平时开发和面试总结还是很有帮助的,如果觉得对您有启发,请手动点赞一下。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Java5.0Tiger程序高手秘笈

Java5.0Tiger程序高手秘笈

BrettMclaughlin / 东南大学出版社 / 2005-10 / 28.00元

代号为 “Tiger”的下一个 Java 版本,不只是个小改动版。在语言核心中有超过 100 项以上的变动,同时有大量的对 library 与 API 所做的加强,让开发者取得许多新的功能、工具与技术。但在如此多的变化下,应该从何处开始着手?也许可以从既长又无趣的语言规范说明书开始看起;或等待最少 500 页的概念与理论巨著出版;甚至还可以直接把玩新的 JDK 看看能够有什么发现;或者借由《Jav......一起来看看 《Java5.0Tiger程序高手秘笈》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具