美文网首页
Android内存优化案例分析

Android内存优化案例分析

作者: 阳生植物人 | 来源:发表于2022-01-04 10:39 被阅读0次

    前言:

    术语

    开机内存:手机连接Wifi热点、插入注册网络的SIM卡,重启后、静置5分钟,采集的进程内存值;
    常驻内存:业务进程工作任务结束退至后台、静置5分钟,采集的进程内存值;
    动态内存:业务进程在后台运行工作任务时,采集的进程内存峰值;
    场景内存:用户使用典型业务场景时,相关依赖服务进程在前台、后台运行的内存总和;
    驻留比率:用于标识进程在后台的常驻概率,驻留比率=“B Serveices优先级及以上采样命中数” /“总样本数”。

    常用分析工具

    Android Studio Profile

    Android Studio 3.0 及更高版本中的 Android Profiler 取代了 Android Monitor 工具。Android Profiler 工具可提供实时数据,帮助您了解应用的 CPU、内存、网络和电池资源使用情况。该工具大家用得比较多,这里就不过多赘述。

    传送门:https://developer.android.google.cn/studio/profile/android-profiler

    Memory Analyzer工具

    MAT 是一个快速,功能丰富的 Java Heap 分析工具,通过分析 Java 进程的内存快照 HPROF 分析,从众多的对象中分析,快速计算出在内存中对象占用的大小,查看哪些对象不能被垃圾收集器回收,并可以通过视图直观地查看可能造成这种结果的对象。
    adb或Android studio Profile抓取的heap文件,需要使用(AndroidSdk\platform-tools\hprof-conv.exe)转换之后才能打开
    传送门:https://www.eclipse.org/mat/

    Perfetto

    可以分析内存产生过程的方法栈,区别于hprof文件,heap文件时某个时刻的内存。而perfetto作用的是过程内存。一般用于分析可以复现内存问题的场景,这里不过多赘述。
    传送门:https://ui.perfetto.dev/#!/record?p=instructions

    Jadx-gui

    jadx是个人首选的反编译利器,同时支持命令行和图形界面,能以最简便的方式完成apk的反编译操作。
    下载地址

    常用adb命令

    adb shell dumpsys meminfo <package>

    查看进程内存分布信息,如:adb shell dumpsys meminfo com.demo
    注意该方式会产生一次gc

    adb shell "dumpsys meminfo | grep pcakgename"

    查看某个进程内存总值,该方式不会触发gc

    adb shell showmap <pid>
    adb pull proc/<pid>/smaps

    code,system的内存分布,可以用于分析代码量内存分布,包括系统的类

    adb shell am dumpheap <pid>

    dump javaHeap部分的对象实例,可以借助Android Profile,MAT打开hprof文件分析
    Android Profile会显示所有的对象实例,包括可以待gc回收的对象,而且方便清除知道对象的变量,方便定义是什么业务产生的
    MAT只会显示不会被gc回收的对象,可以查看GC链,但是可以对比两个hprof文件差异性

    adb shell am dumpheap -n <pid>

    dump native的对象,然后根据编译Rom的产物之一:带有符号信息so文件(默认在$ANDROID_PRODUCT_OUT/symbols目录下),如果没有可以从root的手机里获取(system/lib64,vendor/lib64),使用native_heapdump_viewer.py解析,然后前后对比,就可以知道是哪些方法导致的内存增长。
    Linux命令:python native_heapdump_viewer.py --html --symbols /symbols/ heap.txt > heap_info.tx
    注意需要在Linux服务器上执行,要不然无法全部解析所有的方法栈。或者使用python工程解析也可以
    传送门:
    native_heapdump_viewer.py

    案例分析:

    一、常驻内存优化

    根据dumpsys meminfo查看内存分布后,根据不同内存分布,做相应的优化

    如下为应用优化前的内存分布:

     App Summary

                           Pss(KB)                        Rss(KB)

                            ------                         ------

               Java Heap:     4488                          32216

             Native Heap:     7016                          12576

                    Code:    16596                          62912

                   Stack:     2272                           2288

                Graphics:        0                              0

           Private Other:     3416

                  System:     1964

                 Unknown:                                    7880

               TOTAL PSS:    35752            TOTAL RSS:   117872      TOTAL SWAP (KB):        0

    数据分析初步结论:发现code部分占大头,native内存也偏高

    Java Heap:

    通过adb shell am dumpheap或profile工具抓取javaHeap文件 重点关注以下几个点(要很细心,一个个查看,不要错过任何怀疑的可能性)

    1、对象个数(Allocations值)高的对象
    2、对象内存占用值(Shallow Size),查看代码确认该对象是否有必要常驻
    3、相关联的内存值(Retainaed Size)
    4、预期结果是只有单个实例的,是否出现了多个实例
    5、常见的内存大对象,比如:Thread,HandlerThread等

    通过javaHeap文件可知,

    1、其中一个内存大块为数据库相关,应用由5个db数据库,
    只能做到延迟加载,使用到对应的数据库之后才进行加载,并且减少数据库执行语句的缓存数,
    SQLiteDatabase.setMaxSqlCacheSize()
    另外,我也尝试过,写一个有效期的Map,长时间不使用主动关闭数据库,并且释放缓存。发现没法完全释放掉,会残留一些用于同步的ThreadLocal对象,如果频繁的创建,连接、关闭数据库,就会累计很多无用的对象,导致内存泄漏,所有我放弃了该方式

    2、Thread、HandlerThread的优化

    重点:自定义线程,自定义线程池,一定要复写自己的线程名,方便定位问题,要不然dump内存时或者看日志时,都不知道该线程是由哪个业务创建的

    线程的创建,也会带来Stack内存

    修改措施:
    1)、使用线程池,防止频繁创建线程,产生临时内存,
    2)、去掉没有必要的HandlerThread,使用公共HandlerThread或者线程池替代
    3)、未及时关闭的Closeable对象

    可以配置
    StrictMode.VmPolicy vmPolicy = new StrictMode.VmPolicy.Builder().detectActivityLeaks()
                .detectLeakedClosableObjects()
                .detectLeakedSqlLiteObjects()
                .penaltyLog()
                .build();
            StrictMode.setVmPolicy(vmPolicy);

    如果没有及时关闭,日志里会打印相关的堆栈打印
    如:W/System: A resource failed to call close,

    Native Heap:

    方式一、通过perfetto,和Android Profile的抓取,查看执行过程

    主要如下几个方面:

    1)、数据操作相关的,而且还发现不仅打开数据时会产生内存,执行sqlite语句,也会产生内存,目前还没分析出原因,使用的是加密的数据库,没找到对应的源码,只能暂时放弃了,知道的小伙伴欢迎留言
    2)、另外一块就是网络安全请求相关,目前也没想到好的优化方法
    方式二、因为找不到Rom编译的产物,带有符号信息so文件,所有放弃了查看当前native堆栈对象

    Code:

    1、反编译apk,去除不必要的SDK,不必要的代码

    1.1、应用是由多个插件化apk的,

    使用jadx工具反编译,先分析baseApk发现有引用很多界面相关的代码,

    androidx.appcompat:appcompat
    com.google.android.material:material
    androidx.constraintlayout:constraintlayout

    是后台常驻系统应用,不需要界面相关的内容,并且通过搜索反编译的代码,无界面相关的应用,可去除掉,并且引入
    去掉之后,release APK由7726KB减少至3983KB

    1.2、应用插件apk去掉baseApk已经引入的SDK

    可以通过gradlew app:dependencies 命令可以查看SDK引用之间的依赖关系
    引入某个SDK间接引入其他SDK的,可以通过在gradle文件里exclude去除如:

    通过一通裁剪后,插件APK由7.7M降到3.7M

    2、额外引入的SDK,深度裁剪

    查看引用的SDK实现的功能,原生Java、Android接口是否有等效接口
    案例:应用的某个业务使用到joda-time:joda-time SDK,用于实现某个时间戳是星期几、一年中的第几天等功能。该SDK的引用会带来应用进程1M左右的Code 内存。

    □修改方案:使用等效功能的java原生接口(java.util.Calendar)替代额外引用的SDK(org.joda.time.DateTime)的功能,并打包apk时不引用该SDK
    裁剪后APK 大小由3.7M降至2.4M
    替换的接口,建议大家最好写个单元测试,测试一下替换前后返回的结果都是一致的

    Graphics

    该部分多数为view,图片等原因导致,本应用这里不涉及,不过多赘述

    二、场景内存优化

    主要为执行业务过程中,内存是否有优化的空间
    主要从如下几个方面分析

    1、内存碎片化

    在执行周期性任务,或者频繁执行的任务时,避免创建新的实例对象
    可能会导致产生很多临时对象,只能等到下次gc触发了才可以释放,会出现内存抖动的情况
    在实现自定义view中,咱们都知道不能在onDraw()频繁创建对象

    案例:

    主要通过dump java heap分析,对象GC引用链为0的(Depth为-),就代表该对象待GC回收
    手机静置5分钟左右,出现了大量ThreadPool$ThreadTask,Runnable,ConnectedWiifBean等对象
    通过业务代码,可知,

    1)、业务会周期性获取wifi连接信息,并且做一些业务处理,周期性的任务调度会产生大量(ThreadTask,Runnable)
    修复方案:参考android.os.Message的缓存策略,通过链表方式缓存Message,防止频繁创建新的对象
    2)、业务会保持一次队列,只需要保存最近获取到20条wifi连接信息,旧的移除掉
    修复方案:复用被移除的对象,清除内部变量值,并重新赋值

    2、内存泄露

    估计大家都熟悉,这里就不过多赘述,
    个人认为只要该内存对象以后多不需要用到了,但是无法gc回收,都属于内存泄露的范畴
    主要分析方法为通过MAT查看GC引用链来定位
    一般开发过程需要注意,Listener之类的要成对出现,结束后确保结束监听等

    3、提前初始化

    分离测试代码,正式版本使用哑类来实现
    惰性加载等方式延迟初始化,防止无效的内存占用
    如果使用kotlin就有现成的语法糖,by lazy,koin依赖注入框架等

    三、日常开发建议

    1、尽量缩小变量的应用范围,能使用局部变量,传参方式实现的,就不使用全局变量,即可以优化内存开销,也可以解决多线程带来的错误数据

    2、线程一定要自定义线程名,便于定位是哪个业务使用的线程

    附录:

    1、如果有发现不妥的地方,欢迎来扰

    相关文章

      网友评论

          本文标题:Android内存优化案例分析

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