专栏名称: 吴七禁
Android开发工程师
目录
相关文章推荐
新智元  ·  Ilya又回来了!神秘初创SSI估值200亿 ... ·  昨天  
爱可可-爱生活  ·  【[99星]Ollama模型直链生成与安装工 ... ·  昨天  
爱可可-爱生活  ·  【[74星]EasyDeploy:一站式大规 ... ·  昨天  
爱可可-爱生活  ·  通俗版解读 查看图片-20250207090641 ·  2 天前  
宝玉xp  ·  GitHub Copilot 现在也支持 ... ·  2 天前  
51好读  ›  专栏  ›  吴七禁

Handler机制实现原理(一)宏观理论分析与Message源码分析

吴七禁  · 掘金  ·  · 2019-09-17 10:54

正文

阅读 41

Handler机制实现原理(一)宏观理论分析与Message源码分析

预热

在写这篇文章前我不止一次的问自己,网上分析Handler机制原理的文章那么多,为啥还要画蛇添足啊?不是说前人们写的文章不好,我就是觉得他们写的不细,有些点不讲清楚,逻辑很难通顺的,每次我学个什么东西时遇到这种情况都贼难受。

我们处在这么一个被层层封装的世界里搞开发,被真相所蒙蔽本来就是一件很痛苦的事,如果分享知识的人再搞出了一堆天书般的“经验总结”,这得多打击求学者的心啊!

此篇文章为我【 Android Framework层分析】系列的第一篇。说了这么一大堆就当作个序吧。作为在探索技术之路中饱经风霜中一员,希望我能谨记前人的经验教训,多走点心,思路清晰的分析,尽量写高质量的文章,既对得起自己也对得起他人。

本文所分析的内容大概有以下几个模块:

  • 开发人员最初设计Handler时想要解决什么问题
  • Handler 为我们提供了哪些功能以及如何使用
  • Handler实现原理的理论分析
  • Handler实现原理的源码分析
  • Android UI线程中Handler的特殊操作

文章很长,但思路是循序渐进的,如果你能坚持读完我相信肯定不会让你失望,只要跟着文章的思路走,就不会有需要反复读好几遍加深理解的地方。建议刚读的时候先快速浏览一遍,在脑海中宏观的理清逻辑,有想不通的细节再仔细研究,如有不明白的地方或觉得我分析有误的地方欢迎留言评论。

设计Handler 的初衷

在分析Handler之前,需要先搞清楚两个概念:

  • 同步与异步的区别
  • 线程与多线程的概念

讲道理上述两点中每个拿出来都涉及到很多东西,一方面我学的也不是很深不好意思献丑另一方面这不是本文重点,所以我就当老铁们都知道啦(嘿嘿一笑)。

同步与异步 - 金拱门篇

Java多线程通信

Java中有很多种方法实现线程之间相互通信访问数据,大概先简单的介绍两个典型的,就不上代码了。

  1. 通过 synchronized 关键字以“上锁”机制实现线程间的通信。多个线程持有同一个对象,他们可以访问同一个共享变量,利用 synchronized “上锁”机制,哪个线程拿到了锁,它就可以对共享变量进行修改,从而实现了通信。

  2. 使用 Object 类的 wait / notify 机制,执行代码 obj.wait(); 后这个对象 obj 所在的线程进入阻塞状态,直到其他线程调用了 obj.notify(); 方法后线程才会被唤醒。

Android多线程的特殊性

在上面的两个Java多线程通信的方法中都有一个共同的特点,那就是线程的 阻塞 。利用 synchronized 机制拿不到锁的线程需要等拿到锁了才会继续执行操作, obj.wait(); 需要等 obj.notify(); 才会继续执行操作。

虽然Android系统是由Java封装的,但是由于Android系统的特殊性,Google的开发人员对Android线程的设计进行了改造。他们把启动APP时运行的主线程定义为 UI线程

UI线程负责所有你能想到的所有的跟界面相关的操作,例如分发绘制事件,分发交互事件等可多了。由于其特殊性Android系统强制要求以下两点:

  1. 为保持用户界面流畅UI线程不能被阻塞,如果线程阻塞界面会卡死,若干秒后Android系统抛出ANR。

  2. 除UI线程外其他线程不可执行UI操作。

(此处只是简单介绍一下UI线程,后面会有专门一节分析Android UI线程。)

Android 多线程通信

既然UI线程中不能被阻塞,那么查询数据库和访问网络这类的耗时操作肯定就不能在UI线程中执行了,我们就需要单独开个线程操作。

但是除UI线程外其他线程又不可执行UI操作,最后还是要回到UI线程更新UI,这就需要多线程之间的通信。

可Java中线程间通信又都是阻塞式方法,所以传统的Java多线程通信方式在Android中并不适用。

为此Google开发人员就不得不设计一套UI线程与Worker线程通信的方法。既能实现多线程之间的通信,又能完美解决UI线程不能被阻塞的问题。具体方法有以下几类:

  1. view.post(Runnable action) 系列,通过View对象引用切换回UI线程。

  2. activity.runOnUiThread(Runnable action) ,通过Activity对象引用切换回UI线程。

  3. AsyncTask ,内部封装了UI线程与Worker线程切换的操作。

  4. Handler ,本文的主角,异步消息处理机制,多线程通信。

小结

说到了这里应该大概明白了当初设计Handler的初衷。

由于Android系统的特殊性创造了 UI线程

由于UI线程的特殊性创造了若干个 UI线程与Worker线程通信的方法

在这若干个线程通信方法中就包含了 Handler

Handler就是针对Android系统中与UI线程通信而专门设计的 多线程通信机制

Handler 提供的一些方法

Handler API方法相对其他Android API方法来说算少了的,不过要是都拿出挨个介绍还是很多,所以这里给出官方的API文档地址: 不用搭梯子就能看的 Android官网 - Handler API

在介绍Handler的消息处理前还有一件事,为了线程传递数据时方便处理,开发人员为Handler专门设计了一个传递消息的载体 Message ,这样就能让传输数据比较规范化,它有两个十分重要且常用的属性:

  • int waht; 开发者自定义的消息标识,我们可以根据它来区分不同的消息,例如 switch(message.what)
  • Object obj; 开发者想要传递的数据,具体什么类型的都可以。

同样,此处就是简单介绍一下Message,后面会详细分析的。

Handler 提供的方法有些我们是用不到的,能用到的方法大体分为 发送消息 处理消息 切换线程 三类。

发送消息类方法

1. sendEmptyMessage boolean sendEmptyMessage (int what) 发送一个只有消息标识 waht 的空消息。该方法适用于不需要传递具体消息只是单独的发通知时。

2. sendEmptyMessageAtTime boolean sendEmptyMessageAtTime (int what, long uptimeMillis) 在具体指定的时间 uptimeMillis 发送一个只有消息标识 waht 的空消息。 uptimeMillis 为系统开机到当前的时间(毫秒)。

3. sendEmptyMessageDelayed boolean sendEmptyMessageDelayed (int what, long delayMillis) 在过了 delayMillis 毫秒之后发送一个只有消息标识 waht 的空消息。

4. sendMessage boolean sendMessage (Message msg) 发送一条消息。

5. sendMessageAtTime boolean sendMessageAtTime (Message msg, long uptimeMillis) 在具体指定的时间 uptimeMillis 发送一条消息。 uptimeMillis 为系统开机到当前的时间(毫秒)。

6. sendMessageDelayed boolean sendMessageDelayed (Message msg, long sendMessageDelayed ) 在过了 delayMillis 毫秒之后发送一条消息。

处理消息类方法

handleMessage void handleMessage (Message msg) 负责接受消息,所有发送的消息都会返回该方法,注意!必须 Override 这个方法才能接收消息。

切换线程类方法

1. post boolean post (Runnable r) Runnable r 会运行在 handler 对象被创建的线程上。当我们在UI线程创建了 Hnadler 对象,在Worker线程调用 handler.post() 方法时, Runnable 就会运行在UI线程中。

2. postAtTime boolean postAtTime (Runnable r, long uptimeMillis) 在具体指定的时间 uptimeMillis Runnable 运行在 Handler 对象被创建的线程中。

3. postDelayed boolean postDelayed(Runnable r, long delayMillis) 在具体指定的时间 delayMillis 之后让 Runnable 运行在 Handler 对象被创建的线程中。

使用Handler

在上节方法介绍中出现了 XXXAtTime(long uptimeMillis) XXXDelayed(long delayMillis) 这两类控制时间的方法,两类方法的时间参数虽然都是毫秒,但是代表的意义却不一样:

  • XXXDelayed(long delayMillis) 中的时间参数是指 从当前时间开始 delayMillis 毫秒后
  • XXXAtTime(long uptimeMillis) 中的时间参数是指 从系统开机算起 uptimeMillis 毫秒后

利用静态方法 SystemClock.uptimeMillis() 可以得到从系统开机到现在的毫秒数,所以,下面两个语句执行的时间是相等的:

  • XXXDelayed(1000);
  • XXXAtTime(SystemClock.uptimeMillis() + 1000)

知道了这些后就可以随意使用Handler了。下面是使用Handler的一个小Demo,代码有点长但大部分都是注释,代码共分为三块:

  1. 创建Handler,实现处理消息逻辑
  2. 定义了Worker线程,在Worker线程内部使用Handler提供的方法。
  3. activity.onCreate(Bundle savedInstanceState) 初始化控件,启动Worker线程。
public class HandlerActivity extends AppCompatActivity {

    TextView mTextView;

    // 实现Handler
    Handler mHandler = new Handler(){

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){

                case 1:
                    mTextView.setText("接收到一条从Worker Thread线程发来空消息");
                    break;
                case 2:
                    mTextView.setText("接收到一条从Worker Thread线程发来指定时间的空消息");
                    break;
                case 3:
                    mTextView.setText("接收到一条从Worker Thread线程发来指定时间之后的空消息");
                    break;
                case 4:
                    String data = (String) msg.obj;
                    mTextView.setText("接收到一条从Worker Thread线程发来消息,消息中包含数据:"+ data);
                    break;
                case 5:
                    String data1 = (String) msg.obj;
                    mTextView.setText("接收到一条从Worker Thread线程发来指定时间的消息,消息中包含数据:" + data1);
                    break;
                case 6:
                    String data2 = (String) msg.obj;
                    mTextView.setText("接收到一条从Worker Thread线程发来指定时间之后消息,消息中包含数据:" + data2);
                    break;
            }
        }
    };

    // 定义Worker线程
    class WorkerThread extends Thread{

        @Override
        public void run() {
            super.run();

            // 在Worker Thread 向UI Thread 发送一条只有 what 的空消息
            mHandler.sendEmptyMessage(1);

            // 在指定的时间Worker Thread 向UI Thread 发送一条只有 what 的空消息
            mHandler.sendEmptyMessageAtTime(2, SystemClock.uptimeMillis() + 1000);

            // 在指定的时间之后Worker Thread 向UI Thread 发送一条只有 what 的空消息
            mHandler.sendEmptyMessageDelayed(3,1000);

            // 创建一个Message
            Message msg1 = new Message();
            msg1.what = 4;
            msg1.obj = "这是一个从Worker Thread发送的普通信息";
            // 在Worker Thread 向UI Thread 发送一条消息
            mHandler.sendMessage(msg1);

            // 创建一个Message
            Message msg2 = new Message();
            msg2.what = 5;
            msg2.obj = "这是一个在指定的时间从Worker Thread发送的信息";
            // 在指定的时间Worker Thread 向UI Thread 发送一条消息
            mHandler.sendMessageAtTime(msg2,SystemClock.uptimeMillis() + 1000);

            // 创建一个Message
            Message msg3 = new Message();
            msg3.what = 6;
            msg3.obj = "这是一个在指定的时间之后从Worker Thread发送的信息";
            // 在指定的时间之后Worker Thread 向UI Thread 发送一条消息
            mHandler.sendMessageDelayed(msg3,1000);

            // post方法可以让Runnable直接运行在UI Thread中
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    mTextView.setText("通过post方法,从Worker Thread回到了UI Thread");
                }
            });

            // postAtTime方法可以让Runnable在指定的时间直接运行在UI Thread中
            mHandler.postAtTime(new Runnable() {
                @Override
                public void run() {
                    mTextView.setText("通过postAtTime方法,从Worker Thread回到了UI Thread");
                }
            },SystemClock.uptimeMillis() + 1000);

            // postDelayed方法可以让Runnable在指定的时间之后直接运行在UI Thread中
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    mTextView.setText("通过postDelayed方法,从Worker Thread回到了UI Thread");
                }
            },1000);

        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        // 绑定控件
        mTextView = (TextView)findViewById(R.id.tv);
        // 启动worker线程
        new WorkerThread().start();
    }
}

复制代码

小结

到这里我们已经熟练的掌握了Handler提供的操作,是不是很简单啊?其实Handler中还有一些比较特别方法这里没有介绍,在后面看源码分析实现原理里会介绍。

Message的用法绝不是实例化出来赋值两个属性那么简单,本节只介绍了一点点,同样在分析源码的时候会详细的介绍。

Handler实现原理 - 理论分析

关于源码分析的文章我真是有一肚子吐槽的话想说,总的来说就是介绍源码的作者嗨的不行,而看文章的人一脸懵逼。吸取了前辈们的教训,这里分析源码前先来一波理论上的分析。

线程中接收消息端的特殊性

首先我们得知道理想状态下使用Handler是希望它被实例化在哪个线程,哪个线程就是消息的接收端,虽然在其他线程内发送消息时调用的同样是这个Handler的引用。这没错吧?

两个线程通信

多线程通信

根据上面的结论可以知道Handler接收消息端是线程独立的,不管handler的引用在哪个线程发送消息都会传回自己被实例化的那个线程中。

但显而易见的是Handler不可能是线程独立的,因为它的引用会在别的线程作为消息的发送端,也就是说它本身就是多线程共享的引用,不可能独立存在于某个线程内。

所以!Handler需要一个 独立存在于线程内部且私有使用的类 帮助它接收消息!这个类就是Looper!

Looper - 线程独立

通过上节分析我们已经知道设计Looper就是为了辅助Handler接收消息且仅独立于线程内部。那如何才能实现线程独立的呢?

好消息是Java早就考虑到了这一点,早在JDK 1.2的版本中就提供 ThreadLocal 这么一个工具类来帮助开发者实现线程独立。这里简单分析一下 ThreadLocal 的使用方法,不分析实现原理了。 Android官网 - ThreadLocal API

ThreadLocal 支持泛型,用于定义线程私有化变量的类型,实例化对象时可选 Override 一个初始化方法 initialValue() ,这个方法的作用就是给你的引用变量赋初始值,如果没有 Override 这个方法那么默认你的引用变量就是 null 的:

    //定义一个线程私有的String类型变量
    private static final ThreadLocal<String> local = new ThreadLocal<String>(){
        
        // 设置引用变量的初始化值
        @Override
        protected String initialValue() {
            return super.initialValue();
        }
    };
复制代码

定义好了 ThreadLocal 之后还需要了解三个方法:

  • get() 得到你的本地线程引用变量。
  • set(T value) 为你的本地线程引用变量赋值。
  • remove() 删除本地线程引用变量。

是不是很简单呢?有了 ThreadLocal 之后我们只需要把Looper存进去就能实现线程独立了。

private static final ThreadLocal<Looper> mLooper = new ThreadLocal<Looper>();
复制代码

到这里再梳理一下流程:

  1. Handler 引用可以多线程间共享。
  2. 当Handler对象在其他线程发送消息时,通过Handler的引用找到它所在线程的Looper接收消息。
  3. Looper 负责接收消息再分发给Handler的接收消息方法。

Looper

但是!这样还会有一个问题,如果多个线程同时使用一个Handler发消息,Looper该怎么办?给接收消息的方法上锁吗?显然不能这样做啊!于是就设计了 MessageQueue 来解决这个问题。

MessageQueue - 多线程同时发消息

为了防止多个线程同时发送消息Looper一下着忙不过来,于是设计一个MessageQueue类以队列的方式保存着待发送的消息,这样Looper就可以一个个的有序的从MessageQueue中取出消息处理了。

既然MessageQueue是为Looper服务的,而Looper又是线程独立的,所以MessageQueue也是线程独立的。

MessageQueue

小结

现在我们已经知道为了完成异步消息功能需要有Handler家族的四位成员共同合作:

  • Handler: 负责发送消息,为开发者提供发送消息与接收消息的方法。
  • Message: 消息载体,负责保存消息具体的数据。
  • **MessageQueue:**消息队列,以队列形式保存着所有待处理的消息。
  • **Looper:**消息接受端,负责不断从MessageQueue中取出消息分发给Handler接受消息端。

这四位成员哪个都不是平白无故出现的。因为要规范化消息传递格式而定义了Message;为了实现消息接收端只存在线程内部私有化使用而定义了Looper;为了解决多线程同时发送数据Looper分发消息处理时会产生的问题而设计MessageQueue队列化消息。

到这里你应该知道了Handler家族四位成员各自负责的是什么工作,以及他们自身的特点特殊性,比如Handler是线程间共享的而Looper是线程独立的,MessageQueue跟Looper又是一对一的。

接下来我们就可以开始读源码了!

Message 源码分析

本文出现的源码版本均为Android 7.1.1(Nougat) - API 25 版本。

Message作为消息传递的载体,源码主要分为以下几个部分:

  1. 操作数据相关,类似 getter() setter() 这种方法还有之前提到过的 what obj 这类属性。
  2. 创建与回收对象实例相关,除了用关键字 new 外,其他得到对象实例的方法。
  3. 其他工具类性质的扩展方法。

Message中的数据属性与方法

首先说一个本篇文章忽略的属性及相关方法: public Messenger replyTo;

为什么要忽略过去呢?因为 Messenger 类是基于 Message 上实现 进程间通信 的类。注意,是进程间通信,不是线程间通信。一方面进程间通信不是本文分析的重点,另一方面进程间通信需要掌握AIDL方面的知识。

接下来就让我们看看Message源码有哪些可供我们使用的属性吧:

  • public int what; :开发者可自定义的消息标识代码,用于区分不同的消息。
  • public int arg1; :如果要传递的消息只有少量的 integer 型数据,可以使用这个属性。
  • public int arg2; :同上面 arg1
  • public Object obj; 开发者可自定义类型的传输数据。

上面四个属性作为常用的消息传递的数据载体可直接赋值,例如 msg.arg1 = 100; 。基本可以满足我们日常开发中简单消息传递。

如果上面几个数据属性不能满足我们的需求,可以使用 扩展数据:Bundle 来传递*(Bundle是啥应该都知道吧?)*:

    Bundle data;

    // 得到Bundle数据,如果data是空的就new一个
    public Bundle getData() {
        if (data == null) {
            data = new Bundle();
        }
        return data;
    }

    // 得到Bundle数据,如果data是空的就返回 null
    public Bundle peekData() {
        return data;
    }
    // 设置Bundle数据
    public void setData(Bundle data) {
        this.data = data;
    }

复制代码

这段代码也没什么逻辑好分析的,值得一提就是 Bundle data 不是 public 的,所以我们不能直接操作这个属性,需要通过上面三个方法操作数据。使用Bundle数据也非常简单:

    Bundle bundle = new Bundle();
    bundle.putString("String","value");
    bundle.putFloat("float",0.1f);
        
    Message msg = Message.obtain();
    msg.setData(bundle);
复制代码

创建与回收Message对象的基本方法

先看一下源码中Meesage的构造方法:

    /** Constructor (but the preferred way 
        to get a Message is to call {@link #obtain() Message.obtain()}).
    */
    public Message() {
    }
复制代码

没错,这货的构造方法里什么也没有,不过它的注释却告诉我们想要得到Message对象首选的方法应该是调用静态方法 Message.obtain() 。那这个 obtain() 方法干了什么呢?其实就是 内部维持了一个链表形式的Meesage对象缓存池 ,这样会节省重复实例化对象产生的开销成本。

老样子还是理论分析一波,数据结构中的链表一个单元有两个值,当前单元的值(head)和下一个单元的地址指针(next),如果下一个单元不存在那么next就是 null 的。

链表结构

所以,想要实现Message对象链表式缓存池就需要额外的两个Message类型的引用 head next ,都说了叫缓存池,所以把 head pool 更合适一点。

有了链表的基础结构我们再想实例化对象的时候就可以先去链表缓存池中看看有没有,有的话直接从缓存池中拿出来用,没有再 new 一个。

从缓存池中取对象

由于代码多起来逻辑有些复杂,这样不太好分析,所以我在源码中加了许多自己的注释,下面代码看上去很长,其实把注释都去掉后并没有多少。

    // 用于标识当前对象是否存在于缓存池,0代表不在缓存池中
    int flags;

    /** 
     * 这个常量是供上面的 flags 使用的,它表示in use(正在使用)状态
     *
     * 如果Message对象被存入了MessageQueue消息队列排队等待Looper处
     * 理或者被回收到缓存池中等待重复利用时,那么它就是in use(正在使用)状态
     * 
     * 只有在new Message()和Message.obtain()时候才可以清除掉flags上的in use状态
     *
     * 你不可以让一个in use状态的Message对象去传递消息。
     *
     *  1<< 0 还是1,真不知道为啥要这么写,直接写等于1不就得了
     */
    static final int FLAG_IN_USE = 1 << 0;

    /** 静态常量对象,通过synchronized (sPoolSync)让它作为线程并发操作时的锁
     * 确保同一时刻只有一个线程可以访问当前对象的引用
     */
    private static final Object sPoolSync = new Object();
    
    // 当前链表缓存池的入口,装载着缓存池中第一个可用的对象
    private static Message sPool;

    // 链表缓存池中指向下一个对象引用的next指针
    Message next;
    
    // 当前链表缓存池中对象的数量
    private static int sPoolSize = 0;

    /**
     * 从缓存池中拿出来一个Message对象给你
     * 可以让我们在许多情况下避免分配新对象。
     */
    public static Message obtain() {
        // 上锁,这期间只有一个线程可以执行这段代码
        synchronized (sPoolSync) {
            // pool不等于空就说明缓存池中还有可用的对象,直接取出来
            if (sPool != null) {
                // 声明一个Message引用指向缓存池中的pool对象
                Message m = sPool;
                // 让缓存池中pool引用指向它的next引用的对象
                sPool = m.next;
                // 因为该对象已经从缓存池中被取出,所以将next指针置空
                m.next = null;
                // 将从缓存池中取出的对象的flags的in use标识清除掉
                m.flags = 0; 
                // 缓存池中Message对象数量减去一个
                sPoolSize--;
                return m;
            }
        }
        // 如果缓存池中没有可用的对象就new一个吧
        return new Message();
    }

复制代码

理论上我们希望 sPool 引用指向了链表缓存池中的第一个对象,让它作为整个缓存池的出入口。所以我们把它设置成 static 的,这样它就与实例化出来的对象无关,也就是说无论我们在哪个Message对象中进行操作, sPool 还是 sPool

静态方法 obtain() 的代码逻辑流程:

先判断缓存池是不是空的: if(sPool != null) ,如果是空的就直接: return new Message(); ,不是空的就声明一个引用让它指向缓存池第一个对象: Message m = sPool; ,而缓存池的链表头部 sPool 引用就指向了链表中下一个对象: sPool = m.next; ,因为这个时候缓存池中第一个对象已经取出交给了引用 Message m ,所以需要清除掉这个对象身上的特殊标识,包括缓存池中的 next 引用和用来标记对象状态的 flags 值: m.next = null; m.flags = 0; ,最后将缓存池中的对象数量减一: sPoolSize--;

逻辑理清了整个流程就显得很简单了,再看看图解逻辑流程:

从缓存池中取出一个对象

分析到这里我们知道了为什么官方推荐我们使用 Message.obtain() 得到对象了,因为它是在缓存池中取出来重复利用的,但是通过上面代码也看可以看到,只有缓存池里有东西时也就是 sPool != null 的时候才可以取,Message是怎么把对象回收到缓存池中的呢?

回收Message对象到缓存池的方法

阅读源码后发现有一个 public void recycle() 方法用于回收Message对象,但是它也牵扯出了一堆其他方法与属性:

    // 缓存池最大存储值
    private static final int MAX_POOL_SIZE = 50;

    // 区分当前Android版本是否大于或者等于LOLLIPOP版本的全局静态变量,默认初始值为true
    private static boolean gCheckRecycle = true;

    /**
     *  用于区分当前Android版本是否大于或者等于LOLLIPOP版本
     *  内部隐藏方法,在APP启动时就会执行该方法,开发者是不可见的
     *  @hide
     */
    public static void updateCheckRecycle(int targetSdkVersion) {
        if (targetSdkVersion < Build.VERSION_CODES.LOLLIPOP) {
            gCheckRecycle = false;
        }
    }

    /**
     * 判断当前对象的flags是否为in-use状态
     */
    boolean isInUse() {
        return ((flags & FLAG_IN_USE) == FLAG_IN_USE);
    }

    /**
     * 调用这个方法后,当前对象就会被回收入缓存池中。
     * 你不能回收一个在MessageQueue排队等待处理或者正在交付给Handler处理的Message对象
     * 说白了就是in-use状态的不可回收
     */
    public void recycle() {
        // 判断当前对象是否为in-use状态
        if (isInUse()) {
            // 如果当前版本大于或者等于LOLLIPOP则抛出异常
            if (gCheckRecycle) {
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            }
            // 如果当前版本小于LOLLIPOP什么也不干直接结束方法
            return;
        }
        // 回收Message对象
        recycleUnchecked();
    }

    /**
     * 回收一个可能是in use状态的Message对象
     * 在MessageQueue和Looper内部处理排队Message时也会使用这个方法
     */
    void recycleUnchecked() {
        // 将当前Message对象置为in-use状态
        flags = FLAG_IN_USE;

        // 清除当前Message对象的所有数据属性
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;

        // 上锁
        synchronized (sPoolSync) {
            // 如果当前缓存池对象中的数量小于缓存池最大存储值(50)就存入缓存池中
            if (sPoolSize < MAX_POOL_SIZE) {
                // 存入缓存池
                next = sPool;
                sPool = this;
                // 缓存池数量加1
                sPoolSize++;
            }
        }
    }
复制代码

上面代码的逻辑很清晰,执行 recycle() 方法后先判断当前对象是否为in-use状态: if (isInUse()) ,如果是in-use状态的话当前Android版本是LOLLIPOP(5.0)版本之前直接结束程序,LOLLIPOP及之后版本抛出异常。如果当前对象不是in-use状态,那么就执行 recycleUnchecked() 方法先将它切换到in-use状态: flags = FLAG_IN_USE; ,再把所有的数据属性全部清除,最后把对象存入缓存池链表中。

回收Message对象

为什么要区分Android LOLLIPOP(5.0)前后版本?

源码刚开始就有两个用于区分Android版本的全局属性和方法:

  • private static boolean gCheckRecycle = true;
  • public static void updateCheckRecycle(int targetSdkVersion)

通过查看源码发现Message类在LOLLIPOP版本进行了一次更新也就是我们现在看到的源码,在LOLLIPOP版本之前虽然 recycle() 方法的注释上同样警告了我们不能回收in-use对象,但是如果你坚持让in-use状态的对象调用 recycle() 的话也会也会被回收:

    /**
     * Android LOLLIPOP版本源码
     *
     * Return a Message instance to the global pool.  You MUST NOT touch
     * the Message after calling this function -- it has effectively been
     * freed.
     */
    public void recycle() {
        // 清除数据
        clearForRecycle();
        // 存入缓存池
        synchronized (sPoolSync) {
            if






请到「今天看啥」查看全文