Android 性能优化之内存抖动
在这里先说一下在android 系统中使用最广泛的防止内存抖动的一个机制 Message
在android 系统中 Message 是使用最频繁的一个类之一了,整个系统的运行都是建立在 Message 只上的,那么为了防止频繁的创建和销毁对象,Message 又是使用的什么方式来防止内存抖动的呢
1.对象缓存池
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();
}
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 = UID_NONE;
workSourceUid = UID_NONE;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
Message 内部有一个叫做 是Message 的next 属性,用来记录下一个Message 内容,还有一个 sPool 的静态变量,用来保存当前缓存的头结点,那么这就形成了一个由无用消息组成的单链表
由于是无用消息,我们不关心这个消息的内容,所以非常适合现在这种场景
适用场景
在循环中创建重复的对象 , 在onDraw 中组装绘制对象 , ,生产消费模式也比较使用这种情况
下面我们句一个小例子来看一下profiler 中创建了多少个对象
class MainActivity : AppCompatActivity() {
private var isRunning=false
private val queue: LinkedBlockingQueue<TsmMessage> = LinkedBlockingQueue<TsmMessage>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<View>(R.id.tv_click).setOnClickListener {
isRunning=true
test()
}
findViewById<View>(R.id.tv_end).setOnClickListener {
isRunning=false
}
}
fun test(){
thread {
while (isRunning){
kotlin.runCatching {
queue.add(TsmMessage())
sleep(2)
}
}
}
thread {
while (isRunning){
kotlin.runCatching {
queue.take()
sleep(2)
}
}
}
}
}
在这4秒多的时间里面我们一共创建了9872 个 TsmMessage 的对象
点击一个TsmMessage 可以看到 这个对象的创建和运行的位置,kotlin 有点不准确,但是足够用了
再来看一下我们优化后的效果
从这里我们可以看到 在这个时间段里面,他只创建了12个对象,大大减少了我们创建对象的个数
在项目中比较常见的 String+="abc"
相信绝大多数人在看了这段代码都知道 string + "abc" 都知道jvm 会为我们创建重复的对象 ,如果这句话写在了 循环中 也会造成内存抖动
对于数组 byte[] 数组的回收 Glide Array数据回收机制 LruArrayPool
这个类的路径是 package com.bumptech.glide.load.engine.bitmap_recycle; LruArrayPool
先来说一下他这个类的思想
先来说一下他的put 方法,比较简单
用这个数据的lenght 作为key 放入到map 中, 之后将这个lenght 的数据个数放入到treemap 中,整体的逻辑就是这样的
再来说一下他的get 方法,思想特别神奇,
入参是 length ,他的意思是我要获取一个length 的Array 数据,他会先去treemap 中获取一个等于或者大于length 最接近这个长度的数组key,再根据这个key去map中拿到一个链表中的数据,最后将treemap 的length 的数据个数-1,整个过程完成
他的非常重要的思想就是你想获取一个长度为length 的 array ,我返回给你一个大于或者等于这个length 的array ,满足你的需求
对于数组的回收 Glide Array数据回收机制 LruArrayPool 弊端
再来说一下这个方法的弊端
private final Map<Class<?>, NavigableMap<Integer, Integer>> sortedSizes = new HashMap<>();
弄了一个简易的测试方法发现,在使用Interger 做了Map 的key ,但是我们实际传入的是int 类型,在get 与 put 的过程中 jvm 就会自动实现 对象拆箱装箱的这个操作,也创建了非常多的对象,那么如何去优化他呢, int 作为key 的 Array ,没错 就是SparseArray 的思想,