Android系统源码分析--消息循环机制

作者: 翰墨飘香 | 来源:发表于2017-07-23 12:01 被阅读115次

    上一章我们讲解SystemServer时涉及到了消息机制,因此这一章我们先介绍一下消息循环机制,帮助大家弄清楚消息循环的原理,有助于代码的编写和优化。

    Looper-Message-MessageQueue-Handler消息处理机制

    在Android系统有两个通信机制,一个是Binder,一个是消息机制,前者是跨进程通信,后者是进程内部通信。消息通信主要包括几个部分:

    • 消息发送者和处理者:Handler
    • 消息循环器:Looper
    • 消息队列:MessageQueue
    • 消息:Message

    我们先看一个时序图:

    005.jpg

    图中,1-11步是Looper的准备过程,12-17步是获取消息,处理消息,回收消息的循环过程。

    下面是一张消息循环过程图,图片来自网络博客(blog.mindorks.com),Looper会通过loop方法不断从消息队列去取消息,然后交给handler处理,处理完成就回收消息,要注意的是只有一个looper,但是可能有多个handler:

    002.jpg

    1、Looper

    Looper是一个循环器,通过里面的loop方法不断去取消息,发送给Handler进行处理。根据上面时序图以及SystemServer启动代码我们开始分析Looper的调用过程:

    private void run() {
            try {
                ...
                
                // 准备主线程的Looper
                Looper.prepareMainLooper();
    
                ...
            } finally {
                ...
            }
    
            ...
    
            // Loop(循环) forever.
            Looper.loop();
            throw new RuntimeException("Main thread loop unexpectedly exited");
        }
    

    我们先看Looper.prepareMainLooper方法:

        /**
         * Initialize the current thread as a looper, marking it as an
         * application's main looper. The main looper for your application
         * is created by the Android environment, so you should never need
         * to call this function yourself.  See also: {@link #prepare()}
         */
        public static void prepareMainLooper() {
            prepare(false);
            synchronized (Looper.class) {
                if (sMainLooper != null) {
                    throw new IllegalStateException("The main Looper has already been prepared.");
                }
                sMainLooper = myLooper();
            }
        }
    

    上面有段注释,我翻译一下,就是:初始化当前线程作为一个looper,并把它标记为应用的主looper。这个looper是被Android环境(系统)创建的,因此你不需要自己调用这个方法。也就是系统创建了这个looper,你不需要再创建了。我们接着看里面的内容,首先调用了prepare方法,需要注意的是Looper.prepare()在每个线程只允许执行一次,该方法会创建Looper对象:

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    ...
        private static void prepare(boolean quitAllowed) {
            if (sThreadLocal.get() != null) {// 确保ThreadLocal中只有一个Looper
                throw new RuntimeException("Only one Looper may be created per thread");
            }
            sThreadLocal.set(new Looper(quitAllowed));
        }
    

    上面的ThreadLocal是声明在类里的,并且是静态的,因此,随着类创建了该对象,get方法是获取Looper的,如果能获取到,则抛出异常,也就是确保当前线程只有一个Looper。如果是空,那么我们创建一个Looper放到里面去。

    我们先看一下ThreadLocal:线程本地存储区(Thread Local Storage,简称为TLS),每个线程都有自己的私有的本地存储区域,不同线程之间彼此不能访问对方的TLS区域。(来自Android消息机制1-Handler(Java层))我们看一下它的set和get方法:

    ThreadLocal的set方法:

        public void set(T value) {
            //获取当前线程
            Thread t = Thread.currentThread();
            // 获取当前线程里的ThreadLocalMap
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }
    

    如果map不为空,则以键值对放入进行存储,此处map不是HashMap,而是其他,这里不详细解释。如果map为空,则通过下面代码创建map:

        void createMap(Thread t, T firstValue) {
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }
    

    ThreadLocal的get方法:

        public T get() {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
            }
            return setInitialValue();
        }
    

    我们看到获取的时候也是根据当前线程去获取的。因此每个线程会保存一个Looper。

    我们接着看Looper的构造函数有哪些操作,也就是创建Looper做了哪些处理:

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

    首先是创建了MessageQueue对象,接着创建一个线程,也就是当前线程,currentThread是一个native方法,我们不再分析,我们看一下MessageQueue创建做了哪些事情:

        MessageQueue(boolean quitAllowed) {
            // 是否可以退出消息队列
            mQuitAllowed = quitAllowed;
            // 返回底层的MessageQueue对象的内存地址,如果为空返回0
            mPtr = nativeInit();
        }
    

    上面的nativeInit是调用的jni,我贴一下代码,不再解释:

    001.png

    我们回到prepareMainLooper方法接着看,如果sMainLooper不为null,则抛出异常,提示sMainLooper已经创建了,如果是null,那么调用myLooper方法回去sMainLooper:

        public static @Nullable Looper myLooper() {
            return sThreadLocal.get();
        }
    
    

    其实这个get方法就是我们上面new完Looper放进去的,到此prepareMainLooper就完成了,相关信息也准备好了。接下来就是调用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;
    
            ...
    
            for (;;) {// 无限循环
                Message msg = queue.next(); // might block
                if (msg == null) { // message为空为结束信号,退出循环
                    // No message indicates that the message queue is quitting.
                    return;
                }
    
                ...
                
                try {
                    // 将真正的处理工作交给message的target,即handler
                    msg.target.dispatchMessage(msg);
                } finally {
                    ...
                }
    
                ...
    
                // 回收Message
                msg.recycleUnchecked();
            }
        }
    

    首先是通过myLooper方法获取Looper,如果为空,则抛出异常,提示还没有调用Looper.prepare方法,如果不为空,则通过looper获取MessageQueue对象,然后进入for循环,因为for语句中没有条件,因此该for循环为无限循环,在这个循环中有三件事,一个是获取消息队列中的下一个消息,然后处理该消息,最近处理完消息,回收消息。这三个过程就是Looper的主要作用:取消息,处理消息,回收消息。

    2.Message

    Message是整个循环中信息的载体,它是一个链表结构,关于链表结构可以参考下面文章:
    Android自助餐--Handler消息机制完全解析--系列
    链表数据结构图解 和 代码实现
    基本数据结构:链表(list)
    链表结构之单链表

    我们看个图:

    006.png

    上面就是一个示例图,每个Message中都有一个后面Message的引用next,链表最后一个next为空,sPool是第一个Message。但是每个Message的内存地址不是挨着的,这样可以占用零碎的内存。

    我们先来看Message包含的参数:

        public int what;
        public int arg1; 
        public int arg2;
        public Object obj;
        /*package*/ int flags;
        /*package*/ long when;
        /*package*/ Handler target;
        // 消息队列中下一个消息的引用
        /*package*/ Message next;
        // sPool这个变量可以理解为消息队列的头部的指针,也就是当前消息对象
        private static Message sPool;
        // sPoolSize是当前的消息队列的长度
        private static int sPoolSize = 0;
        private static final int MAX_POOL_SIZE = 50;    
    

    前四个参数很熟悉,不再解释,flags是一个标签,表示是否正在使用;when是处理消息的时间;target就是我们上面提到的Handler;next是下一个Message的引用;sPool是一个静态变量,说明只有一个,其实这个是消息队列的头消息;sPoolSize是消息队列中消息个数;MAX_POOL_SIZE是消息队列最大消息数量。

    Message中有多个用来获取Message对象的obtain复写方法。因为后面的obtain方法都是通过第一个obtain方法获取Message对象的,因此我们只看第一个参数为空的方法:

        /**
         * Return a new Message instance from the global pool. Allows us to
         * avoid allocating new objects in many cases.
         */
        public static Message obtain() {
            // 避免多线程进行争抢资源,给sPoolSync进行加锁
            synchronized (sPoolSync) {
                // 如果消息队列的头部不为空,则可以取出头部重用
                if (sPool != null) {
                    Message m = sPool;
                    // 头部消息取出后,将sPool指向后面的消息对象
                    sPool = m.next;
                    // next(队列尾部)设置为null
                    m.next = null;
                    m.flags = 0; // clear in-use flag
                    // 消息队列长度减一
                    sPoolSize--;
                    return m;
                }
            }
            // 如果消息队列的头部为空,则创建新的Message对象
            return new Message();
        }
    

    系统提示尽量用这种方法获取Message对象,避免创建大量新的对象,其实也可以通过Handler来获取Message,这个我们在将Handler时候再讲。

    在上面Looper中我们讲到最后消息处理完后需要回收,这个回收方法recycleUnchecked也在Message类中:

     /**
         * 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;
    
            // 避免多线程进行争抢资源,给sPoolSync进行加锁
            synchronized (sPoolSync) {
                if (sPoolSize < MAX_POOL_SIZE) {
                    // 回收当前消息后时,将sPool消息后移
                    next = sPool;
                    // 将当前消息放到头部
                    sPool = this;
                    // 队列长度加一
                    sPoolSize++;
                }
            }
        }
    

    消息回收时,将对应消息的标签设置为使用中,其他标签设置为空或者默认值,如果消息队列没有超过最大值,那么将sPool赋值给next,将这个Message赋值给sPool,消息队列长度加一。也就是将处理完的消息清空,重新放回消息队列等待使用。

    3.Handler

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

    Handler中的looper是获取当前线程中的looper,looper不能为空,MessageQueue也是looper中的。

    首先是发送消息,发送消息的方法很多,我们看一张图:

    004.jpg

    我们看到Handler中有多个发送消息的方法,但是最终调用了enqueueMessage方法:

        private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
            msg.target = this;
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            return queue.enqueueMessage(msg, uptimeMillis);
        }
    

    从代码我们可以看到msg.target就是Handler,也就是在这里进行赋值的,然后是调用MQ(MessageQueue)的enqueueMessage方法,这个方法是添加消息队列的,具体内容我们后面再讲。因此,发送消息就是讲消息添加到消息队列。我们前面还讲过获取Message对象可以通过Message中的obtain方法,也可以通过Handler中的方法,我们先看一张图:

    003.jpg

    Handler是通过多个复写方法obtainMessage来获取Message的,只是传入参数不同,我们看一个没有参数的方法代码:

        public final Message obtainMessage(){
            return Message.obtain(this);
        }
    

    我们看到其实还是调用了Message.obtain方法,并且传入了this,也就是Handler,通过Message.obtain方法将Handler赋值给Message中的target。从这,我们基本对Handler与Message的关系基本明确了,获取Message的方法我们也完全知道了,因此我们在以后用的时候不需要再去new一个Message对象,而是通过obtain方法去获取,如果有就不需要new了,如果没有系统会自己去创建。

    4.MessageQueue

    MessageQueue是消息队列,其实是管理消息链表的。它主要功能是取出消息--next方法,将消息加入队列--enqueueMessage方法。

    我们先看加入消息队列方法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;
        }
    

    插入消息队列有两种情况,一种是消息队列处于空闲状态,直接将消息放在消息队列前面,可能需要唤醒主线程,另一种是消息队列处于忙碌状态,就不需要唤醒,而是根据消息处理时间将消息插入到消息队列的对应位置中。

    第一种状态:插入队列头

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

    if语句的三个条件是:一、队列为空,二、插入消息需要立即处理,三、插入消息处理时间比消息队列头消息早,这三个条件说明消息队列处于闲置状态,此时要把消息放置到消息队列头部,即将插入消息的next指向消息队列的头p,然后将消息队列要处理的消息指向插入消息对象,最后判断是否需要唤醒,如果队列阻塞则需要唤醒,否则不需要。

    第二种状态:插入队列中间或者后面,这种情况比较复杂

    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;
    

    因为不是在队列头,所以需要for循环去查找应该的位置,首先将第一个消息用prev进行缓存,然后当前消息引用指向下一个消息对象,依次类推,直到p == null(到队列最后),或者当前消息触发时间小于后面这个消息的触发时间,停止循环,说明找到了位置,此时执行最后两行代码,也就是将当前出入消息的next指向p,也就是,如果p==null,则说明插入到最后一个,如果不为空,则插入到p前面,然后将前一个prev的next指向插入的消息,此时插入成功。最后的if语句中如果需要唤醒消息队列,则调用底层方法nativeWake唤醒消息队列开始循环。到此,消息插入就讲完了。我们上面说到loop方法是通过MessageQueue的next方法取出消息,那么下面我们看一下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;
            }
        }
    

    mPtr是MessageQueue初始化的时候通过调用底层方法获取的底层NativeMessageQueue的对象,如果底层不能初始化则返回0,如果可以初始化返回对象地址,此处判断,如果没有初始化也就没有底层的NativeMessageQueue对象,因此返回null。紧接着开始for循环,开始遍历消息队列,查找需要处理的消息,在这里,如果消息队列为空,或者没有需要立即处理的消息都要使线程开始等待。接着调用nativePollOnce方法来查看当前队列中有没有消息,传入参数nextPollTimeoutMillis表示要等待的时间,如果nextPollTimeoutMillis为0则说明不需要等待。接着获取当前时间now,初始化prevMsg来缓存消息,初始化msg来缓存当前消息mMessages,下面if语句判断消息不为空但target为空,则说明该消息为“同步分隔栏”(关于“同步分隔栏”请参看聊一聊Android的消息机制一文),如果该消息为同步分隔栏,则后面的同步消息都不会被查找,只能查找异步消息来处理,也就是do-while语句中的代码,如果没有同步分割栏或者找到了后面的异步消息(可能没有),则接着判断。

    如果消息不为空,则还有消息,开始判断时间,如果当前时间小于下一个消息的执行时间,说明还需要等待,那么计算需要等待的时间nextPollTimeoutMillis,如果当前时间不小于当前消息执行时间时,并且前一个消息prevMsg不为空,说明出现了“同步分隔栏”,也就是执行了do-while代码,do-while执行完,说明找到了异步消息或者遍历完整个队列没有异步消息,如果有异步消息,此时prevMsg.next = msg.next,也就是跳过同步消息,将异步消息msg.next赋值给prevMsg.next,然后将取出的msg的next赋值为null,因为要处理了,所以不再指向后面队列的消息对象,然后将msg设置为正在使用,并且返回,如果prevMsg为空,则说明没有出现“同步分隔栏”,此时将当前消息mMessages的下一个消息赋值给mMessages,然后将msg.next设置为空,就是不再引用,然后设置为正在使用,返回该消息。

    如果消息为空,则nextPollTimeoutMillis = -1,说明没有消息了,则接着向下执行,如果退出消息队列,则说明所有消息都执行完了,最终调用nativeDestroy方法,如果不退出消息队列,则要进入等待状态。如果第一次进入,并且当前消息为空或者消息不为空,但是处于等待状态,那么要获取IdleHandler个数,如果小于等于0,则说明没有IdleHandler运行,调用continue执行下一次循环,如果IdleHandler个数大于0,但是等待的Handler(mPendingIdleHandlers)为空,则要创建IdleHandler数组,将mIdleHandlers放入数据,然后for循环调用每个IdleHandler的queueIdle方法,如果这个方法返回false,则从数组移除这个对象,否则保留改对象,下次空闲继续执行,最后将pendingIdleHandlerCount置为0,nextPollTimeoutMillis置为0,继续下一次循环。

    那么到此,整个循环就讲完了,因为不懂C++代码,所以底层没法分析,只能分析framework层代码,说了很多还是需要自己对比代码多理解。

    5.Handler的使用方法

    我们在使用Handler的时候软件会提示我们有问题,那么到底该怎么写Handler呢,我从Stack Overflow找到了答案,在这就分享一下:

    首先,定义一个静态MxHandler继承Handler,里面使用弱引用:

    public abstract class MxHandler<T> extends Handler {
    
        private WeakReference<T> weak;
    
        public MxHandler(T t) {
            this.weak = new WeakReference<T>(t);
        }
    
        @Override
        public void handleMessage(Message msg) {
            if (null == weak || null == weak.get()) {
                return;
            }
            handleMessage(msg, weak);
            super.handleMessage(msg);
        }
    
        protected abstract void handleMessage(Message msg, WeakReference<T> weak);
    }
    

    然后我们再写具体的MyHandler继承这个MxHandler:

    private static final class MyHandler extends MxHandler<HandlerDemo> {
    
            public MyHandler(HandlerDemo handlerDemo) {
                super(handlerDemo);
            }
    
            @Override
            protected void handleMessage(Message msg, WeakReference<HandlerDemo> weak) {
                switch (msg.what) {
                    case 0:
                        HandlerDemo h = weak.get();
                        h.doSomething();
                        break;
                    default:
                        break;
                }
            }
        }
    

    这样我们在Activity中使用是不会出现内存泄漏之类的错误。

    参考:

    android的消息处理机制(图+源码分析)——Looper,Handler,Message
    Android中Thread、Handler、Looper、MessageQueue的原理分析
    Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系
    Android应用程序消息处理机制(Looper、Handler)分析

    原文地址:Android系统源码分析--消息循环机制

    Android开发群:192508518

    微信公众账号:Code-MX


    注:本文原创,转载请注明出处,多谢。

    相关文章

      网友评论

        本文标题:Android系统源码分析--消息循环机制

        本文链接:https://www.haomeiwen.com/subject/grtmkxtx.html