一文捋清Android消息机制

栏目: 后端 · 发布时间: 6年前

内容简介:消息机制可称为Handler机制,Android中的视图绘制,事件传递,四大组件的生命周期都离不开Handler机制,都是由Handler机制直接或间接的完成。下面是类图(第一次画类图,哈哈哈...)先放一张流程图

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

重要组件

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

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

一文捋清Android消息机制

消息流程

先放一张流程图

一文捋清Android消息机制

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());
                }
     }
复制代码

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

放张图就明白了

一文捋清Android消息机制

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

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消息池

一文捋清Android消息机制

完整流程图

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

(点击图片查看大图)

一文捋清Android消息机制

以上所述就是小编给大家介绍的《一文捋清Android消息机制》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Realm of Racket

Realm of Racket

Matthias Felleisen、Conrad Barski M.D.、David Van Horn、Eight Students Northeastern University of / No Starch Press / 2013-6-25 / USD 39.95

Racket is the noble descendant of Lisp, a programming language renowned for its elegance and power. But while Racket retains the functional goodness of Lisp that makes programming purists drool, it wa......一起来看看 《Realm of Racket》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具