性能优化

最近在做一个相机的的APP,其中涉及到了很多以前没有接触过的知识,APP做到现在功能也都基本完成了,现在有心思把精力放在一些以前比较奇怪的代码上。

内存优化

在目前的实现中,相机的预览页采用了open GL 的方式来展示摄像头采集的数据,但是在根据人脸识别后的添加的sticker上还是采用了Bitmap和 canvas 实现的方式,看到了bitmap就知道内存抖动这块一定是跟它有关了

  1. 问题一:sticker 资源的加载
    在优化前实现中,采用的是通过glide同步的获取bitmap对象,同时通过一个队列来缓存加载的图片,但是这样面对的一个问题是在一个动态的sticker中,如果出现的sticker过多,那么很可能造成这个队列不够用,那么实际上造成的效果是bitmap对象根本没有办法复用。例如实际上这个队列的最大容量是 10, 但是我们有40张图片需要加载的时候,这个队列根本起不到缓存的作用,并且在sticker较大的时候还是比较大的OOM 风险。

1.1 减少bitmap.
首先切换了思路,我们并没有采用队列的方式来缓存所有的图片,而是针对每一个类型的sticker资源都只缓存2个bitmap,这样就内存里面关于sticker的bitmap数量最大为10个(有些类型的sticker只有一个图片资源),这样大大的减少了sticker的内存占用,在这种情况下,也基本上不会出现OOM 的风险,虽然这个时候可能会出现sticker加载慢导致的sticker掉帧情况,但是实际上这个对视觉上的影响并不大,同时加载本地资源的时候,sticker加载的速度还是很快的,并且因为在子线程执行所以这个优化方法是可以采纳的。

1.2 使用option.inBitmap 和 option.inMutable

option.inBitmap 是ANDROID 11 之后推出的属性,通过这个属性在加载bitmap时候可以复用inBItmap 所指向对象的内存而不用重新开辟内存的占用,这个属性在不同的sdk基本上有不同的限制要求,使用的时候需要注意,因为我们的同类型的sticker序列的图片大小都是相同的,所以在使用的时候不用担心这个限制问题。

option.inMutable 通过这个属性我们可以让每次返回的bitmap都是之前的同一个对象

这里引用一下一篇博客的内容

inBitmap
Android在API11添加的属性,用于重用已有的Bitmap,这样可以减少内存的分配与回收,提高性能。但是使用该属性存在很多限制: 在API19及以上,存在两个限制条件:
被复用的Bitmap必须是Mutable。违反此限制,不会抛出异常,且会返回新申请内存的Bitmap。
被复用的Bitmap的内存大小(通过Bitmap.getAllocationByteCount方法获得,API19及以上才有)必须大于等于被加载的Bitmap的内存大小。违反此限制,将会导致复用失败,抛出异常IllegalArgumentException(Problem decoding into existing bitmap)
在API11 ~ API19之间,还存在额外的限制:
被复用的Bitmap的宽高必须等于被加载的Bitmap的原始宽高。(注意这里是指原始宽高,即没进行缩放之前的宽高)
被加载Bitmap的Options.inSampleSize必须明确指定为1。
被加载Bitmap的Options.inPreferredConfig字段设置无效,因为会被被复用的Bitmap的inPreferredConfig值所覆盖(不然,所占内存可能就不一样了)

1.3 使用bitmap pool

因为在最终的绘制到canvas的时候使用的是一个bitmap,在优化前每次都重新创建了一个bitmap对象,实际上这是没有必要的,在使用完了bitmap之后可以通过bitmap pool 缓存起来,然后再次使用,在再次使用之前通过btimap.erase() 方法来擦除之前的数据信息即可,这样就避免了每次都重复创建新的bitmap对象。

1.4 正确的使用Glide.

这一点在Glide相关的文档中应该有正确的说明,避免将Bitmap与不正确的生命周期对象绑定,从而导致在相应的绑定对象被回收之后而bitmap无法回收。

1.5 合理优化图片大小

在使用Glide加载大图的时候,可以重写加载目标的高宽避免加载过大的图片,在项目中使用到了高斯模糊处理的图片作为背景图,如果不作处理的话,直接加载一个屏幕高宽的大图会消耗极大的内存,通过缩小高斯模糊背景处理之后图片的高宽可以极大的优化内存的占用

锁优化

在多线程的环境下锁的使用的确是避不开的,但是在一定条件下,是可以避免锁的使用的。例如在这次开发的APP中,摄像头的数据通过回调的方式传递过来(主线程),同时通过人脸识别的子线程对这个数据进行人脸识别的数据的处理。因为摄像头数据存放在一个类的成员变量中,主线程会进行写操作,而子线程会进行读写两个操作,在优化前两个线程操作这个数据都加上了锁。但是实际上分析一下会发现加锁的情况是可以避免的。锁的作用就是让两个操作在多线程的情况下不会交叉进行,既然是多线程的情况,我们把这个情况变成单线程的即可,在单线程的情况下就不会有这个问题了。

所以优化操作中,直接把子线程读写操作放到了主线程当中,这样数据的操作就避免加锁的情况。

通过对比前后的操作,一个方法的平均执行时间从32ms降低到了 0.4ms,优化有极大的提升。

##滑动优化

  1. 优化每一个列表项的布局结构,尽量减小布局中View的数量,在最近的一次优化中,通过减少TextView的数量来减少了布局的耗时。
  2. 避免使用View的alpha属性,需要使用透明色的时候,通过颜色的透明来实现而不是直接使用alpha.