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<Looper> sThreadLocal = new ThreadLocal<Looper>();
      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资源,不会引起系统卡死。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 228,546评论 6 533
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 98,570评论 3 418
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 176,505评论 0 376
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 63,017评论 1 313
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 71,786评论 6 410
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 55,219评论 1 324
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 43,287评论 3 441
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 42,438评论 0 288
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 48,971评论 1 335
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 40,796评论 3 354
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 42,995评论 1 369
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 38,540评论 5 359
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,230评论 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,662评论 0 26
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 35,918评论 1 286
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 51,697评论 3 392
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 47,991评论 2 374

推荐阅读更多精彩内容