编程进阶网编程进阶网
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 1.1App启动流程梳理
  • 1.2ActivityThread分析
  • 1.3Context设计思想
  • 2.1Activity基础介绍
  • 2.2Activity启动流程
  • 2.3Activity布局创建
  • 2.4Activity布局绘制
  • 2.5Service基础介绍
  • 2.6Service启动流程
  • 2.7Receiver广播基础
  • 2.8Receiver深入原理
  • 2.9ContentProvider分析
  • 2.10Fragment实践
  • 2.11Intent深入思考
  • 3.1Paint和Canvas
  • 3.2View的绘制基础
  • 3.3onMeasure流程设计
  • 3.4onLayout流程设计
  • 3.5onDraw流程设计
  • 3.6View工作原理
  • 3.7View刷新设计流程
  • 3.8自定义View控件
  • 3.9自定义ViewGroup控件
  • 4.1Handler基础使用
  • 4.2消息机制流程分析
  • 4.3Handler深度解析
  • 4.4Message深度理解
  • 4.5MessageQueue解析
  • 4.6Looper深度解析
  • 4.7理解Handler同步屏障
  • 4.8ThreadLocal分析
  • 4.9ThreadLocal场景
  • 5.1View事件设计思考
  • 5.2View滑动冲突处理
  • 5.3View事件源码分析
  • 5.4View事件总结案例
  • 6.1DecorView设计思想
  • 6.2视图的载体View
  • 6.3视图管理者Window
  • 6.4窗口管理服务WMS
  • 6.5布局解析者Inflater
  • 7.1AsyncTask深入介绍
  • 7.2HandlerThread设计
  • 7.3IntentService设计
  • 8.1IPC通信方式介绍
  • 8.2AIDL进程间通信
  • 8.7Binder通信机制设计
  • /zh/android/basic/9.1注解设计思想和原理.html
  • 9.2APT技术设计详解
  • 9.3APT多种案例实践

4.4Message深度理解

目录介绍

  • 01.先来了解下Message
  • 02.设计Message考虑点
  • 03.Message是做什么用
  • 04.Message如何创建
  • 05.看一下几个成员字段
  • 06.Message.obtain()分析
  • 07.Message的命令模式

01.先来了解下Message

Android应用是事件驱动的

也可以说是Message来驱动的。每个线程中都有一个默认的消息队列MessageQueue,其维护了一个待处理的消息列表,Looper不断地从中取出消息、处理消息。

如何处理Message创建和销毁

此时我们不禁会抱有一个疑问,在应用运行期间,系统岂不是会不断地创建Message、处理Message、销毁Message?

答案当然是否定的,Android作为一个成熟的系统平台,自然不会轻易地采用大量重复构建这种低效耗能的运作方式。本篇文章我们就来了解一下Message。

02.设计Message考虑点

先来了解下Message,如果是你,你会如何设计消息体。那么考虑点有那些?

  1. 消息体需要能附带传递的内容【可以设计属性object】,发送消息和接受消息处理需要通过code来判断【可以设计属性what】,延迟消息需要附带时间【可以设计属性when】
  2. 使用handler发送,在处理消息的时候需要知道是那个handler发送的【可以设计属性target】,通过回调处理消息【可以设计属性runnable】
  3. 当开发者使用handler发送大量消息,系统可以优先处理系统UI级别消息,需要区分同步还是异步消息【可以设计属性flags】

最后,消息使用频率高,而且有时候会很多,比如1000个消息,我肯定希望消息体创建能否做到复用【避免高频创建和销毁】?

那么如何做,这种场景明显就是,如果存在,则重用该对象,而不是创建一个新的对象。如果不存在,则创建一个新的对象并将其添加到共享池中,以供以后使用。利用享元模式!

03.Message是做什么用

Message的作用

大概意思是,Message类被用于发送给Handler,其可以存储一些描述信息和任意数据类型的对象。

Message有两个额外的int字段和一个额外object字段,可以满足很多情况下的需求配置。

虽然Message的构造方法是public的,但是创建Message最好的方法还是去调用Message.obtain()方法或者调用一系列Handler.obtainMessage()的方法,这些方法会从一个回收池中去获取Message对象。

复用对象池

  1. Android的消息机制并不会大量重复创建Message对象,而是重复利用在消息池中已存在的Message对象。
  2. 从Handler类源码中我们可以看到,Handler中所有需要构建Message对象的地方,包括一系列Handler.obtainMessage()的方法,其实质上都是通过Message.obtain来获得的。

04.Message如何创建

创建Message对象的几种方式:

  1. Message msg = new Message();
  2. Message msg = Message.obtain();
  3. Message msg = handler1.obtainMessage();

几种方法对比

后两种方法都是从整个Message池中返回一个新的Message实例,能有效避免重复Message创建对象,因此更鼓励这种方式创建Message

Message核心字段含义

m.what = 定义常量,用来发送消息和匹配消息的数值;
m.target = 表示这个消息是从那个handler发出来的
m.arg1 = orig.arg1;
m.arg2 = orig.arg2;
m.obj = 赋值的对象;
m.replyTo = orig.replyTo;
m.sendingUid = orig.sendingUid;

05.看一下几个成员字段

代码如下所示

// sometimes we store linked lists of these things
/*package*/ Message next;
private static final Object sPoolSync = new Object();
private static Message sPool;
private static int sPoolSize = 0;
private static final int MAX_POOL_SIZE = 50;

先来看看Message类中几个重要的字段

  1. next字段的注释我们可以发现Message的消息池实质上就是个链表,而这个next指向下一个Message对象;
  2. sPoolSync是一个普通Object对象,用于在获取Message对象时进行同步锁;
  3. mPool字段就是一个Message对象,mPool是链表中的第一个Message对象;
  4. sPoolSize字段表示的是消息池已有Message的数量;MAX_POOL_SIZE表示消息池的最大容纳数量。

06.Message.obtain()分析

看看Message.obtain()方法

如果消息链中没有消息时,就会new一个新的Message对象并返回;如果消息链中有可用对象,就会获取到当前链表的mPool,而后将mPool指向下一个Message对象。

/**
 * Return a new Message instance from the global pool. Allows us to
 * avoid allocating new objects in many cases.
 */
public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}

当链表中有可用对象时,obtain方法是从链表中获取Message对象,但是没有对象时,obtain方法中创建的Message又是怎么添加到消息池中的呢?说到这里,不知大家还记不记得在Looper.loop方法中,每一条Message被处理后最终都会调用Message.recycleUnchecked()方法,没错,答案就在这个方法里,我们来看看Message.recycleUnchecked()方法的源码。

/**
 * Recycles a Message that may be in-use.
 * Used internally by the MessageQueue and Looper when disposing of queued Messages.
 */
void recycleUnchecked() {
    // Mark the message as in use while it remains in the recycled object pool.
    // Clear out all other details.
    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) {
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}
  • 从代码中可以看出
    • recycleUnchecked()方法有两个作用:首先,清空消息状态,同时设置flags = FLAG_IN_USE,表明该消息已被使用,这个flags在obtain()方法中会被设置为0;
    • 然后,如果消息池的大小未超过最大容纳数量,就将自身添加到链表的表头。也就是说,Message并不是在创建时而是在回收时被添加到消息池中的。

05.Message的命令模式

  • Android消息机制实质上是一个非典型的命令模式,所以这里简单说一下命令模式。
    • 定义:将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。
  • 命令模式中主要有三种角色:
    • Receiver:接收者角色,命令的最终执行者。
    • Command:命令角色,需要执行的所有命令都是在这里声明的。
    • Invoker:调用者角色,接受命令并执行命令。
  • 命令模式的优点:
    • 类间解耦,将调用者角色与接收者角色实现解耦,调用者实现功能时只需调用Command抽象类的execute方法就可以,不需要了解到底是哪个接收者执行;可扩展性好,Command的子类可以非常容易地扩展,而调用者Invoker和高层次的模块Client不产生严重的代码耦合。
  • 命令模式的缺点:
    • 命令模式主要的缺点就是Command类膨胀的问题,如果有N个命令,Command的子类不是几个而是N个。
  • 使用场景:
    • 需要抽象出待执行的动作,然后以参数的形式提供出来——类似于过程设计中的回调机制,而命令模式正是回调机制的一个面向对象的替代品。
    • 在不同的时刻指定、排列和执行请求。一个命令对象可以有与初始请求无关的生存期。
    • 需要支持取消操作。
    • 支持修改日志功能,这样当系统崩溃时,这些修改可以被重做一遍。
    • 需要支持事务操作。
  • 回到Android消息机制,上边说到Android消息机制是一个非典型的命令模式,其分工如下:
    • 接收者:Handler,执行消息处理操作;
    • 调用者:Looper,调用消息的的处理方法;
    • 命令角色:Message,消息类。
  • 为什么Android消息机制为什么会使用命令模式了。
    • 不难发现,Android消息机制的接收者和调用者之间是存在依赖的,似乎与命令模式相违和,但仔细一想倒也无可厚非,设计模式的存在意义就是为了使代码的更易于维护和扩展。
    • 之前说过接收者和调用者实现解耦,其最大的好处就是让未知对象之间的调用关系更加灵活,而Android消息机制中的接收者、调用者和命令角色三者角色固定而明确,自然也不必需要这个扩展性,命令角色的单一还解决了命令模式的类膨胀问题。
贡献者: yangchong211
上一篇
4.3Handler深度解析
下一篇
4.5MessageQueue解析