Android进阶1:Android的消息机制

Android消息机制主要是指Handler的运行机制。在开始之前先问下,考虑以下几个问题:

  1. 为什么需要handler?
    Android不建议在UI主线程做耗时操作,因为这样的话,可能会造成ANR, 那么,如果需要做耗时操作,需要开启线程做耗时操作,但是如果我们想更新Ui 呢,此时Handler就诞生了,系统之所以提供handler,主要是为了解决子线程中无法访问UI线程的矛盾。那么问题而就来了。
  2. 系统为什么不允许子线程更新UI呢?
    因为Android的UI控件不是线程安全的,官方给的解释是,如果同时操作并发操作Ui线程那么将造成不可预期的状态。UI控件都是单线程模式的,那么问题又来了:
  3. 既然线程不安全,那么给UI控件添加锁机制可以吗?
    首先添加锁机制会让ui访问的逻辑变得更复杂,其次锁机制会降低ui的访问效率,因为锁机制会阻塞某些线程。最简单高效的方法就是采用单线程模型来处理ui操作。同时也可以想一下,Android系统刚推出的时候,不论是内存还是本地磁盘都不成熟,Google可能初衷是UI显示,以用户感知为基础,不必要的资源占用都会成为不使用的因素。

Message

1 : 什么是Message?
通俗讲就是,两个线程之间通信的传递的介质就是Message。

2:Message的成员变量:

   public int what; //用于标记传递执行什么操作,通常用于switch判断。
    public int arg1; //如果传递的数据是整数类型就可以使用arg1或者arg2,不用再创建bundle
    public int arg2;
    public Object obj; //在进程间通信时,传递数据,通常使用setData传递数据,需要实现Android方式的序列化
    public Messenger replyTo; //在进程间通信时,可以通过replyTo给服务端传递一个客户端的Messenger的对象,此时服务端就可以使用Messenger发送消息给客户端
    public int sendingUid = -1;
    /*package*/ int flags; //标记位,标记Message是否正在被使用。
    /*package*/ long when; //消息的什么是有被执行。
    /*package*/ Bundle data; //传递数据,封装成Bundle对象
    /*package*/ Handler target; //新建此消息的handler对象
    /*package*/ Runnable callback; // handler.postMessage的runnable对象
    /*package*/ Message next;

    private static final Object sPoolSync = new Object(); 
    private static Message sPool; //回收消息池
    private static int sPoolSize = 0; //回收消息池此时的长度

    private static final int MAX_POOL_SIZE = 50; //回收消息池的最大长度

    private static boolean gCheckRecycle = true; //消息是否能够被回收。

从上可以看出,如果不考虑进程间通信,我们关心的就是 when, target,flags 以及消息回收池。flags很重要,这是个标记位,用于标记Message此刻的状态,如果消息处于0状态,表明Message可以被使用,但是消息处于FLAG_IN_USE状态,也就是说消息此刻正在被使用或者二次入队列,此此时将抛出异常:“This message is already in use.”
其实sPool本质是一个单向链表,内部存储着回收的消息,那么问题来了为什么采用链表存储回收的消息呢?这个问题看了消息的创建源码会找到答案。

3:Message的创建
对于很多开发者都是通过new Message()新建消息的。但是官方建议我们通过以下两种方法创建消息:

1 : Message.obtain();
2 : Handler.obtainMessage(); 

再看下obtain()和obtainMessage的源码:

   public static Message obtain() {
        synchronized (sPoolSync) { //加锁
            if (sPool != null) { //消息回收池不是null
                Message m = sPool; //获取链表头节点
                sPool = m.next; //获取第一个Message
                m.next = null; 
                m.flags = 0; // clear in-use flag
                sPoolSize--; //消息回收池长度-1
                return m; //返回此消息
            }
        }
        return new Message(); //如果是空链表,就新建Message。
    }

从上述代码可以看出,obtain先判断回收池是否是null,如果是null,就new Message,如果不是null,就从回收池获取一个旧的Message,然后将消息的flags置为0,返回消息。整体思想就是重复利用创建过的Message,这也就是为什么官方建议我们这样做,肯定是为了节约资源啊。

接下来我们再看下Handler的obtainMessgae()源码:

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

从上述代码可以看出,this指的就是Handler的引用,也就是Message的target变量。最终调用的还是Message内部的obtain(Handler target)。源码如下:

    public static Message obtain(Handler h) {
        Message m = obtain();
        m.target = h;
        return m;
    }

上述两种方法的唯一区别就是,是否是初始化了Message中的target变量。

4:Message的回收
既然有创建对象,那么同理也就有回收对象,Message的回收源码如下:

 public void recycle() {
        if (isInUse()) { //是否正在被使用
            if (gCheckRecycle) { //是否能够回收消息
                //抛出异常,消息正在被消费,不能够被回收。
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            }
            return;
        }
        //消息已经被消费过了,可以被回收了
        recycleUnchecked(); 
    }
    void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        //从上述的英文文档可以看出,刚方法就是将Message回复原始状态,也就是说以前是怎么样的,现在就是怎么样的,
        //但是唯一不同时flags对象置为FLAG_IN_USE对象,不能重复入队。
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;

        //将消息添加到回收池中。
        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

总结:Message就是介质,相当于我们从一个Activity到另一个Activity要传递的数据,只不过这类型是固定的:Message。

注意点:

  1. flags的值
  2. 回收池
  3. target对象

MessageQueue

1:什么是MessageQueue?
MessageQueue中文翻译过来就是:消息队列。通常理解是:MessageQueue是存储消息的,是一个队列。但是MessageQueue是怎样工作的呢?MessageQueue真的是一个队列吗?

2:MessageQueue工作原理
我们先看下MessageQueue的创建,MessageQueue的构造方法如下:

    MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed; //队列是否能够退出
        mPtr = nativeInit(); //底层使用的Code
    }

那么MessageQueue在哪里实例化呢?是在Looper的构造方法里:

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

可以看到,这个是在Looper实例化的,通过上述代码可以了解到,创建一个Looper就会创建一个MessgaeQueue。
接下来看下MessageQueue的成员变量:


    // True if the message queue can be quit.
    private final boolean mQuitAllowed; //是否能够退出

    @SuppressWarnings("unused")
    private long mPtr; // used by native code

    Message mMessages; //链表的头节点(思考:队列里怎么会有链表呢?)
    private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
    private SparseArray<FileDescriptorRecord> mFileDescriptorRecords;
    private IdleHandler[] mPendingIdleHandlers;
    private boolean mQuitting;

    // Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout.
    private boolean mBlocked; //是否阻塞?

    // The next barrier token.
    // Barriers are indicated by messages with a null target whose arg1 field carries the token.
    private int mNextBarrierToken;

通过上述代码,可以思考一个问题:成员变量里怎么会有链表Message?

3:Message入队
既然是队列,就肯定有消息入队嘛,代码如下:

    //思考:我怎么知道这个是消息入队呢?
    boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) { //判断消息是否持有发送消息的handler对象
            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) { //链表是null
                // 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. 消息是否持有发送该消息的handler
  2. 消息是否正在被使用
  3. 消息队列是否退出了,如果退出了,那么回收消息,等等,回收消息?回收?Message的回收方法在这里调用一次。

判断完了之后,就是消息入队了,但是,这代码好像是链表的插入操作啊?别好像,本来就是,所以我们可以基本断定MessageQueue实际上操作的是消息链表,上一个问题:成员变量里怎么会有链表Message?是不是有答案了呢?

那么又有个问题了,这个方法什么时候调用呢?

4:Message出队
既然消息入队了,那么得有出队啊,源码如下:

    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 (;;) { //死循环
            //如果有消息过段时间再处理,就执行如下方法。Binder管道连接,等待到手通知消费消息
            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;
                //遍历消息链表,从链表中取出一个msg。
                if (msg != null && msg.target == null) { //判断消息是否为null并且是否拥有发送消息的handler
                    // 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) { //消息不为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(); //调用的底层C代码,处理还未处理的消息,准备退出队列了,此方法是安全的退出方式。默认是安全退出方式
                    return null; //返回null,  Looper退出的判断条件
                }

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

从上述代码可以看出出队的基本步骤:

  1. 消息队列是否已经退出
  2. 如果没有退出,进入死循环,遍历链表,从链表中取出一个消息,如果消息是null,就表明没有消息可以处理了。
  3. 如果Msg不是null, 判断消息是否是可以立即执行,如果不可以,那么设置可时间点,在将来的某个时间点执行。
  4. 如果消息可以立即执行,那么就是链表的删除元素操作了。

通过入队和出队,可以总结出,其实MessageQueue本质就是:内部维护了一个消息链表,操作都是链表的插入和删除操作。

思考:入队方法什么时候调用呢?

5:MessageQueue退出
关于MessageQueue的退出方法代码如下:

   void quit(boolean safe) {
        if (!mQuitAllowed) { //队列是否允许退出,这个值是在MessageQueue构造的时候传入的。
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) { 
            if (mQuitting) { //队列已经退出
                return;
            }
            mQuitting = true;

            if (safe) { //是否是安全的退出
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }

可以看出就是遍历链表,然后回收所有的消息。再看下安全的退出removeAllFutureMessagesLocked()方法源码:

    private void removeAllFutureMessagesLocked() {
        final long now = SystemClock.uptimeMillis();
        Message p = mMessages;
        if (p != null) {
            if (p.when > now) { //如果链表头节点是延迟消息,那么直接回收掉所有的消息
                removeAllMessagesLocked();
            } else {
                Message n;
                for (;;) {
                    n = p.next;
                    if (n == null) { //如果消息链表是null,返回
                        return;
                    }
                    if (n.when > now) { //找出链表中的第一个延迟消息
                        break;
                    }
                    p = n;
                }
                p.next = null;
                do { //循环删除第一个延迟消息的所有的消息
                    p = n;
                    n = p.next;
                    p.recycleUnchecked();
                } while (n != null);
            }
        }
    }

结论:
不安全的退出:删除所有的消息
安全的退出:删除第一个延迟消息之后的所有消息(包括第一个延迟消息)。
对于这个结论,至少源码是这样告诉我们的,但是还有没有其他的区别呢?看完Looper应该会有惊喜~
思考:退出什么时候调用呢?

Looper

刚开始接触Android的时候,就知道一点,looper中有个Loop方法,能够从MessageQueue中去消息。那么具体怎么去消息呢?

1:Looper的成员变量

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); //存储线程不同线程的不同数据
    private static Looper sMainLooper; //Ui 主线程的 sMainLooper

    final MessageQueue mQueue; //关联的消息队列
    final Thread mThread; //该Looper所在的线程

    private Printer mLogging;
    private long mTraceTag;

    /* If set, the looper will show a warning log if a message dispatch takes longer than time. */
    private long mSlowDispatchThresholdMs;

从成员变量可以看出,Looper内关联的有MessageQueue对象,创建了当前线程对象,自带Ui主线程的Looper。这三个都好说,那么sThreadLocal又是什么呢?从它的泛型可以看出是Looper的ThreadLocal类,使用场景:以线程为作用域并且不同线程有不同的数据副本。

2:创建Looper对象
通常的创建Looper的方法是:

    public static void prepare() {
        prepare(true); //是否能够退出队列
    }

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) { //是否已经存储了Looper对象,也就是说该线程下,是否已经创建过Looper了,
            //如果Looper已经存在了,就异常显示
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        //新建Looper对象,并且将Looper添加到ThreadLocal中
        sThreadLocal.set(new Looper(quitAllowed));
    }

    private Looper(boolean quitAllowed) { 
        //新建MessageQueue
        mQueue = new MessageQueue(quitAllowed);
        //新建Looper所在的线程对象
        mThread = Thread.currentThread();
    }

从上述代码可以看出,创建Looper做了三件事:

  1. 将Looper添加到ThreadLooper对象中;
  2. 创建MessageQueue对象;
  3. 创建当前Looper的线程对象;

其中从sThreadLocal.get()的判null操作,我们可以得出结论:一个线程只能存在一个Looper!
等等,这个MessageQueue的参数也就是quitAllowed嘛,也就是是否允许退出队列。

3:Looper的loop()方法
在开始loop()方法之前,有必要先看个方法myLooper():

    /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    //从翻译来看:获取与当前线程相关联的Looper对象
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

从上述方法可以猜测ThreadLocal存储的是:Looper以及Looper关联的线程

接下来是重头戏loop()方法:

    public static void loop() {
        final Looper me = myLooper(); //返回当前线程关联的Looper对象
        if (me == null) { //如果Looper为null,表明当前线程不存在Looper,也就是没有实例化Looper对象
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue; //获取当前线程中Looper对象对应的MessageQueue对象

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

        for (;;) { //死循环
            Message msg = queue.next(); // 从MessageQueue中取出消息。有消息就取出,没消息就阻塞
            if (msg == null) { //消息是否为null,这个应该在MessageQueue的next方法中,消息队列退出之后会返回null
                // No message indicates that the message queue is quitting.
                return;
            }
            ...
            msg.target.dispatchMessage(msg); //回调给发送消息的handler处理
            ...
            msg.recycleUnchecked(); //处理完毕,消息回收
        }
    }

从上出代码可以看出,loop方法内部实际上是个死循环,调用MessageQueue的next()方法获取消息,等等,那之前的问题(队列中的next()方法在哪里调用)是不是解决了?有消息就处理,没消息阻塞。

那么怎么消费这个消息了呢? msg.target.dispatchMessage(msg);直接给出了答案,回调给了发送消息的target对象,让handler处理,从这里可以看你出,其实loop并不是真正的消费消息,只是起到一个消息调度的作用,真正消费消息的还是发送该消息的handler。

等等,loop死循环,那么怎么跳出死循环呢? 从源码来看只有满足一个条件,那就是返回的消息为null, 这样就退出死循环了。从MessageQueue的next()方法中可以看到,

// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
      dispose();
      return null;
}

也就说mQuitting为true的时候返回null, 那么怎么mQuitting怎么为true呢?从上文中的MessageQueue的quit代码中可以找到给mQuitting赋值的踪迹:mQuitting=true; 那这个MessageQueue的quit(boolean safe)什么时候调用呢?

4:Looper的退出
对于Looper的退出方式,Google给出了两种方式:

    /**
     * Quits the looper.
     * <p>
     * Causes the {@link #loop} method to terminate without processing any
     * more messages in the message queue.
     * </p><p>
     * Any attempt to post messages to the queue after the looper is asked to quit will fail.
     * For example, the {@link Handler#sendMessage(Message)} method will return false.
     * </p><p class="note">
     * Using this method may be unsafe because some messages may not be delivered
     * before the looper terminates.  Consider using {@link #quitSafely} instead to ensure
     * that all pending work is completed in an orderly manner.
     * </p>
     *
     * @see #quitSafely
     */
     //注释最后一句的翻译是:考虑使用{@ Link quitSafely},以确保所有未决工作都有秩序地完成。
    public void quit() {
        mQueue.quit(false); //安全退出
    }

    /**
     * Quits the looper safely.
     * <p>
     * Causes the {@link #loop} method to terminate as soon as all remaining messages
     * in the message queue that are already due to be delivered have been handled.
     * However pending delayed messages with due times in the future will not be
     * delivered before the loop terminates.
     * </p><p>
     * Any attempt to post messages to the queue after the looper is asked to quit will fail.
     * For example, the {@link Handler#sendMessage(Message)} method will return false.
     * </p>
     */
    //中间几句话翻译一下:在已交付的消息队列中已被处理。然而,延迟的消息与未来的到期时间将不会在循环结束之前处理
    //换句话说:在退出之前,可以立即处理的消息,可以就都发送给handler处理,但是延迟的消息将不会得到处理。
    public void quitSafely() {
        mQueue.quit(true); //不安全退出
    }

猜你喜欢

转载自blog.csdn.net/lmq121210/article/details/81272331