Android性能优化建议

最近看了一本腾讯测试同学写的书 :《Android移动性能实战》,书中介绍了很多如何检测Android性能问题的工具比如磁盘、内存、CPU、电池等。不过也指出了一些在开发过程中需要注意的点:

磁盘

SharedPreferences

我们知道SharedPreferences底层是使用xml文件来实现的。所以对于SharedPreferences的操作其实是I/O操作,是耗时操作。

commit

每一次commit的调用都会对应一次文件的打开和关闭。commit是同步操作,apply是异步操作。

最佳实践

减少commit的次数; 在一个逻辑操作(方法)中不要多次commitcommit应放在最后。或者使用缓存来保存多次写入的数据,最后提交commit

如果对数据的实时性没有要求,可以使用apply来代替commit

flag的保存与使用

我们通常会使用SharedPreferences来保存一些flag。但是要注意不要随意使用。比如你在一个Recycleview的卡片中,根据一个flag来做不同UI的判断:

class SimpleCard : LinearLayout, AdapterItem{
    fun bindData(...){
        if(Sp.getBoolean(xxx)){
            ...
        }    
    }
}

因为SimpleCardRecycleview中频繁被bindData, 因此SP.getBoolean(xxx)会被频繁调用,前面已经说了SP的读或写是io操作。这种写法势必会造成UI卡顿。

最佳实践

flag一般来说在app启动的时候就已经确定了,所有我们只需要获取一次。可以提前初始化这些flag:

//比如在某个Activity的onCreate

    runOnIoThread({
        FlagCenter.initAllFlag()
    })

//SimpleCard.bindData

 fun bindData(){
        if(FlagCenter.useStyle1){
            ...
        }
    }

ObjectOutputStream

利用它我们可以把对象保存到磁盘中。不过它有一个特点:在保存对象的时候,每个数据成员会带来一次I/O操作。因此如果你对象很多、属性很复杂时,ObjectOutputStreamI/O操作会异常凶猛。

最佳实践

最好在ObjectOutputStream上再封装一个输出流,比如BufferedOutputStream。先把对象写入到这个流中,然后再使用ObjectOutputStream保存到磁盘。

合理设置buffer

在读一个文件我们一般会设置一个buffer。即先把文件读到buffer中,然后再读取buffer的数据。所以: 真正对文件的次数 = 文件大小 / buffer大小 。 所以如果你的buffer比较小的话,那么读取文件的次数会非常多。当然在写文件时buffer是一样道理的。

很多同学会喜欢设置1KB的buffer,比如byte buffer[] = new byte[1024]。如果要读取的文件有20KB, 那么根据这个buffer的大小,这个文件要被读取20次才能读完。

最佳实践 -> buffer应该设置多大呢?

java默认的buffer为8KB,最少应该为4KB。那么如何更智能的确定buffer大小呢?

  1. buffer的大小不能大于文件的大小。
  2. buffer的大小可以根据文件保存所挂载的目录的block size, 什么意思呢? 来看一下SQLiteGlobal.java是如何确定buffer大小的 :
public static int getDefaultPageSize() { 
    return SystemProperties.getInt("debug.sqlite.pagesize", new StatFs("/data").getBlockSize());
}

Bitmap的解码

在Android4.4以上的系统上,对于Bitmap的解码,decodeStream()�的效率要高于decodeFile()decodeResource(), 而且高的不是一点。所以解码Bitmap要使用decodeStream(),同时传给decodeStream()的文件流是BufferedInputStream

最佳实践

val bis =  BufferedInputStream(FileInputStream(filePath))
val bitmap = BitmapFactory.decodeStream(bis,null,ops)

内存

在虚拟机的Heap内存使用超过堆内存最大值就会发生OOM。当手机内存低于内存警戒线时,占用内存越多的APP越有可能被Low Memory Killer给杀掉。

内存泄漏

Activit内存泄漏

Activity对象会间接或者直接引用View、Bitmap等,所以一旦无法释放,会占用大量内存。并且Activity在Destroy的情况下,更新UI是会引发crash的。而Activity内存泄漏最常见的就以下几种case:

  1. 生命周期比Activtiy长的对象持有了Activity的引用。比如在getSystemService时使用了Activity

  2. Activity的内部类作为一个异步的回调监听。比如定义了一个Handler内部类,这时候Handler默认就会引用Activity

最佳实践

在使用getSystemService方法时尽量使用Application, 比如: applicationContext.getSystemService(Context.INPUT_METHOD_SERVICE)

Activity中的Handler定义为静态内部类(解除对Activity的引用),并使用WeakReference<Activity>来引用Activity。

图片

Feed流中的图片如果可以应尽可能降低所占内存大小

对于要加载的图片应做压缩,并在适当的情况下可以在解码时降低质量

最佳实践

对于无透明效果、比较小的图片可以使用RGB_565格式来解码。

在设置解码图片的inSmapleSize参数时应参考要显示的View的宽与高来显示恰当的值。

图片资源不要放在错误的目录

Android的drawable分为好几个层级,比如:drawable、drawable-hdpi、drawable-ldpi、drawable-mdpi等,其实就是对应不同的屏幕密度。Android在获取某个屏幕密度的图片时会去对应的drawable目录下寻找,如果找不到就会取相近目录的资源。但这里是存在一个问题的,举个例子:

比如一张 800 * 480 的图片放置在了ldpi目录。如果480dpi(xxxhdpi)的屏幕要显示这张图片,那么他就会把这张图片放大4倍,即 3200 *1920,然后再去解码。所以图片在内存中被放大了4倍。如果这种case多了,内存很容易爆掉的。

最佳实践

尽量问设计师要高品质图片然后放在高密度目录下,这样在低密度上放大倍数是小于1的,在保证画质的前提下,内存也是可控的。

SparseArray与ArrayMap

SparseArray也是一个map,它的key必须为int类型, 在数据量不大的情况下(千级以内),它的性能会要比HashMap好,并且更升内存。

SparseArray与ArrayMap的key可以为任意类型,当数据量比较小时也可以使用它来代替HashMap

欢迎关注我的Android进阶计划看更多干货

    原文作者:susion哒哒
    原文地址: https://www.jianshu.com/p/2d06e31a1fa9
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞