×

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

96
翰墨飘香
2017.07.23 12:01* 字数 4042

上一章我们讲解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技术分析
Web note ad 1