Android源码--Handler消息机制浅析

1. 整体流程概述

整体流程概述:在Activity的启动过程中,ActivityThread的main()方法被执行,会初始化主线程Looper对象;接着检查到主线程Handler对象为空的话就重新赋值给它;并执行主线程Looper对象的loop()方法来开启死循环,循环体中不断从MessageQueue中取出Message,再通过主线程Handler对象来分发并执行它的handleMessage(msg)方法,最后调用Message的回收方法;注意:如果MessageQueue中取不到Message,则说明MessageQueue正在退出同时跳出loop()的死循环。

2. 流程分析

2.1 Message介绍

开始分析Handler消息机制流程前,我们先来看看消息Message是什么,再逐步分析取出消息并分发处理,发送消息的过程。Message有哪些重要的成员变量(Java层)?

    public int what;
    public int arg1;
    public int arg2;
    public Object obj;
    Handler target;
    Runnable callback;
    Message next;//当前消息的下一节点
    private static Message sPool;//消息池头节点
    private static int sPoolSize = 0;
    private static final int MAX_POOL_SIZE = 50;
    private static boolean gCheckRecycle = true;

从上的代码看出,Message实现一个单链表数据结构;包含私有静态对象sPool作为消息池来缓存消息链,并且sPool是消息池的首节点,next表示当前消息的下一消息节点;默认消息池缓存大小为50;gCheckRecycle为true说明默认开启消息回收检测标志(5.0系统以上才为true);同时包含Handler类型的成员变量target和Runnable类型的callback,看样子应该和消息回调什么的有关,那么对不对呢?继续往下。

2.2 Looper循环取出消息的过程

2.2.1 ActivityThread的main()

开篇中说到Looper.loop()是在ActivityThread的main()方法被执行。那它的main()方法怎么开启的,你倒是说呀!啥都别说,看源码的实现。

   public static void main(String[] args) {
       // ignore some code.
       //初始化主线程Looper
       Looper.prepareMainLooper();
       // ignore some code.
       ActivityThread thread = new ActivityThread();
       thread.attach(false, startSeq);
       if (sMainThreadHandler == null) {
           //重新赋值到主线程Handler对象
           sMainThreadHandler = thread.getHandler();
       }
       //开启Looper循环
       Looper.loop();
       throw new RuntimeException("Main thread loop unexpectedly exited");
   }

从源码(已加注释)可以看出干了这些事:

1 初始化主线程Looper对象
2 创建ActivityThread对象
3 确保主线程Handler对象sMainThreadHandler有值
4 开启Looper循环

2.2.2 初始化主线程Looper对象

分析Looper.prepareMainLooper()之前,看看Looper成员变量是啥。

      // sThreadLocal.get() will return null unless you've called prepare().
      static final ThreadLocal sThreadLocal = new ThreadLocal();
      private static Looper sMainLooper;  // guarded by Looper.class
      final MessageQueue mQueue;
      final Thread mThread;

从成员变量的命名和类名称看出,Looper内部包含1个消息队列对象mQueue和1个线程对象mThread,静态内部对象sThreadLocal和sMainLooper。其中ThreadLocal的作用是保证线程之间存储数据相互独立。
开始分析prepareMainLooper()方法,初始化主线程Looper

public static void prepareMainLooper() {
    //初始化Looper并存入ThreadLocal
    prepare(false);
    synchronized (Looper.class) {
    //验证sMainLooper,防止多次执行prepareMainLooper()
    if (sMainLooper != null) {
     throw new IllegalStateException("The main Looper has already been prepared.");
    }
    //设置sMainLooper
    sMainLooper = myLooper();
    }
}

    //prepare(false)方法,prepare()方法内部调用的是prepare(true)

private static void prepare(boolean quitAllowed) {
    //验证sThreadLocal.get() ,防止多次执行prepare()
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    //创建Looper,并放入sThreadLocal
    sThreadLocal.set(new Looper(quitAllowed));
}

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);//新建MessageQueue
    mThread = Thread.currentThread();//关联当前线程
}

//myLooper()方法
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();//取出Looper对象
}

从代码中知道,初始化1个Looper并存入ThreadLocal,同时将此Looper设为主线程Looper即sMainLooper。而且Looper中ThreadLocal有且只有1个。结合前面的成员变量介绍,1个Thread对应1个Looper,每个Looper对应1个MessageQueue。Looper的prepareXXX()方法只能被调用1次。

2.2.3 Looper.loop()的实现

查看Looper.loop()的源码

public static void loop() {
    //获取looper对象
    final Looper me =myLooper();
    //获取MessageQueue
    final MessageQueue queue = me.mQueue;
    //ignore some codes
    for (;;) {
    //从消息队列中取出Message
    Message msg = queue.next(); // might block
    if (msg ==null) {
        // No message indicates that the message queue is quitting.
        return;
    }
    //ignore some codes
    try {
        msg.target.dispatchMessage(msg);
    }finally {
    //ignore some codes
    //回收消息
    msg.recycleUnchecked();
    }
}

可以看到获取到Looper后,再获取MessageQueue,死循环代码中不断重复以下过程,通过queue.next()在消息队列中取出message,通过msg持有的Handler来分发消息--msg.target.dispatchMessage(msg),target就是前面介绍的Handler,分发后回收这条msg进消息池。如果取不到message则跳出循环体。那么queue.next()方法究竟如何获取出消息?

Message next() {
        final long ptr = mPtr;
        if (ptr == 0) {//消息循环已退出,直接退出next方法
            return null;
        }
        //开始循环时pendingIdleHandlerCount设为-1
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            //epoll机制来阻塞,调用底层方法等待nextPollTimeoutMillis长时间,
            //或者消息队列被唤醒后,退出此方法
            nativePollOnce(ptr, nextPollTimeoutMillis);
            synchronized (this) {
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    //消息队列不为空,且没有handler时,去查找第一条异步消息
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }

                if (msg != null) {//handler不为空
                    if (now < msg.when) {
                        //下一条消息未准备好,设置下一次唤醒时间,时间到达后唤醒它
                        nextPollTimeoutMillis = 
                            (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 取出消息
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // 没有消息nextPollTimeoutMillis设为-1
                    nextPollTimeoutMillis = -1;
                }

                // 所有消息已处理完,消息队列正在退出,返回null
                if (mQuitting) {
                    dispose();
                    return null;
                }
                
                //在第一次循环时,pendingIdleHandlerCount = -1,获取IdleHandler数,空闲句柄
                //而这些IdleHandler是当消息队列为空或者消息队列中的第一条消息在将来被处理时才会运行
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }

                if (pendingIdleHandlerCount <= 0) {
                    // 没有IdleHandler可运行.  循环并等待.
                    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);
                    }
                }
            }
            // 重置IdleHandler数为0
            pendingIdleHandlerCount = 0;
            // 当有空闲的IdleHandler, 一个新的message可以被交付时,
            //不用等待,再次查询一条可以被处理的message
            nextPollTimeoutMillis = 0;
        }
    }

从next的实现来看,同样是循环体中去获取消息,整体循环以下过程:

1 在取之前执行本地方法nativePollOnce()(实现epoll机制),该方法作用是阻塞消息队列等待nextPollTimeoutMillis长时间后或者消息队列被唤醒后,跳出nativePollOnce()方法,从而实现阻塞操作避免系统卡死;
2 优先尝试获取异步消息(如果消息队列中包含同步消息和异步消息,同步消息中没有handler去处理消息时,优先获取第一条异步消息)
3 判断当前消息是否为空,不为空时判断消息是否可以立即返回,并设置下一次等待唤醒消息队列的时间或者直接返回消息;为空时nextPollTimeoutMillis设为-1,满足pendingIdleHandlerCount <= 0条件,回到nativePollOnce()方法,继续阻塞等待;
4 如果有空闲IdleHandler,重置pendingIdleHandlerCount和nextPollTimeoutMillis,不等待直接再次查询消息队列;
至此next方法分析完成,本质就是从消息队列中不断尝试取出消息,取不到就通过本地方法进行阻塞操作,避免系统卡死。MessageQueue本质是通过Message的单链表结构对消息队列进行操作,同时还拥有一系列的native方法,是Handler通信机制java层和C/C++层的连接桥梁。

2.3 发送消息

既然不断取消息并分发处理的过程有了,只要Handler再发送一条消息,整个流程就完整了。继续看Handler是如何发送消息的。
Handler内部:
sendMessage(Message msg) --> sendMessageDelayed(Message msg, long delayMillis) --> sendMessageAtTime(Message msg, long uptimeMillis)

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            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);
    }

可以看到,发送消息最后调用MessageQueue的enqueueMessage()方法。继续跟进enqueueMessage()方法。

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) {
                //正在退出消息队列,回收消息,返回false
                msg.recycle();
                return false;
            }

            msg.markInUse();//标记消息正在使用
            msg.when = when;
            Message p = mMessages;//消息队列中的头消息
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
            //消息队列中没有消息或者入参消息msg需要在消息队列头消息之前处理
            //如果消息队列处于阻塞需要唤醒
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {//消息队列中有消息并且入参msg需要在消息队列头消息之后处理
        
            //按时间先后顺序将msg插入消息队列。只有当消息队列中头消息节点是异步消息并且被
            //阻塞了,才会去唤醒消息队列(不然的话,头消息节点是异步的,且被阻塞,它之后
            //的所有消息都会阻塞),一般情况下不会唤醒消息队列
            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;
}

可以看出MessageQueue是按Message触发时间的先后顺序来排列的,队列头的消息最早触发。如果入参消息需要加入到队列中,从队列头开始遍历并插入到合适的位置,保证时间先后顺序。至此发送消息的过程分析完成。由于Looper.loop()在不断取消息进行处理,当发送出一条消息时,自然就完成消息的完整收发处理过程。另外,我们现在只知道Looper内部有MessageQueue,Thread,MessageQueue内部有Message,消息发送过程,消息接收处理过程,那么Handler是怎么和这些东西关联起来的?看看Handler的构造方法

public Handler(Callback callback, boolean async) {
    mLooper = Looper.myLooper();//关联Looper,而Looper会关联当前线程
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;//关联LMessageQueue
    mCallback = callback;
    mAsynchronous = async;
}

通过成员变量的形式将它们关联起来,那么可以看出,Handler会关联1个Looper,1个MessageQueue,并且关联当前Thread,Handler操作的都是当前线程的Looper和MessageQueue。

3. 其他相关知识

1 Message的obtain()中,消息池中有Message的话,直接取出消息链的头节点返回;消息池为空则创建新的Message返回;
2 Message的recycle()中,重置成员变量为初始值,消息池缓存的消息没超过50时,回收当前消息进消息链头部;
3 Message的消息池缓存机制,一定程度上起到了性能优化的作用;
4 Looper中的ThreadLocal,存储Looper对象;ThreadLocal通过继承弱引用WeakReference的静态内部类Entry实现存储,而Entry中key为ThreadLocal,value为Looper,其弱引用作用于key对象ThreadLocal,默认存储大小为16。Looper中ThreadLocal被声明为静态变量,是为了避免内存泄漏(弱引用被回收后,key为null,value存在,外部无法通过为null的key来获取Looper,同时因为引用关系存在,如果对应线程不及时回收,会引起此问题)
5 Looper.loop()或者MessageQueue.next()不会引起系统卡死或者ANR,是因为底层有阻塞操作,当被唤醒或者阻塞时间结束才会继续循环执行下一步;被阻塞时主线程让出CPU资源进入休眠状态,直到下个消息到达或者有事务发生。详情请自行百度Linux pipe/epoll机制
6 通常我们在代码中自己实现自定义Handler以及重写handleMessage方法,并且通过Looper.prepare(),Looper.loop()和Handler.sendMessage()处理异步任务。而系统的主线程在启动Activity时已经调用过Looper.prepareMain(),所以主线程可以直接进行收发和处理消息。自定义Handler要处理消息,必须调用Looper.prepare()来初始化Looper,构建消息队列,同时Looper.loop()来开启消息队列循环。
7 自定义Handler时,需要注意避免内存泄漏,谷歌官方的建议是,构建静态类Handler,并且内部通过软引用来持有外部的Activity。

4. 总结

ActivityThread的main()方法中,创建并初始化Looper,关联当前线程--主线程,开启Looper的loop()方法,循环检测有无消息,有就处理,没有就阻塞在MessageQueue的next中的nativePollOnce()方法,让出主线程cpu资源,不会引起系统卡死。

你可能感兴趣的:(Android源码--Handler消息机制浅析)