Android 内存优化&实践

作者: echoMuJS | 来源:发表于2018-03-21 16:17 被阅读119次

    这是一篇关于Android 内存优化文章的摘要,原文见Android 内存优化总结&实践

    一、Android常见内存问题和对应检测,解决方式

    1. 内存泄露

    可以说大部分的内存问题都是内存泄露导致的,Android里也有一些很常见的内存泄露问题:

    单例:
    主要原因还是因为一般情况下单例都是全局的,有时候会引用一些实际生命周期比较短的变量,导致其无法释放;

    静态变量:
    同样也是因为生命周期比较长

    Handler内存泄露:
    匿名内部类(匿名内部类会引用外部类,导致无法释放,比如各种回调)

    资源使用完未关闭:
    BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap

    2. 图片分辨率相关

    分辨率适配问题。很多情况下图片所占的内存在整个App内存占用中会占大部分。我们知道可以通过将图片放到hdpi/xhdpi/xxhdpi等不同文件夹进行适配,通过xml android:background设置背景图片,或者通过BitmapFactory.decodeResource()方法,图片实际上默认情况下是会进行缩放的。在Java层实际调用的函数都是或者通过BitmapFactory里的decodeResourceStream函数。

    decodeResource在解析时会对Bitmap根据当前设备屏幕像素密度densityDpi的值进行缩放适配操作,使得解析出来的Bitmap与当前设备的分辨率匹配,达到一个最佳的显示效果,并且Bitmap的大小将比原始的大。

    尽管现在已经有比较先进的图片加载组件类似Glide,Facebook Freso, 或者老牌Universal-Image-Loader,但是有时就是需要手动拿到一个bitmap或者drawable,特别是在一些可能会频繁调用的场景(比如ListView的getView),怎样尽可能对bitmap进行复用呢?这里首先需要明确的是对同样的图片,要尽可能复用,我们可以简单自己用WeakReference做一个bitmap缓存池,也可以用类似图片加载库写一个通用的bitmap缓存池

    3. 图片压缩

    BitmapFactory 在解码图片时,可以带一个Options,有一些比较有用的功能,比如:

    inTargetDensity 表示要被画出来时的目标像素密度

    inSampleSize 这个值是一个int,当它小于1的时候,将会被当做1处理,如果大于1,那么就会按照比例(1 / inSampleSize)缩小bitmap的宽和高、降低分辨率,大于1时这个值将会被处置为2的倍数。例如,width=100,height=100,inSampleSize=2,那么就会将bitmap处理为,width=50,height=50,宽高降为1 / 2,像素数降为1 / 4

    inJustDecodeBounds 字面意思就可以理解就是只解析图片的边界,有时如果只是为了获取图片的大小就可以用这个,而不必直接加载整张图片。

    inPreferredConfig 默认会使用ARGB_8888,在这个模式下一个像素点将会占用4个byte,而对一些没有透明度要求或者图片质量要求不高的图片,可以使用RGB_565,一个像素只会占用2个byte,一下可以省下50%内存。

    inPurgeable和inInputShareable 这两个需要一起使用,BitmapFactory.java的源码里面有注释,大致意思是表示在系统内存不足时是否可以回收这个bitmap,有点类似软引用,但是实际在5.0以后这两个属性已经被忽略,因为系统认为回收后再解码实际会反而可能导致性能问题

    inBitmap 官方推荐使用的参数,表示重复利用图片内存,减少内存分配,在4.4以前只有相同大小的图片内存区域可以复用,4.4以后只要原有的图片比将要解码的图片大既可以复用了。

    4. 缓存池大小

    现在很多图片加载组件都不仅仅是使用软引用或者弱引用了,实际上类似Glide 默认使用的事LruCache,因为软引用 弱引用都比较难以控制,使用LruCache可以实现比较精细的控制,而默认缓存池设置太大了会导致浪费内存,设置小了又会导致图片经常被回收,所以需要根据每个App的情况,以及设备的分辨率,内存计算出一个比较合理的初始值,可以参考Glide的做法。

    5. 内存抖动

    什么是内存抖动呢?Android里内存抖动是指内存频繁地分配和回收,而频繁的gc会导致卡顿,严重时还会导致OOM。

    一个很经典的案例是string拼接创建大量小的对象(比如在一些频繁调用的地方打字符串拼接的log的时候)。

    而内存抖动为什么会引起OOM呢?

    主要原因还是有因为大量小的对象频繁创建,导致内存碎片,从而当需要分配内存时,虽然总体上还是有剩余内存可分配,而由于这些内存不连续,导致无法分配,系统直接就返回OOM了。

    其他

    枚举,Android平台上枚举是比较争议的,目前Android官方建议,使用枚举变量还是需要谨慎,因为枚举变量可能比直接用int多使用2倍的内存。

    ListView复用,这个大家都知道,getView里尽量复用conertView,同时因为getView会频繁调用,要避免频繁地生成对象

    谨慎使用多进程,现在很多App都不是单进程,为了保活,或者提高稳定性都会进行一些进程拆分,而实际上即使是空进程也会占用内存(1M左右),对于使用完的进程,服务都要及时进行回收。

    尽量使用系统资源,系统组件,图片甚至控件的id

    减少view的层级,对于可以延迟初始化的页面,使用viewstub

    数据相关:序列化数据使用protobuf可以比xml省30%内存;慎用shareprefercnce,因为对于同一个sp,会将整个xml文件载入内存,有时候为了读一个配置,就会将几百k的数据读进内存;数据库字段尽量精简,只读取所需字段。

    dex优化,代码优化,谨慎使用外部库:多dex也是常态,不仅占用rom空间,实际上运行的时候需要加载dex也是会占用内存的(几M);有时候为了使用一些库里的某个功能函数就引入了整个庞大的库,此时可以考虑抽取必要部分;开启proguard优化代码,使用Facebook redex使用优化dex(好像有不少坑)。

    三、工具

    通过各种内存泄露检测组件,如LeakCanary,解决大部分内存泄漏;
    通过MAT查看内存占用,优化占用内存较大的地方;Memory Monitor跟踪整个App的内存变化情况, Heap Viewer查看当前内存快照, Allocation Tracker追踪内存对象的来源;
    利用崩溃上报平台从多个方面对App内存进行监控和优化。

    参考资料:

    1. Android Studio3.0 Android Profiler分析器
    2. Android Developer:Heap Viewer演示
    3. Android性能专项测试之Allocation Tracker(Device Monitor)
    4. Android性能全面分析与优化方案研究—几乎是史上最全最实用的

    相关文章

      网友评论

        本文标题:Android 内存优化&实践

        本文链接:https://www.haomeiwen.com/subject/oobmaxtx.html