存储架构

一文捋清Android消息机制

微信扫一扫,分享到朋友圈

一文捋清Android消息机制
0

消息机制可称为Handler机制,Android中的视图绘制,事件传递,四大组件的生命周期都离不开Handler机制,都是由Handler机制直接或间接的完成。

重要组件

  • ThreadLocal 每个线程独有的本地变量,线程停止时,线程独有的变量将消失 用于存储线程对应的Looper
  • Looper 用于消息循环的类,由它来源源不断的从MessageQueue中取出Message,并交由Handler处理,与线程关联,每个线程只有一个实例
  • MessageQueue 消息队列 Looper不断从MessageQueue中获取Message,Handler发送消息交由MessageQueue排队,每个线程只有一个实例
  • Handler 发送消息和处理消息的类 发送消息交由MessageQueue去排队,Looper拿到消息会交于对应的Handler去处理,每个线程可有多个
  • Message 消息对象 承载消息数据,并且包含target对象指向发送该消息的Handler

下面是类图(第一次画类图,哈哈哈…)


消息流程

先放一张流程图


1.Looper.prepare()

准备循环,主要是新建一个Looper并与本地线程进行绑定,具体源码如下

public static void prepare() {
        prepare(true);
    }
 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));
    }
复制代码

有两个重载方法,默认传入的true,quitAllowed是否运行退出,主线程是不允许退出的。

内容比较简单,创建一个Looper并设置给mThreadLocal。那mThreadLocal是何方神圣呢?

// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
复制代码

可看到它被static final修饰,进程中全局只有一个。泛型是Looper的ThreadLocal对象,看下ThreadLocal的注释

* This class provides thread-local variables.  These variables differ from
 * their normal counterparts in that each thread that accesses one (via its
 * {@code get} or {@code set} method) has its own, independently initialized
 * copy of the variable.  {@code ThreadLocal} instances are typically private
 * static fields in classes that wish to associate state with a thread (e.g.,
 * a user ID or Transaction ID).
复制代码

提供线程的本地变量,每一个线程都是独有的 再看下它的set方法

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

复制代码

获取当前线程,并获取线程中的threadLocals变量(该变量可看做一个Map集合),并将ThreadLocal本身为Key,set的值为value存入。由此看出神奇的ThreadLocal实际上通过Thread对象中的threadLocals变量保证了保存的变量在每个线程的唯一性。

继续看下Looper的构造方法

private Looper(boolean quitAllowed) {
    	//创建一个MessageQueue
        mQueue = new MessageQueue(quitAllowed);
        //保存当前线程对象
        mThread = Thread.currentThread();
    }
复制代码

2.Looper.loop()

开启循环,主要代码如下

public static void loop() {
 		//1.获取当前线程的Looper
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        //2.获取Looper中的MessageQueue对象
        final MessageQueue queue = me.mQueue;
		···
        for (;;) {
        	//3.从MessageQueue中获取下一条要处理的消息  queue.next是阻塞方法
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            ···
            try {
            	//4.将获取的消息交于 msg.target处理 msg.target实际存储了发送该消息的Handler对象
                msg.target.dispatchMessage(msg);
            } 
            ···
            5.消息回收
            msg.recycleUnchecked();
        }
    }
复制代码

该方法主要有四个步骤:

  1. 获取当前线程的Looper
  2. 获取Looper中的MessageQueue对象
  3. 从MessageQueue中获取下一条要处理的消息 queue.next是阻塞方法
  4. 将获取的消息交于 msg.target处理 msg.target实际存储了发送该消息的Handler对象
  5. 消息使用完毕,进行回收,消息回收后面有讲到
    重复执行步骤3、4源源不断的从消息队列获取消息并处理消息

3.handler.send…和handler.post…

贴几个关键方法吧

public final boolean postAtTime(Runnable r, long uptimeMillis){
        return sendMessageAtTime(getPostMessage(r), uptimeMillis);
    }
   private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }
   public final boolean sendMessageDelayed(Message msg, long delayMillis){
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
   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);
    }
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
复制代码

可以看出所有的发送消息方法post和send最后都会调用sendMessageAtTime方法,然后调用queue.enqueueMessage(msg, uptimeMillis)方法,将Handler自身绑定到Message的target变量上

post方法有个不同点是将Message.callback赋值为传入的Runnable对象,这块后面有用到。

4.queue.enqueueMessage

MessageQueue的enqueueMessage方法

boolean enqueueMessage(Message msg, long when) {
  		//target不允许为空
        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) {
                msg.recycle();
                return false;
            }
            //将msg标记为正在使用中
            msg.markInUse();
            //设置msg的触发时间
            msg.when = when;
            // 将P变量赋值为mMessages,mMessage是消息队列中的第一个消息
            Message p = mMessages;
            //是否需要唤醒  
            boolean needWake;
            //如果消息队列中没有消息 或者 新消息的触发时间在消息队列的第一个消息之前时
            //则新消息放入消息队列头部。如果当前正在阻塞,则需要唤醒  needWake = mBlocked
            if (p == null || when == 0 || when < p.when) {
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // 如果当前是阻塞状态 && 第一个消息是栅栏消息 && 新消息时异步消息时 需要唤醒 (栅栏后面会讲)
                needWake = mBlocked && p.target == null && msg.isAsynchronous();

                //根据when 从小到大的顺序 找到新消息在队列中的位置 并插入到消息队列中
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }

                    //如果需要唤醒 && p是异步消息 则取消唤醒  这是因为新消息不是第一个要执行的异步消息,不用唤醒。
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            
            if (needWake) {
            	//调用本地方法唤醒
                nativeWake(mPtr);
            }
        }
        return true;
    }
复制代码

从上面的源码可以看出,MessageQueue.enqueueMessage方法,是将新消息按照消息的触发时间when进行队列,找到新消息的具体位置,再根据当前的状态判断是否进行唤醒操作(实际上不管同步和异步消息,如果新消息是下一次要执行的消息时就会进行唤醒操作。)异步消息后面会讲,它和栅栏有一些关系

5.queue.next()

我们从上面知道Looper.loop()方法会一直调用queue.next()取出消息进行处理,先放下queue.next()的源码

Message next() {
       	//mPtr和 native层的NavtiveMessageQueue 关联
        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();
            }
            //1.调用本地阻塞方法,nextPollTimeoutMillis为阻塞时长,-1 会一直阻塞,直到调用ativeWake(mPtr)唤醒
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;

                //2.取出下一次要执行的消息
                Message msg = mMessages;//取出入队列的第一个元素
                if (msg != null && msg.target == null) {
                    // 如果该消息是栅栏消息,则获取第一个异步消息
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }

                //3.根据下一条要执行的消息when 判断是否需要返回当前该条消息
                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 != null  取出第一个异步消息后,保证队列的顺序
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                    
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // 如果没有消息,设置下一次超时时间为-1 会一直阻塞,直到调用ativeWake(mPtr)唤醒
                    nextPollTimeoutMillis = -1;
                }

                // 正在退出时会返回空
                if (mQuitting) {
                    dispose();
                    return null;
                }

                //4.mIdleHandlers 空闲任务处理
               	//pendingIdleHandlerCount 表示将要处理的空闲任务数量  只有第一次执行循环时 pendingIdleHandlerCount<0  
               	// mIdleHandlers 所有的空闲任务  调用一次next方法至多一次会进入该判断
                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);
            }

            //如果执行到这里表示 有待执行的空闲任务 并且当前没有Message要处理

           	//执行待处理的任务
            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);
                    }
                }
            }

            //重置待执行空闲任务的数量
            pendingIdleHandlerCount = 0;

            //将阻塞时间设置为0 马上进行下一次消息的取出 
            nextPollTimeoutMillis = 0;
        }
    }

复制代码

主要分4大块

  1. 调用本地方法nativePollOnce(ptr, nextPollTimeoutMillis),该方法会阻塞nextPollTimeoutMillis时长,当值为-1时,会一直阻塞,直到被唤醒
  2. 取出下一次要执行的消息
  3. 根据下一条要执行的消息when 判断是否需要返回当前该条消息,如果when小于等于当前时间,则返回,否则继续下次循环
  4. 如果当前没有要处理的消息,则执行mIdleHandlers 空闲任务

** 空闲任务IdleHandle **

MessageQueue.java

    public void addIdleHandler(@NonNull IdleHandler handler) {
        if (handler == null) {
            throw new NullPointerException("Can't add a null IdleHandler");
        }
        synchronized (this) {
            mIdleHandlers.add(handler);
        }
    }
    public void removeIdleHandler(@NonNull IdleHandler handler) {
        synchronized (this) {
            mIdleHandlers.remove(handler);
        }
     public static interface IdleHandler {
        boolean queueIdle();
    }

复制代码

MessageQueue提供了两个关系IdleHandler的方法,一个是添加一个是删除

IdleHandler是一个接口,该接口只有一个queueIdle方法,返回一个boolean,如果返回false,该IdleHandler掉用过一次即被移除掉,否则每次调用next方法,进入空闲状态都会执行一次IdleHandler

5.handler.dispatchMessage(msg)

从上面得知MessageQueue.next()方法返回要处理的message时,会在Looper.loop()方法的循环内部调用msg.target.dispatchMessage(msg),我们知道msg.target实际上是发送该msg的Handler,下面是handler.dispatchMessage(msg)源码

public void dispatchMessage(Message msg) {
    	//通过post... 方法发送的消息 msg.callback!=null
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
        	//mCallback可以通过Handler的构造方法传入  如果mCallback.handleMessage(msg)返回true表示该消息已消费处理
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //调用自身的handlerMessage  一般我们自定义Handler时会重写该方法
            handleMessage(msg);
        }
    }
     private static void handleCallback(Message message) {
        message.callback.run();
    }
复制代码

从上面的源码可以看出,平常我们在使用自定义Handler时,如果重写了handlerMessage方法,执行的条件是,通过send方法发送消息,并且没有传入mCallback或者传入的mCallback的handlerMessage方法返回false

栅栏和异步消息

栅栏也可叫做同步栅栏,作用是拦截同步消息,放行异步消息。MessageQueue有下面几个关于栅栏的方法

MessageQueue.java
	
 	public int postSyncBarrier() {
        return postSyncBarrier(SystemClock.uptimeMillis());
    }
    //加入一个同步栅栏
    private int postSyncBarrier(long when) {
        synchronized (this) {
            //获取一个token mNextBarrierToken从0开始自增长
            final int token = mNextBarrierToken++;
            //创建一个栅栏消息 可以看出并没有设置msg.target 其实msg是否是栅栏消息就是根据msg.target==null判断的
            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;
            }
            //返回token  作为一个栅栏标记 用于取消栅栏
            return token;
        }
    }

    /**
     *根据栅栏的token值移除栅栏消息
     * @hide
     */
    public void removeSyncBarrier(int token) {
        // Remove a sync barrier token from the queue.
        // If the queue is no longer stalled by a barrier then wake it.
        synchronized (this) {
            //找出栅栏消息和栅栏的前一个消息
            Message prev = null;
            Message p = mMessages;
            while (p != null && (p.target != null || p.arg1 != token)) {
                prev = p;
                p = p.next;
            }
            if (p == null) {
                throw new IllegalStateException("The specified message queue synchronization "
                        + " barrier token has not been posted or has already been removed.");
            }
            final boolean needWake;
            if (prev != null) {//如果栅栏不是第一个消息 则表示栅栏消息还没在执行到 所以不需要进行唤醒
                prev.next = p.next;
                needWake = false;
            } else {//如果第一个是要移除的栅栏消息,则移除消息 移除后消息队列的第一个消息不是栅栏消息则进行唤醒
                mMessages = p.next;
                needWake = mMessages == null || mMessages.target != null;
            }
            //回收消息
            p.recycleUnchecked();
            if (needWake && !mQuitting) {
                nativeWake(mPtr);
            }
        }
    }
复制代码
  1. 加入栅栏时,根据栅栏的时间when,按照时间顺序插入到消息队列中并返回栅栏消息的标记token
  2. 删除栅栏时,根据栅栏消息的token,删除栅栏消息,如果没有执行到该消息,则不需要唤醒操作。如果执行到了,并且下一条消息不是栅栏消息则进行唤醒操作

同步栅栏功能的实现,再看上面的MessageQueue.next()方法

Message next() {
      	···
        for (;;) {
            ···
            synchronized (this) {
             	 ···
                Message msg = mMessages;
                //如果执行的该条消息是栅栏消息,则获取队列中的异步消息  msg.target == null即为同步栅栏消息
                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());
                }
     }
复制代码

可以看出同步栅栏的作用是 拦截栅栏后面同步消息,放行异步消息

放张图就明白了


红色的栅栏消息 蓝色的为同步消息 绿色的为异步消息

Message消息池

Message的构造方法上有下面这些注释

/*While the constructor of Message is public, the best way to get
 * one of these is to call {@link #obtain Message.obtain()} or one of the
 * {@link Handler#obtainMessage Handler.obtainMessage()} methods, which will pull
 * them from a pool of recycled objects
 */
复制代码

当前我们需要构造Message时,最好的方式是调用Message.obtain() 或者Handler#obtainMessage 方法的一个, 这样会复用消息池的对象

Handler#obtainMessage 方法最终调用的也是Message.obtain()方法,那么看下Message.obtain()方法的源码

private static Message sPool;
  public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {//消息池不等于空
                //取出消息池的第一个元素 并且将缓存数量-1 并返回
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        //如果消息池没有消息 则新建一个消息
        return new Message();
    }
复制代码

可以看出sPool即是消息池,并且是静态的,通过静态实现缓存效果。调用obtain方法时,会从消息池中取出第一个消息,如果消息为空,则新建一个Message

那么他在哪里赋值的呢? 我们看Looper.loop()方法,在掉msg.target.dispatchMessage(msg)后面会调用一个 msg.recycleUnchecked() 方法,我们看下这个方法

void recycleUnchecked() {
     
        // 清除所有标记
        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) { //如果消息池数量小于MAX_POOL_SIZE  MAX_POOL_SIZE=50
            	//把消息自身作为消息池的第一个元素,消息池数量+1
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }
复制代码

来张图说明一下,左边是MessageQueue 右边是sPool消息池


完整流程图

这张流程图包括Native层,后期有时间会写篇关于Native消息机制的文章。

(点击图片查看大图)


阅读原文...


稀土掘金

新建NodeJS Web项目的几个最佳实践

上一篇

《仙剑奇侠传》官方淘宝店上线 赵灵儿浴巾只要128元

下一篇

您也可能喜欢

评论已经被关闭。

插入图片
一文捋清Android消息机制

长按储存图像,分享给朋友