Android中Message对象复用原理
Android 中 Message的应用
Message
在Android中主要是在 消息循环机制 中使用,即配合 Handler
,Looper
和MessageQueue
来进行线程切换,线程间传递数据;
以及配合Handler
在IPC中传递数据; 这里不对这些进行展开,它不是我们关注的重点.
我们在代码中,被建议(网上或者前辈或看注释)用以下的方式来使用 Message
,并且被告知,这样会提高性能.
Message msg = Message.obtain(); /** * Return a new Message instance from the global pool. Allows us to * avoid allocating new objects in many cases. */ public static Message obtain();
嗯! 根据官方的文档,这样确实能够提高性能,将避免在大多数情况下创建新的对象!
下面我们来看它是如何实现高效的.
Message对象复用的实现
Message
能提高效率的原因是,将使用完的Message
清除附带的数据后,添加到复用池中,当我们需要使用它时,直接在复用池中取出对象使用,而不需要重新new
创建对象. 而复用池本质上就是一个单向链表,为了更好的理解,Message
复用池的实现,我们先来看下,简单的单向链表实现.
public class Linked<T> { // 链表的头节点 private Node<T> head; // 链表的长度 private int length; // 数据插入头部 public void insertHead(T data) { Node<T> n = new Node<>(); n.data = data; // 将所添加的节点的下一个节点指向头结点 n.next = head; // 将头结点指向党员元素 head = n; // 链表长度加1 length++; } // 移除头部,并取出数据 public T removeHead() { // 拿到原头结点 Node<T> n = head; // 将头结点设置为下一个节点 head = n.next; // 原头节点的下一个节点置空 n.next = null; // 链表长度减1 length--; // 返回原头结点数据 return n.data; } public int getLength() { return length; } // 节点类 static class Node<T> { T data; // 节点存放的元素 Node<T> next; // 下一个节点的引用 } }
上面实现了单向链表的插入头部,和移除头部功能.
相信大家,对这种单向链表的实现,都十分熟悉,这里不再详细讲解,如果看不懂,请自行复习数据结构相关知识点.
下面我们带着上面的知识,来看Message
中源码的实现.
// 只显示我们需要关注的代码 public final class Message implements Parcelable { ... // 下一个节点的引用 Message next; // 这里起到类锁的功能,相当于 Message.class 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对象,类比单向链表的removeHead操作 public static Message obtain() { // 同步锁, 这里相当于锁住 Message.class, 起到类锁的作用, 每一个Message实例都是同一把锁 synchronized (sPoolSync) { // 链表中有可复用的Message,直接拿到头结点,然后将头结点指向下一个元素 if (sPool != null) { Message m = sPool; sPool = m.next; m.next = null; m.flags = 0; // clear in-use flag sPoolSize--; return m; } } // 链表中,没有可复用的message,直接new return new Message(); } // 类比单向链表的insertHead操作 void recycleUnchecked() { // 清空数据操作 obj = null; ... // 同步锁 synchronized (sPoolSync) { // 如果链表长度小于50,将当前结点,加入链表头部 if (sPoolSize < MAX_POOL_SIZE) { next = sPool; sPool = this; sPoolSize++; } } } ... }
从上面的分析,我们来梳理一下整体的流程思想.
Message
使用一个静态变量,来起到头结点的作用.
静态变量属于类变量,内存中只会存在一份引用,正好能起到 头结点的作用
Message
将自身作为Node节点,存储下一个节点的引用,和自身数据obtain()
方法相当于链表中移除首元素,并返回该元素的操作
从复用池中获取Message
避免了 new创建的消耗.
recycleUnchecked()
方法相当于链表中将节点加入到头部的操作
添加到复用池之前,会先将Message
中的数据清空.
Message
添加了线程安全的操作Message
复用池最多保存50个使用完待复用的Message
50个可能是考虑到内存开销和时间开销的平衡, 不是将数据无限制的添加到复用池.
Message
将自身作为节点, 使用一个静态变量作为头结点,让Message
自身形成一个链表,而链表中存的是 已经清空数据的Message
对象, 以这种方式起到复用的效果!
疑问
从代码中可以看出, Message
中,将 private static final Object sPoolSync = new Object();
作为锁标志,来起到类锁的作用.
它能起到类锁的作用是因为,static
修饰的变量在类加载的初始化阶段就将被创建,final
使得引用不可改变,从而达到 内存独一份的效果,进而起到和类锁同样的作用.
这里有个疑问, 为何不直接使用类锁来加锁呢? 使用上诉方式,反而需要new
一个Object
对象,不是增加开销吗?
// why not ??? synchronized (Message.class) { if (sPoolSize < MAX_POOL_SIZE) { next = sPool; sPool = this; sPoolSize++; } }
有答案的同学, 欢迎在评论区中指出!
引用
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
不会用kotlin?这篇看完不懂,我跪搓衣板
最近用kotlin重写了项目中的部分模块,领略到了这个Google官方推荐语言的魅力。 正文 对比Java语言Kotlin有很多优点,如:空指针安全、支持方法扩展、lamda表达式、高阶函数、函数式编程、kotlin协程(用同步的方式写异步代码)等等。Kotlin可以100%等价替换Java代码,Android Studio 提供了一键把Java代码转义为Kotlin的能力。Kotlin可以无缝和Java代码互相调用,并且极大的提升开发效率,以改写的24小时节目单逻辑类ProgramListLogic为例,java代码行数为415行,用kotlin改写后减少到了329行,减少20.7%代码量。 一、代码优化 1、空指针安全 示例1: 示例2: 一句话搞定,?:操作符表示如果 ?: 左侧的表达式值不是null, 就会返回表达式的的值,否则, 返回右侧表达式的值。 上面只是2个比较简单的例子,从中可以看出kotlin可以极大的提升代码的简介性,让我们远离不断判空判空的繁琐,而且kotlin在编译阶段就会报出可能为空的错误,从开始就解除NullPointer风险。 2、去除findViewB...
- 下一篇
iOS 编写高质量Objective-C代码(七)—— GCD专栏
《编写高质量OC代码》已经顺利完成一二三四五六七篇!附上链接: iOS 编写高质量Objective-C代码(一)—— 简介iOS 编写高质量Objective-C代码(二)—— 面向对象iOS 编写高质量Objective-C代码(三)—— 接口和API设计iOS 编写高质量Objective-C代码(四)—— 协议与分类iOS 编写高质量Objective-C代码(五)—— 内存管理机制iOS 编写高质量Objective-C代码(六)—— block专栏iOS 编写高质量Objective-C代码(七)—— GCD专栏 本篇的主题是iOS中的 “ 大中枢开发 GCD ”。 先简单介绍一下今天的主角:GCD。 GCD(Grand Central Dispatch):一种与块相关的技术,提供了对线程的抽象管理(基于派发队列dispatch queue)。GCD会根据系统资源情况,适时且高效地 “创建线程” 、“复用线程” 、 “销毁线程”。 一、多用派发队列,少用同步锁 问:在iOS开发中,如何通过锁来提供同步机制?(以前面试中,经常问道的问题..) 答:在GCD出现之前,有两种方式...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7,CentOS8安装Elasticsearch6.8.6
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- Red5直播服务器,属于Java语言的直播服务器
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Linux系统CentOS6、CentOS7手动修改IP地址
- CentOS8编译安装MySQL8.0.19
- CentOS关闭SELinux安全模块
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作