Android消息处理机制

作者: unual_ | 来源:发表于2019-08-05 18:20 被阅读15次

上次文章谈到ThreadLocal的原理。今天接着谈谈Android的消息处理机制。主要使用了四个类,Looper,Message,MessageQueue,Handler。先简单介绍一个这个四个类的作用,再从源码角度去分析。

  • Looper
    从名字可以猜测,他是不断循环。思考一个问题,在我们new Thread创建一个线程并start之后,在执行完中间的Runnable体之后,线程便结束他的工作,等待被回收。为什么Android主线程能一直运行,并且等待着处理UI事件和其他消息。我们猜测由于主线程被某种机制阻塞,使得主线程不能退出,一直在执行状态。而Looper就提供了这种作用。
  • Message
    被传递的消息体
  • MessageQueue
    存储消息的队列
  • Handler
    处理消息的处理器

Looper

image.png

有几个主要的变量,sThreadLocal,sMainLooper,mQueue,mThread


image.png
image.png
  1. prepare()方法往sThreadLocal这个类静态变量里面放置了一个Looper对象,在set之前先判断是否有值,如果有则抛出异常。从ThreadLocal的学习我们知道,ThreadLocal的set()和get()方法会区分线程,也就是说线程和Looper对象会一一对应。

  2. Looper的构造函数需要一个quitAllowed参数,在构造函数里,将该参数传给了MessageQueue,并实例化该对象赋值给mQueue变量。mThread变量表示的是,实例化Looper时,即调用Looper.prepare()方法时所在的线程。

  3. 同样prepareMainLooper()则是做了更多一步操作,设置
    sMainLooper,myLooper()是从sThreadLocal里面取值。为什么调用myLooper()得到sMainLooper呢。因为prepareMainLooper()方法是系统调用的,系统会在应用启动时调用设置sMainLooper这个静态变量。

image.png

Looper还有两个重要的方法,loop()以及quit()。

  1. loop()方法会取到当前线程对应的Looper对象,然后用for()循环不断从Looper对象的mQueue里面取出Message对象,dispatchMessage这些对象。在msg为空时,跳出循环。结束loop方法。
  2. quit()方法则是调用mQueue的quit方法。

Message

image.png

几个常用的public变量:what,arg1,arg2,obj


image.png
  1. replyTo 和 sendingUid 是Messager使用时需要用到,暂时不需要关注。

  2. data的作用和obj类似,区别在于obj在跨进程通信时,只能存放系统的Parcelable对象,其他类型的数据可以通过data传输。

  3. target是一个Handler对象,在整个消息机制中需要使用到,后续再看。
    Message对象存储的信息可以是存储在arg1,arg2,obj或是data中,也可以是一个存储在callback的Runnable对象。

  4. next,指向Message对象的引用,MessageQueue中会使用到。

  5. flags 消息的int型标志位,最后一位是否使用,倒数第二位是否为同步消息


    image.png

    Message提供了一系列签名不同的obtain方法,其他需要传参的方法都是在调用obtain()之后,对返回Message对象设置成员变量。obtain()方法会判断sPool变量是否为空,如果为空就返回一个新的Message对象。如果不为空则返回sPool指向的Message对象,将该对象使用标志flags置为未使用,将next置为null(无法通过获取到的message对象访问下一个可使用的message),sPoolSize--,并且将sPool指向message.next。

image.png

recycle()方法用来回收被使用过的Message对象,在回收前会先检查当前对象是否在使用,然后清除message所有字段,将使用标志置为被使用中,放入消息池中。

总结来说,obtain()是从message链表取出可被使用的message对象,这个链表是通过Message.next字段实现的,通过recycle()回收对象,obtain()再次使用,避免了新对象的生成。通过Message的sPool静态字段,维护这个链表。

MessageQueue

MessageQueue是一个按时间排序的消息队列。严格来说他并不是消息队列,插入消息会按照时间排序插入而不是从队尾插入,取出消息在设置同步障碍时,也不是从队头取出。


image.png

MessageQueue构造器使用了quitAllowed参数,该参数只在quit()方法里面用到。从抛出的异常信息我们知道,主线程对应的MessageQueue对象不允许调用quit方法。只有主线程对应的MessageQueue传递了false,其他线程传递的应该都是true。MessageQueue对象的创建依赖于native的实现。在创建时候获取了一个mPtr指针,在quit的时候,用该指针去释放相关的资源。

Looper里面循环调用queue.next()获取Message对象,分发Message。下面是MessageQueue的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;
        }
    }

    ...

    public int postSyncBarrier() {
        return postSyncBarrier(SystemClock.uptimeMillis());
    }

    private int postSyncBarrier(long when) {
        // Enqueue a new sync barrier token.
        // We don't need to wake the queue because the purpose of a barrier is to stall it.
        synchronized (this) {
            final int token = mNextBarrierToken++;
            final Message msg = Message.obtain();
            msg.markInUse();
            msg.when = when;
            msg.arg1 = token;

            Message prev = null;
            Message p = mMessages;
            if (when != 0) {
                while (p != null && p.when <= when) {
                    prev = p;
                    p = p.next;
                }
            }
            if (prev != null) { // invariant: p == prev.next
                msg.next = p;
                prev.next = msg;
            } else {
                msg.next = p;
                mMessages = msg;
            }
            return token;
        }
    }

1.nativePollOnce的作用是阻塞nextPollTimeoutMillis毫秒的时间。

2.在msg.target为null时,返回第一个达到when的异步消息。msg.target一般不为null,只有通过postSyncBarrier插入同步障碍Message时,target才会为null,此时不处理同步消息,只处理异步消息。

3.在msg.target不为null时,取出第一个达到when的消息。

4.在mMessages里取不到新消息时,nextPollTimeoutMillis为-1,nativePollOnce会一直阻塞。此方法造成了looper里queue.next()的阻塞。直到有新消息到来,调用nativeWake(mPtr)。

5.在调用Looper.quit()时,mQuitting会导致next里面循环退出。

    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;
    }
  1. 插入消息时,会验证target和isInUse,以及mQuitting。

  2. 将消息标志flag置为InUse。在Message.recycle()和MessageQueue.enqueueMessage(Message msg, long when)时,都会判断该字段。

  3. 在链表为空,或when=0,或when小于链表里message第一个when,将新message插入头部。唤起由nativePollOnce造成的阻塞。

  4. 在链表不为空。(当前被阻塞,上一条是同步障碍消息,当前是异步消息)->唤起阻塞。根据when插入链表,越大的时间,插入到越后面。整条链表是按时间排序的

image.png

hasMessages(...)方法根据里面的参数,判断链表里面是否有对应消息。这个消息包括两种,正常的消息以及runnable消息。
removeMessages移除对应的某一种消息
removeCallbacksAndMessages则是移除复合条件的所有消息
removeAllMessagesLocked移除所有消息
removeAllFutureMessagesLocked移除当前时间的之后的所有消息。

Handler

handler是message.target所对应的对象。在Looper.loop()中,在通过MessageQueue.next()取到msg之后,调用msg.target.dispatchMessage(msg)。我们看看Handler的相关方法


image.png
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
    ...
    private static void handleCallback(Message message) {
        message.callback.run();
    }
    ...
    /**
     * Subclasses must implement this to receive messages.
     */
    public void handleMessage(Message msg) {
    }

1.Handler的构造器参数Callback会赋值给mCallback。根据当前线程获取mLooper,以及mLooper里面的MessageQueue。如果没有Looper则会报错,这也就是常见的在子线程new Handler()会报错,因为子线程默认没有Looper。

2.在dispatchMessage时,先判断当前message是不是runnable消息。如果是,则调用message.callback.run()执行;如果不是,先尝试使用mCallback.handleMessage()处理消息,如果mCallback为空,或者mCallback.handleMessage()返回false,调用Handler.handleMessage(Message msg)方法。该方法是空方法,handler子类自行实现。

3.Handler的obtainMessage系列方法直接调用Message的obtain的对应方法。post系列方法,是将runnable对象包装成Message对象,调用MessageQueue.enqueueMessage()方法将消息放入待处理队列中;
send系列方法是将Message对象调用MessageQueue.enqueueMessage()方法放入待处理队列中。hasMessages和removeMessages方法是直接调用MessageQueue对应的方法。

4.Handler里面有个mAsynchronous成员变量,这个变量只有在调用Handler.createAsync静态方法创建新Handler时候,赋值为true,其他时候都为false。该值为true时,在enqueueMessage的时候,会将Message对象设置成异步消息,也就是说,异步的Handler发送的消息,都是异步消息。

Android主线程启动

 #ActivityThread类(挑选出部分代码)
public final class ActivityThread extends ClientTransactionHandler {
    final Looper mLooper = Looper.myLooper();
    final H mH = new H();

    final Handler getHandler() {
        return mH;
    }

    class H extends Handler {
        public static final int BIND_APPLICATION        = 110;
        ...

        String codeToString(int code) {
            if (DEBUG_MESSAGES) {
                switch (code) {
                    case BIND_APPLICATION: return "BIND_APPLICATION";
                    ...
                }
            }
            return Integer.toString(code);
        }
        public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case BIND_APPLICATION:
             Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
                    AppBindData data = (AppBindData)msg.obj;
                    handleBindApplication(data);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                case BIND_SERVICE:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceBind");
                    handleBindService((BindServiceData)msg.obj);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                ...
            }
            Object obj = msg.obj;
            if (obj instanceof SomeArgs) {
                ((SomeArgs) obj).recycle();
            }
            if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
        }
    }
 
    public static void main(String[] args) {
        ...
        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        ...
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
}

1.ActivityThread.main()方法是android应用主线程的入口,该方法在ZygoteInit里被调用。

2.先调用Looper.prepareMainLooper(),创建一个和主线程对应的Looper。

3.创建ActivityThread实例,启动相关的类。ActivityThread.mH 是一个继承自Handler子类对象,从handleMessage方法,看到了“serviceCreate”,“serviceBind”等方法。这个类是用来处理来自系统的一些消息事件。

4.Looper.loop()循环从MessageQueue里取出消息,处理消息,阻塞主线程。

总结

1.Looper通过ThreadLocal机制,保证该对象的实例和线程一一对应。在一个新开启的线程里,通过Looper.prepare(),创建实例。在实例化时,MessageQueue也被实例化出来,作为mQueue成员变量保存在Looper对象里。Looper,MessageQueue,Thread三者一一对应。

2.Looper.loop()开启,Looper所在线程将一直从MessageQueue里面取出Message,调用Message.target对应的Handler对象去处理消息。

3.Handler的创建默认取当前线程的Looper,也可以传入一个已有的Looper。Handler对象可以往MessageQueue里插入消息,等到了处理时间,该Handler对象便会去处理消息。Handler对象可以在任意线程post或sendMessage,最后会在Looper所在的线程处理消息。

参考:
【源码分析】关于MessageQueue,这一篇就够了!

相关文章

网友评论

    本文标题:Android消息处理机制

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