乐趣区

关于android:深入探索-Android-内存优化炼狱级别下

前言

本篇是 Android 内存优化的进阶篇,难度能够说达到了炼狱级别,倡议对内存优化不是十分相熟的认真看看前篇文章:Android 性能优化之内存优化,其中详细分析了以下几大模块:

  • 1)、Android 的内存管理机制
  • 2)、优化内存的意义
  • 3)、防止内存透露
  • 4)、优化内存空间
  • 5)、图片治理模块的设计与实现

如果你对以上根底内容都比拟理解了,那么咱们便开始 Android 内存优化的探索之旅吧。

本篇文章十分长,倡议珍藏后缓缓享受~

目录

  • 一、重识内存优化

    • 1、手机 RAM
    • 2、内存优化的纬度
    • 3、内存问题
  • 二、常见工具抉择

    • 1、Memory Profiler
    • 2、Memory Analyzer
    • 3、LeakCanary
  • 三、Android 内存管理机制回顾

    • 1、Java 内存调配
    • 2、Java 内存回收算法
    • 3、Android 内存管理机制
    • 4、小结
  • 四、内存抖动

    • 1、那么,为什么内存抖动会导致 OOM?
    • 2、内存抖动解决实战
    • 3、内存抖动常见案例
  • 五、内存优化体系化搭建

    • 1、MAT 回顾
    • 2、搭建体系化的图片优化 / 监控机制
    • 3、建设线上利用内存监控体系
    • 4、建设全局的线程监控组件
    • 5、GC 监控组件搭建
    • 6、建设线上 OOM 监控组件:Probe
    • 7、实现 单机版 的 Profile Memory 自动化内存剖析
    • 8、搭建线下 Native 内存透露监控体系
    • 9、设置内存兜底策略
    • 10、更深刻的内存优化策略
  • 六、内存优化演进

    • 1、自动化测试阶段
    • 2、LeakCanary
    • 3、应用基于 LeakCannary 的改进版 ResourceCanary
  • 七、内存优化工具

    • 1、top
    • 2、dumpsys meminfo
    • 3、LeakInspector
    • 4、JHat
    • 5、ART GC Log
    • 6、Chrome Devtool
  • 八、内存问题总结

    • 1、内类是有危险的编码方式
    • 2、一般 Hanlder 外部类的问题
    • 3、登录界面的内存问题
    • 4、应用零碎服务时产生的内存问题
    • 5、把 WebView 类型的透露装进垃圾桶过程
    • 6、在适当的时候对组件进行登记
    • 7、Handler / FrameLayout 的 postDelyed 办法触发的内存问题
    • 8、图片放错资源目录也会有内存问题
    • 9、列表 item 被回收时留神开释图片的援用
    • 10、应用 ViewStub 进行占位
    • 11、留神定时清理 App 过期的埋点数据
    • 12、针对匿名外部类 Runnable 造成内存透露的解决
  • 九、内存优化常见问题

    • 1、你们内存优化我的项目的过程是怎么做的?
    • 2、你做了内存优化最大的感触是什么?
    • 3、如何检测所有不合理的中央?
  • 十、总结

    • 1、优化大方向
    • 2、优化细节
    • 3、内存优化体系化建设总结

六、内存优化演进

1、自动化测试阶段

内存达到阈值后主动触发 Hprof Dump,将失去的 Hprof 存档后由人工通过 MAT 进行剖析。

2、LeakCanary

检测和剖析报告都在一起,批量自动化测试和预先剖析都不太不便。

3、应用基于 LeakCannary 的改进版 ResourceCanary

Matrix => ResourceCanary 实现原理

次要性能

目前,它的次要性能有 三个局部,如下所示:

1、拆散 检测和剖析 两局部流程

自动化测试由测试平台进行,剖析则由监控平台的服务端离线实现,最初再告诉相干开发解决问题。

2、裁剪 Hprof 文件,以升高 传输 Hprof 文件与后盾存储 Hprof 文件的开销

获取 须要的类和对象相干的字符串 信息即可,其它数据都能够 在客户端裁剪 ,个别能 Hprof 大小会 减小 至原来的 1/10 左右。

3、减少反复 Bitmap 对象检测

不便通过缩小冗余 Bitmap 的数量,以升高内存耗费。

4、小结

在研发阶段须要一直实现 更多的工具和组件 ,以此系统化地 晋升自动化水平 ,以最终 晋升发现问题的效率

七、内存优化工具

除了罕用的内存剖析工具 Memory Profiler、MAT、LeakCanary 之外,还有一些其它的内存剖析工具,上面我将一一为大家进行介绍。

1、top

top 命令是 Linux 下罕用的性能剖析工具,可能 实时显示零碎中各个过程的资源占用情况 ,相似于 Windows 的工作管理器。top 命令提供了 实时的对系统处理器的状态监督。它将 显示零碎中 CPU 最“敏感”的工作列表。该命令能够按 CPU 应用、内存应用和执行工夫 对工作进行排序

接下来,咱们输出以下命令查看 top 命令的用法:

quchao@quchaodeMacBook-Pro ~ % adb shell top --help
usage: top [-Hbq] [-k FIELD,] [-o FIELD,] [-s SORT] [-n NUMBER] [-d SECONDS] [-p PID,] [-u USER,]

Show process activity in real time.

-H    Show threads
-k    Fallback sort FIELDS (default -S,-%CPU,-ETIME,-PID)
-o    Show FIELDS (def PID,USER,PR,NI,VIRT,RES,SHR,S,%CPU,%MEM,TIME+,CMDLINE)
-O    Add FIELDS (replacing PR,NI,VIRT,RES,SHR,S from default)
-s    Sort by field number (1-X, default 9)
-b    Batch mode (no tty)
-d    Delay SECONDS between each cycle (default 3)
-n    Exit after NUMBER iterations
-p    Show these PIDs
-u    Show these USERs
-q    Quiet (no header lines)

Cursor LEFT/RIGHT to change sort, UP/DOWN move list, space to force
update, R to reverse sort, Q to exit.

这里应用 top 仅显示一次过程信息,以便来解说过程信息中各字段的含意。

整体的统计信息区

前四行 是以后零碎状况 整体的统计信息区。上面咱们看每一行信息的具体意义。

第一行:Tasks — 工作(过程)

具体信息阐明如下所示:

零碎当初共有 729 个过程,其中处于 运行中 的有 1 个,715 个在 休眠(sleep)stoped 状态的有 0 个,zombie 状态(僵尸)的有 8 个。

第二行:内存状态

具体信息如下所示:

  • 1)、5847124k total:物理内存总量(5.8GB)
  • 2)、5758016k used:应用中的内存总量(5.7GB)
  • 3)、89108k free:闲暇内存总量(89MB)
  • 4)、112428k buffers:缓存的内存量(112M)

第三行:swap 替换分区信息

具体信息阐明如下所示:

  • 1)、2621436k total:替换区总量(2.6GB)
  • 2)、612572k used:应用的替换区总量(612MB)
  • 3)、2008864k free:闲暇替换区总量(2GB)
  • 4)、2657696k cached:缓冲的替换区总量(2.6GB)

第四行:cpu 状态信息

具体属性阐明如下所示:

  • 1)、800% cpu:8 核 CPU
  • 2)、39% user:39% CPU 被用户过程应用
  • 3)、0% nice:优先值为负的过程占 0%
  • 4)、42% sys — 内核空间占用 CPU 的百分比为 42%
  • 5)、712% idle:除 IO 等待时间以外的其它等待时间为 712%
  • 6)、0% iow:IO 等待时间占 0%
  • 7)、0% irq:硬中断工夫占 0%
  • 8)、6% sirq – 软中断工夫占 0%

对于内存监控,在 top 里咱们要时刻监控 第三行 swap 替换分区的 used,如果这个数值在一直的 变动 ,阐明内核在一直 进行内存和 swap 的数据交换,这是真正的内存不够用了。

过程(工作)的状态监控

第五行及以下,就是各过程(工作)的状态监控,我的项目列信息阐明如下所示:

  • 1)、PID:过程 id
  • 2)、USER:过程所有者
  • 3)、PR:过程优先级
  • 4)、NI:nice 值。负值示意高优先级,正值示意低优先级
  • 5)、VIRT:过程应用的虚拟内存总量。VIRT = SWAP + RES
  • 6)、RES:过程应用的、未被换出的物理内存大小。RES = CODE + DATA
  • 7)、SHR:共享内存大小
  • 8)、S:过程状态。D = 不可中断的睡眠状态、R = 运行、S = 睡眠、T = 跟踪 / 进行、Z = 僵尸过程
  • 9)、%CPU — 上次更新到当初的 CPU 工夫占用百分比
  • 10)、%MEM:过程应用的物理内存百分比
  • 11)、TIME+:过程应用的 CPU 工夫总计,单位 1/100 秒
  • 12)、ARGS:过程名称(命令名 / 命令行)

从上图中能够看到,第一行的就是 Awesome-WanAndroid 这个利用的过程,它的过程名称为 json.chao.com.w+,PID 为 23104,过程所有者 USER 为 u0\_a714,过程优先级 PR 为 10,nice 置 NI 为 -10。过程应用的虚拟内存总量 VIRT 为 4.3GB,过程应用的、未被换出的物理内存大小 RES 为 138M,共享内存大小 SHR 为 66M,过程状态 S 是睡眠状态,上次更新到当初的 CPU 工夫占用百分比 %CPU 为 21.2。过程应用的物理内存百分比 %MEM 为 2.4%,过程应用的 CPU 工夫 TIME+ 为 1:47.58 / 100 小时。

2、dumpsys meminfo

四大内存指标

在解说 dumpsys meminfo 命令之前,咱们必须先理解下 Android 中最重要的 四大内存指标 的概念,如下表所示:

内存指标 英文全称 含意 等价
USS Unique Set Size 物理内存 过程独占的内存
PSS Proportional Set Size 物理内存 PSS = USS + 按比例蕴含共享库
RSS Resident Set Size 物理内存 RSS= USS+ 蕴含共享库
VSS Virtual Set Size 虚拟内存 VSS= RSS+ 未调配理论物理内存

从上可知,它们之间内存的大小关系为 VSS >= RSS >= PSS >= USS

RSS 与 PSS 类似,也蕴含过程共享内存,但比拟麻烦的是 RSS 并没有把共享内存大小全都平分到应用共享的过程头上,以至于所有过程的 RSS 相加会超过物理内存很多 。而 VSS 是虚拟地址,它的 下限与过程的可拜访地址空间无关,和以后过程的内存应用关系并不大。比方有很多的 map 内存也被算在其中,咱们都晓得,file 的 map 内存对应的可能是一个文件或硬盘,或者某个奇怪的设施,它与过程应用内存并没有多少关系。

PSS、USS 最大的不同在于“共享内存“(比方两个 App 应用 MMAP 形式关上同一个文件,那么关上文件而应用的这部分内存就是共享的),USS 不蕴含过程间共享的内存,而 PSS 蕴含。这也造成了 USS 因为短少共享内存,所有过程的 USS 相加要小于物理内存大小的起因。

最早的时候官网就举荐应用 PSS 曲线图来掂量 App 的物理内存占用,而 Android 4.4 之后才退出 USS。然而 PSS,有个很大的 问题 ,就是 ”共享内存“,思考一种状况, 如果 A 过程与 B 过程都会应用一个共享 SO 库,那么 So 库中初始化所用掉的那局部内存就会平分到 A 与 B 的头上。然而 A 是在 B 之后启动的,那么对于 B 的 PSS 曲线而言,在 A 启动的那一刻,即便 B 没有做任何事件,也会呈现一个比拟大的阶梯状下滑,这会给用曲线图剖析软件内存的行为造成致命的麻烦

USS 尽管没有这个问题,然而因为 Dalvik 虚拟机申请内存牵扯到 GC 时延和多种 GC 策略 ,这些都会 影响到曲线的异样稳定 。例如 异步 GC 是 Android 4.0 以上零碎很重要的个性,然而 GC 什么时候完结?曲线什么时候”升高“?就 无奈预计 了。还有 GC 策略,什么时候开始减少 Dalvik 虚拟机的预申请内存大小(Dalvik 启动时是有一个标称的 start 内存大小,它是为 Java 代码运行时预留的,防止 Java 运行时再申请而造成卡顿),然而这个 预申请大小是动态变化的 ,这一点也会 造成 USS 忽大忽小

dumpsys meminfo 命令解析

理解完 Android 内存的性能指标之后,上面咱们便来说说 dumpsys meminfo 这个命令的用法,首先咱们输出 adb shell dumpsys meminfo -h 查看它的帮忙文档:

quchao@quchaodeMacBook-Pro ~ % adb shell dumpsys meminfo -h
meminfo dump options: [-a] [-d] [-c] [-s] [--oom] [process]
-a: include all available information for each process.
-d: include dalvik details.
-c: dump in a compact machine-parseable representation.
-s: dump only summary of application memory usage.
-S: dump also SwapPss.
--oom: only show processes organized by oom adj.
--local: only collect details locally, don't call process.
--package: interpret process arg as package, dumping all
            processes that have loaded that package.
--checkin: dump data for a checkin
If [process] is specified it can be the name or
pid of a specific process to dump.

接着,咱们之间输出 adb shell dumpsys meminfo 命令:

quchao@quchaodeMacBook-Pro ~ % adb shell dumpsys meminfo
Applications Memory Usage (in Kilobytes):
Uptime: 257501238 Realtime: 257501238

// 依据过程 PSS 占用值从大到小排序
Total PSS by process:
    308,049K: com.tencent.mm (pid 3760 / activities)
    225,081K: system (pid 2088)
    189,038K: com.android.systemui (pid 2297 / activities)
    188,877K: com.miui.home (pid 2672 / activities)
    176,665K: com.plan.kot32.tomatotime (pid 22744 / activities)
    175,231K: json.chao.com.wanandroid (pid 23104 / activities)
    126,918K: com.tencent.mobileqq (pid 23741)
    ...

// 以 oom 来划分,会具体列举所有的类别的过程
Total PSS by OOM adjustment:
    432,013K: Native
        76,700K: surfaceflinger (pid 784)
        59,084K: android.hardware.camera.provider@2.4-service (pid 743)
        26,524K: transport (pid 23418)
        25,249K: logd (pid 597)
        11,413K: media.codec (pid 1303)
        10,648K: rild (pid 1304)
        9,283K: media.extractor (pid 1297)
        ...
        
    661,294K: Persistent
        225,081K: system (pid 2088)
        189,038K: com.android.systemui (pid 2297 / activities)
        103,050K: com.xiaomi.finddevice (pid 3134)
        39,098K: com.android.phone (pid 2656)
        25,583K: com.miui.daemon (pid 3078)
        ...
        
    219,795K: Foreground
        175,231K: json.chao.com.wanandroid (pid 23104 / activities)
        44,564K: com.miui.securitycenter.remote (pid 2986)
        
    246,529K: Visible
        71,002K: com.sohu.inputmethod.sogou.xiaomi (pid 4820)
        52,305K: com.miui.miwallpaper (pid 2579)
        40,982K: com.miui.powerkeeper (pid 3218)
        24,604K: com.miui.systemAdSolution (pid 7986)
        14,198K: com.xiaomi.metoknlp (pid 3506)
        13,820K: com.miui.voiceassist:core (pid 8722)
        13,222K: com.miui.analytics (pid 8037)
        7,046K: com.miui.hybrid:entrance (pid 7922)
        5,104K: com.miui.wmsvc (pid 7887)
        4,246K: com.android.smspush (pid 8126)
        
    213,027K: Perceptible
        89,780K: com.eg.android.AlipayGphone (pid 8238)
        49,033K: com.eg.android.AlipayGphone:push (pid 8204)
        23,181K: com.android.thememanager (pid 11057)
        13,253K: com.xiaomi.joyose (pid 5558)
        10,292K: com.android.updater (pid 3488)
        9,807K: com.lbe.security.miui (pid 23060)
        9,734K: com.google.android.webview:sandboxed_process0 (pid 11150)
        7,947K: com.xiaomi.location.fused (pid 3524)
        
    308,049K: Backup
        308,049K: com.tencent.mm (pid 3760 / activities)
        
    74,250K: A Services
        59,701K: com.tencent.mm:push (pid 7234)
        9,247K: com.android.settings:remote (pid 27053)
        5,302K: com.xiaomi.drivemode (pid 27009)
        
    199,638K: Home
        188,877K: com.miui.home (pid 2672 / activities)
        10,761K: com.miui.hybrid (pid 7945)
        
    53,934K: B Services
        35,583K: com.tencent.mobileqq:MSF (pid 14119)
        6,753K: com.qualcomm.qti.autoregistration (pid 8786)
        4,086K: com.qualcomm.qti.callenhancement (pid 26958)
        3,809K: com.qualcomm.qti.StatsPollManager (pid 26993)
        3,703K: com.qualcomm.qti.smcinvokepkgmgr (pid 26976)
        
    692,588K: Cached
        176,665K: com.plan.kot32.tomatotime (pid 22744 / activities)
        126,918K: com.tencent.mobileqq (pid 23741)
        72,928K: com.tencent.mm:tools (pid 18598)
        68,208K: com.tencent.mm:sandbox (pid 27333)
        55,270K: com.tencent.mm:toolsmp (pid 18842)
        24,477K: com.android.mms (pid 27192)
        23,865K: com.xiaomi.market (pid 27825)
        ...

// 按内存的类别来进行划分
Total PSS by category:
    957,931K: Native
    284,006K: Dalvik
    199,750K: Unknown
    193,236K: .dex mmap
    191,521K: .art mmap
    110,581K: .oat mmap
    101,472K: .so mmap
    94,984K: EGL mtrack
    87,321K: Dalvik Other
    84,924K: Gfx dev
    77,300K: GL mtrack
    64,963K: .apk mmap
    17,112K: Other mmap
    12,935K: Ashmem
     3,364K: Stack
     2,343K: .ttf mmap
     1,375K: Other dev
     1,071K: .jar mmap
        20K: Cursor
         0K: Other mtrack

// 手机整体内存应用状况
Total RAM: 5,847,124K (status normal)
Free RAM: 3,711,324K (692,588K cached pss + 2,428,616K cached kernel +   117,492K cached ion +   472,628K free)
Used RAM: 2,864,761K (2,408,529K used pss +   456,232K kernel)
Lost RAM:   184,330K
    ZRAM:   174,628K physical used for   625,388K in swap (2,621,436K total swap)
Tuning: 256 (large 512), oom   322,560K, restore limit   107,520K (high-end-gfx)

依据 dumpsys meminfo 的输入后果,可归结为如下表格:

划分类型 排序指标 含意
process PSS 以过程的 PSS 从大到小顺次排序显示,每行显示一个过程,个别用来做初步的竞品剖析
OOM adj PSS 展现以后零碎外部运行的所有 Android 过程的内存状态和被杀程序,越凑近下方的过程越容易被杀,排序依照一套简单的算法,算法涵盖了前后台、服务或节目、可见与否、老化等
category PSS 以 Dalvik/Native/.art mmap/.dex map 等划分并按降序列出各类过程的总 PSS 散布状况
total 总内存、残余内存、可用内存、其余内存

此外,为了 查看单个 App 过程的内存信息,咱们能够输出如下命令:

dumpsys meminfo <pid> // 输入指定 pid 的某一过程
dumpsys meminfo --package <packagename> // 输入指定包名的过程,可能蕴含多个过程

这里咱们输出 adb shell dumpsys meminfo 23104 这条命令,其中 23104 为 Awesome-WanAndroid App 的 pid,后果如下所示:

quchao@quchaodeMacBook-Pro ~ % adb shell dumpsys meminfo 23104
Applications Memory Usage (in Kilobytes):
Uptime: 258375231 Realtime: 258375231

** MEMINFO in pid 23104 [json.chao.com.wanandroid] **
                Pss  Private  Private  SwapPss     Heap     Heap     Heap
                Total    Dirty    Clean    Dirty     Size    Alloc     Free
                ------   ------   ------   ------   ------   ------   ------
Native Heap    46674    46620        0      164    80384    60559    19824
Dalvik Heap     6949     6912       16       23    12064     6032     6032
Dalvik Other     7672     7672        0        0
       Stack      108      108        0        0
      Ashmem      134      132        0        0
     Gfx dev    16036    16036        0        0
   Other dev       12        0       12        0
   .so mmap     3360      228     1084       27
  .jar mmap        8        8        0        0
  .apk mmap    28279    11328    11584        0
  .ttf mmap      295        0       80        0
  .dex mmap     7780       20     4908        0
  .oat mmap      660        0       92        0
  .art mmap     8509     8028      104       69
 Other mmap      982        8      848        0
 EGL mtrack    29388    29388        0        0
  GL mtrack    14864    14864        0        0
    Unknown     2532     2500        8       20
      TOTAL   174545   143852    18736      303    92448    66591    25856

App Summary
                   Pss(KB)
                    ------
       Java Heap:    15044
     Native Heap:    46620
            Code:    29332
           Stack:      108
        Graphics:    60288
   Private Other:    11196
          System:    11957

           TOTAL:   174545       TOTAL SWAP PSS:      303

Objects
           Views:      171         ViewRootImpl:        1
     AppContexts:        3           Activities:        1
          Assets:       18        AssetManagers:        6
   Local Binders:       32        Proxy Binders:       27
   Parcel memory:       11         Parcel count:       45
Death Recipients:        1      OpenSSL Sockets:        0
        WebViews:        0

SQL
        MEMORY_USED:      371
 PAGECACHE_OVERFLOW:       72          MALLOC_SIZE:      117

DATABASES
    pgsz     dbsz   Lookaside(b)          cache  Dbname
        4       60            109      151/32/18  /data/user/0/json.chao.com.wanandroid/databases/bugly_db_
        4       20             19         0/15/1  /data/user/0/json.chao.com.wanandroid/databases/aws_wan_android.db

该命令输入了 过程的内存概要 ,咱们应该着重关注 四个要点,上面我将一一进行解说。

1、查看 Native Heap 的 Heap Alloc 与 Dalvik Heap 的 Heap Alloc

  • 1)、Heap Alloc:示意 native 的内存占用,如果持续上升,则可能有透露
  • 2)、Heap Alloc:示意 Java 层的内存占用

2、查看 Views、Activities、AppContexts 数量变动状况

如果 Views 与 Activities、AppContexts 持续上升,则表明有内存透露的危险

3、SQL 的 MEMORY\_USED 与 PAGECACHE\_OVERFLOW

  • 1)、MEMOERY\_USED:示意数据库应用的内存
  • 2)、PAGECACHE\_OVERFLOW:示意溢出也应用的缓存,这个数值越小越好

4、查看 DATABASES 信息

  • 1)、pgsz:示意数据库分页大小,这里全是 4KB
  • 2)、Lookaside(b):示意应用了多少个 Lookaside 的 slots,可了解为内存占用的大小
  • 3)、cache:一栏中的 151/32/18 则别离示意 分页缓存命中次数 / 未命中次数 / 分页缓存个数,这里的未命中次数不应该大于命中次数

3、LeakInspector

LeakInspector 是腾讯外部的应用的 一站式内存透露解决方案 ,它是 Android 手机通过长期积攒和提炼、 集内存透露检测、主动修复零碎 Bug、主动回收已泄露 Activity 内资源、主动剖析 GC 链、白名单过滤 等性能于一体,并 深度对接研发流程、主动剖析责任人并提缺点单的全链路体系

那么,LeakInspector 与 LeakCanary 又有什么不同之处呢?

它们之间次要有 四个方面 的不同,如下所示:

一、检测能力与原理方面不同

1、检测能力

它们都反对对 Activity、Fragment 及其它自定义类的透露检测,然而,LeakInspector 还 减少了 Btiamp 的检测能力,如下所示:

  • 1)、检测有没有在 View 上 decode 超过该 View 尺寸的图片,若有则上报呈现问题的 Activity 及与其对应的 View id,并记录它的个数与均匀占用内存的大小。
  • 2)、检测图片尺寸是否超过所有手机屏幕大小,违规则报警。

这一个局部的实现原理,咱们能够采纳 ARTHook 的形式来实现,还不分明的敌人请再认真看看大图检测的局部。

2、检测原理

两个工具的透露检测原理都是在 onDestroy 时查看弱援用,不同之处在于 LeakInspector 间接应用 WeakReference 来检测对象是否曾经被开释,而 LeakCanary 则应用 ReferenceQueue,两者成果是一样的。

并且针对 Activity,咱们通常都会应用 Application 的 registerActivityLifecycleCallbacks 来注册 Activity 的生命周期,以重写 onActivityDestroyed 办法实现。然而在 Android 4.0 以下 ,零碎并没有提供这个办法,为了防止手动在每一个 Activity 的 onDestroy 中去增加这份代码,咱们能够应用 反射 Instrumentation 来截获 onDestory,以升高接入老本。代码如下所示:

Class<?> clazz = Class.forName("android.app.ActivityThread");
Method method = clazz.getDeclaredMethod("currentActivityThread", null);
method.setAccessible(true);
sCurrentActivityThread = method.invoke(null, null);
Field field = sCurrentActivityThread.getClass().getDeclaredField("mInstumentation");
field.setAccessible(true);
field.set(sCurrentActivityThread, new MonitorInstumentation());

二、透露现场解决方面不同

1、dump 采集

两者都能采集 dump,然而 LeakInspector 提供了 回调办法 ,咱们能够 减少更多的自定义信息,如运行时 Log、trace、dumpsys meminfo 等信息,以辅助剖析定位问题。

2、白名单定义

这里的白名单是为了解决一些零碎引起的透露问题,以及一些因为 业务逻辑要开后门的情景而设置 的。剖析时如果碰到白名单上标识的类,则不对这个透露做后续的解决。二者的配置差别有如下两点:

  • 1)、LeakInspector 的白名单以 XML 配置的模式寄存在服务器上。

    • 长处:跟产品甚至不同版本的利用绑定,咱们能够很不便地批改相应的配置。
    • 毛病:白名单里的类不辨别零碎版本一刀切。
  • 1)、而 LeakCanary 的白名单是间接写死在其源码的 AndroidExcludedRefs 类里。

    • 长处:定义十分具体,并辨别零碎版本。
    • 毛病:每次批改必然得从新编译。
  • 2)、LeakCanary 的零碎白名单里定义的类比 LeakInspector 中定义的多很多,因为它没有主动修复零碎透露性能。
3、主动修复零碎透露

针对零碎透露,LeakInspector 通过 反射主动修复 了目前碰到的一些零碎透露,只有在 onDestory 外面 调用 一个修复零碎透露的办法即可。而 LeakCanary 尽管能识别系统透露,然而它仅仅对该类问题给出了剖析,没有提供理论可用的解决方案。

4、回收资源(Activity 内存透露兜底解决)

如果检测到产生了内存透露,LeakInspector 会对整个 Activity 的 View 进行遍历,把图片资源等一些占内存的数据开释掉,保障此次透露只会透露一个 Activity 的空壳,尽量减少对内存的影响。代码大抵如下所示:

if (View instanceof ImageView) {
    // ImageView ImageButton 解决
    recycleImageView(app, (ImageView) view);
} else if (view instanceof TextView) {
    // 开释 TextView、Button 周边图片资源
    recycleTextView((TextView) view);
} else if (View instanceof ProgressBar) {recycleProgressBar((ProgressBar) view);
} else {if (view instancof android.widget.ListView) {recycleListView((android.widget.ListView) view);
    } else if (view instanceof android.support.v7.widget.RecyclerView) {recycleRecyclerView((android.support.v7.widget.RecyclerView) view);
    } else if (view instanceof FrameLayout) {recycleFrameLayout((FrameLayout) view);
    } else if (view instanceof LinearLayout) {recycleLinearLayout((LinearLayout) view);
    }
    
    if (view instanceof ViewGroup) {recycleViewGroup(app, (ViewGroup) view);
    }
}

这里以 recycleTextView 为例,它回收资源的形式如下所示:

private static void recycleTextView(TextView tv) {Drawable[] ds = tv.getCompoundDrawables();
    for (Drawable d : ds) {if (d != null) {d.setCallback(null);
        }
    }
    tv.setCompoundDrawables(null, null, null, null);
    // 勾销焦点,让 Editor$Blink 这个 Runnable 不再被 post,解决内存透露。tv.setCursorVisible(false);
}

三、前期解决不同

1、剖析与展现

采集 dump 之后,LeakInspector 会上传 dump 文件,并 * 调用 MAT 命令行来进行剖析*,失去这次透露的 GC 链。而 LeakCanary 则用开源组件 HAHA 来剖析失去一个 GC 链。然而 LeakCanary 失去的 GC 链蕴含被 hold 住的类对象,个别都不须要用 MAT 关上 Hporf 即可解决问题。而 LeakInpsector 失去的 GC 链只有类名,还须要 MAT 关上 Hprof 能力具体去定位问题,不是很不便。

2、后续跟进闭环

LeakInspector 在 dump 剖析完结之后,会提交缺点单,并且把缺点单调配给对应类的负责人。如果发现反复的问题则更新旧单,同时具备从新关上单等状态转换逻辑。而 LeakCanary 仅会在告诉栏揭示用户,须要用户本人记录该问题并做后续解决。

四、配合自动化测试方面不同

LeakInspector 跟自动化测试能够无缝联合,当自动化脚本执行中发现内存透露,能够由它采集 dump 并发送到服务进行剖析,最初提单,整个流程是不须要人力染指的。而 LeakCanary 则把剖析后果通过告诉栏告知用户,须要人工染指能力进入下一个流程。

4、JHat

JHat 是 Oracle 推出的一款 Hprof 剖析软件,它和 MAT 并称为 Java 内存动态剖析利器。不同于 MAT 的单人界面式剖析,jHat 应用多人界面式剖析 。它被 内置在 JDK 中,在命令行中输出 jhat 命令可查看有没有相应的命令。

quchao@quchaodeMacBook-Pro ~ % jhat
ERROR: No arguments supplied
Usage:  jhat [-stack <bool>] [-refs <bool>] [-port <port>] [-baseline <file>] [-debug <int>] [-version] [-h|-help] <file>

    -J<flag>          Pass <flag> directly to the runtime system. For
            example, -J-mx512m to use a maximum heap size of 512MB
    -stack false:     Turn off tracking object allocation call stack.
    -refs false:      Turn off tracking of references to objects
    -port <port>:     Set the port for the HTTP server.  Defaults to 7000
    -exclude <file>:  Specify a file that lists data members that should
            be excluded from the reachableFrom query.
    -baseline <file>: Specify a baseline object dump.  Objects in
            both heap dumps with the same ID and same class will
            be marked as not being "new".
    -debug <int>:     Set debug level.
                0:  No debug output
                1:  Debug hprof file parsing
                2:  Debug hprof file parsing, no server
    -version          Report version number
    -h|-help          Print this help and exit
    <file>            The file to read

For a dump file that contains multiple heap dumps,
you may specify which dump in the file
by appending "#<number>" to the file name, i.e. "foo.hprof#3".

呈现如上输入,则表明存在 jhat 命令。它的应用很简略,直在命令行输出 jhat xxx.hprof 即可,如下所示:

quchao@quchaodeMacBook-Pro ~ % jhat Documents/heapdump/new-33.hprof
Snapshot read, resolving...
Resolving 408200 objects...
Chasing references, expect 81 dots.................................................................................
Eliminating duplicate references.................................................................................
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.

jHat 的执行过程是解析 Hprof 文件,而后启动 httpsrv 服务,默认是在 7000 端口监听 Web 客户端链接,保护 Hprof 解析后的数据,以继续供应 Web 客户端进行查问操作

启动服务器后,咱们关上 入口地址 127.0.0.1:7000 即可查看 All Classes 界面,如下图所示:

jHat 还有两个比拟重要的性能,别离如下所示:

1、统计表

关上 127.0.0.1:7000/histo/,统计表界面如下所示:

能够到,按 Total Size 降序 排列了所有的 Class,并且,咱们还能够查看到每一个 Class 与之对应的实例数量。

2、OQL 查问

OQL 是一种模拟 SQL 语句的查问语句,通常用来查问某个类的实例数量,关上 127.0.0.1:7000/oql/ 并输出 java.lang.String 查问 String 实例的数量,后果如下图所示:

JHat 比 MAT 更加灵便,且合乎大型团队安装简单、团队合作的需要。然而,并不适宜中小型高效沟通型团队应用。

5、ART GC Log

GC Log 分为 Dalvik 和 ART 的 GC 日志,对于 Dalvik 的 GC 日志,咱们在前篇 Android 性能优化之内存优化 中曾经具体解说过了,接下来咱们说说 ART 的 GC 日志

ART 的日志与 Dalvik 的日志差距十分大,除了格局不同之外,打印的工夫也不同,而且,它只有在慢 GC 时才会打印进去。上面咱们看看这条 ART GC Log:

Explicit (full) concurrent mark sweep GC freed 104710(7MB)AllocSpace objects, 21(416KB)LOS objects, 33% free,25MB/38MB paused 1.230ms total 67.216ms
GC 产生的起因 GC 类型 采集办法 开释的数量和占用的空间 开释的大对象数量和所占用的空间 堆中闲暇空间的百分比和(对象的个数)/(堆的总空间) 暂停耗时

GC 产生的起因

GC 产生的起因有如下九种:

  • 1)、Concurrent、Alloc、Explicit 跟 Dalvik 的根本一样,这里就不反复介绍了。
  • 2)、NativeAlloc:Native 内存调配时,比方为 Bitmaps 或者 RenderScript 调配对象,这会导致 Native 内存压力,从而触发 GC
  • 3)、Background:后盾 GC,触发是为了给前面的内存申请预留更多空间
  • 4)、CollectorTransition:由堆转换引起的回收,这是运行时切换 GC 而引起的。收集器转换包含将所有对象从闲暇列表空间复制到碰撞指针空间(反之亦然)。以后,收集器转换仅在以下状况下呈现:在内存较小的设施上,App 将过程状态从可察觉的暂停状态变更为可察觉的非暂停状态(反之亦然)
  • 5)、HomogeneousSpaceCompact:齐性空间压缩是指闲暇列表到压缩的闲暇列表空间,通常产生在当 App 曾经挪动到可察觉的暂停过程状态。这样做的次要起因是缩小了内存应用并对堆内存进行碎片整顿
  • 6)、DisableMovingGc:不是真正的触发 GC 起因,产生并发堆压缩时,因为应用了 GetPrimitiveArrayCritical,收集会被阻塞。个别状况下,强烈建议不要应用 GetPrimitiveArrayCritical
  • 7)、HeapTrim:不是触发 GC 起因,然而请留神,收集会始终被阻塞,直到堆内存整理结束

GC 类型

GC 类型有如下三种:

  • 1)、Full:与 Dalvik 的 FULL GC 差不多
  • 2)、Partial:跟 Dalvik 的部分 GC 差不多,策略时不蕴含 Zygote Heap
  • 3)、Sticky:另外一种部分中的部分 GC,抉择部分的策略是上次垃圾回收后新调配的对象

GC 采集的办法

GC 采集的办法有如下四种:

  • 1)、mark sweep:先记录全副对象,而后从 GC ROOT 开始找出间接和间接的对象并标注。利用之前记录的全副对象和标注的对象比照,其余的对象就应该须要垃圾回收了
  • 2)、concurrent mark sweep:应用 mark sweep 采集器的并发 GC
  • 3)、mark compact:在标记存活对象的时候,所有的存活对象压缩到内存的一端,而另一端能够更加高效地被回收
  • 4)、semispace:在做垃圾扫描的时候,把所有援用的对象从一个空间移到另外一个空间,而后间接 GC 残余在旧空间中的对象即可

通过 GC 日志,咱们能够晓得 GC 的量和 它对卡顿的影响 ,也能够 初步定位一些如被动调用 GC、可调配的内存不足、过多应用 Weak Reference 等问题。

6、Chrome Devtool

对于 HTML5 页面而言,抓取 JavaScript 的内存须要应用 Chrome Devtools 来进行近程调试。形式有如下两种:

  • 1)、间接把 URL 抓取进去放到 Chrome 里拜访。
  • 2)、用 Android H5 近程调试。

纯 H5

1、手机装置 Chrome,关上 USB 调试模式,通过 USB 连上电脑,在 Chrome 里关上一个页面,比方百度页面。而后在 PC Chrome 地址栏里拜访 Chrome://inspect,如下图所示:

2、最初,间接点击 Chrome 上面的 inspect 选项即可弹出开发者工具界面。如下图所示:

默认 Hybrid H5 调试

Android 4.4 及以上零碎的原生浏览器就是 Chrome 浏览器,能够应用 Chrome Devtool 近程调试 WebView,前提是须要在 App 的代码里把调试开关关上,如下代码所示:

if (Build.VERSION_SDK_INT >= Build.VERSION_CODES.KITKAT && 是 debug 模式) {WebView.setWebContentsDebuggingEnabled(ture);
}

关上后的调试办法跟纯 H5 页面调试办法一样,间接在 App 中关上 H5 页面,再到 PC Chrome 的 inpsector 页面就能够看到调试指标页面。

这里总结一下 JS 中几种常见的内存问题点

  • 1)、closure 闭包函数
  • 2)、事件监听
  • 3)、变量作用域使用不当,全局变量的援用导致无奈开释
  • 4)、DOM 节点的透露

若想更深刻地学习 Chrome 开发者工具的应用办法,请查看《Chrome 开发者工具中文手册》。

八、内存问题总结

在咱们进行内存优化的过程中,有许多内存问题都能够归结为一类问题,为了便于当前疾速地解决相似的内存问题,我将它们归纳成了以下的多个要点

1、内类是有危险的编码方式

说道内类就不得不提到”**this0“∗∗,它是一种奇异的内类成员,每个类实例都具备一个 this0“**,它是一种奇异的内类成员,每个类实例都具备一个 this0“∗∗,它是一种奇异的内类成员,每个类实例都具备一个 this0,当它的内类须要拜访它的成员时,内类就会持有外类的 this0,通过 this0,通过 this0,通过 this0 就能够拜访外部类所有的成员。

解决方案是在 Activity 敞开,即触发 onDestory 时解除内类和内部的援用关系。

2、一般 Hanlder 外部类的问题

这也是一个 this$0 间接援用的问题,对于 Handler 的解决方案个别能够归结为如下三个步骤:

  • 1)、把内类申明成 static:用来断绝 this$0 的援用。因为 static 形容的内类从 Java 编译原理的角度看,”内类“与”外类“互相独立,相互都没有拜访对方成员变量的能力
  • 2、应用 WeakReference 来援用外部类的实例
  • 3、在外部类(如 Activity)销毁的时候应用 removeCallbackAndMessages 来移除回调和音讯

这里须要在应用过程中 留神对 WeakReference 进行判空

3、登录界面的内存问题

如果在闪屏页跳转到登录界面时没有调用 finish(),则会造成闪屏页的内存透露,在 碰到这种”过渡界面“的状况时,须要留神不要产生这样的内存 Bug

4、应用零碎服务时产生的内存问题

咱们通常都会应用 getSystemService 办法来获取零碎服务,然而当在 Activity 中调用时,会默认把 Activity 的 Context 传给零碎服务,在某些不确定的状况下,某些零碎服务外部会产生异样,从而 hold 住外界传入的 Context。

解决方案是 间接应用 Applicaiton 的 Context 去获取零碎服务

5、把 WebView 类型的透露装进垃圾桶过程

咱们都晓得,对应 WebView 来说,其 网络延时、引擎 Session 治理、Cookies 治理、引擎内核线程、HTML5 调用零碎声音、视频播放组件等产生的援用链条无奈及时打断,造成的内存问题基本上能够用”无解“来形容。

解决方案是咱们能够 把 WebView 装入另一个过程 。具体为 在 AndroidManifes 中对以后的 Activity 设置 android:process 属性即可,最初,在 Activity 的 onDestory 中退出过程,这样即可基本上终结 WebView 造成的透露

6、在适当的时候对组件进行登记

咱们在平时开发过程中常常须要在 Activity 创立的时候去注册一些组件,如播送、定时器、事件总线等等。这个时候咱们应该在适当的时候对组件进行登记,如 onPause 或 onDestory 办法中

7、Handler / FrameLayout 的 postDelyed 办法触发的内存问题

不仅在应用 Handler 的 sendMessage 办法时,咱们须要在 onDestory 中应用 removeCallbackAndMessage 移除回调和音讯,在应用到 Handler / FrameLayout 的 postDelyed 办法时,咱们须要 调用 removeCallbacks 去移除实现控件外部的延时器对 Runnable 内类的持有

8、图片放错资源目录也会有内存问题

在做资源适配的时候,因为须要思考到 APK 的瘦身问题,无奈为每张图片在每个 drawable / mipmap 目录下安置一张适配图片的正本。很多同学不晓得图片应该放哪个目录,如果放到分辨率低的目录如 hdpi 目录,则可能会造成内存问题,这个时候 倡议尽量问设计人员要高品质图片而后往高密度目录下方,如 xxhdpi 目录 ,这样 在低密屏上”放大倍数“是小于 1 的 ,在保障画质的前提下,内存也是可控的。 也能够应用 Drawable.createFromSream 替换 getResources().getDrawable 来加载,这样便能够绕过 Android 的默认适配规定

对于曾经被用户应用物理“返回键”退回到后盾的过程,如果蕴含了以下 两点 ,则 不会被轻易杀死

  • 1)、过程蕴含了服务 startService,而服务自身调用了 startForeground(低版本需通过反射调用)
  • 2)、主 Activity 没有实现 onSaveInstanceState 接口

但倡议 在运行一段时间(如 3 小时)后被动保留界面过程(位于后盾),而后重启它,这样能够无效地升高内存负载

9、列表 item 被回收时留神开释图片的援用

咱们应该在 item 被回收不可见时去开释掉对图片的援用。如果你应用的是 ListView,因为每次 item 被回收后被再次利用都会去从新绑定数据,所以只需 在 ImageView 回调其 onDetchFromWindow 办法的时候区开释掉图片的援用即可 。如果你应用的是 RecyclerView,因为被回收不可见时第一次抉择是放进 mCacheView 中,然而这外面的 item 被复用时并不会去执行 bindViewHolder 来从新绑定数据,只有被回收进 mRecyclePool 后拿进去复用才会从新绑定数据。所以此时咱们应该 在 item 被回收进 RecyclePool 的时候去开释图片的援用 ,这里咱们只有去 重写 Adapter 中的 onViewRecycled 办法 就能够了,代码如下所示:

@Override
public void onViewRecycled(@Nullable VH holder) {super.onViewRecycled(holder);
    if (holder != null) {// 做开释图片援用的操作}
}

10、应用 ViewStub 进行占位

咱们应该应用 ViewStub 对那些没有马上用到的资源去做提早加载 ,并且还有 很多大概率不会呈现的 View 更要去做懒加载,这样能够等到要应用时再去为它们调配相应的内存。

11、留神定时清理 App 过期的埋点数据

产品或者经营为了统计数据会在每个版本中一直地减少新的埋点。所以咱们须要定期地去清理一些过期的埋点,以此来 适当地优化内存以及 CPU 的压力

12、针对匿名外部类 Runnable 造成内存透露的解决

咱们在做子线程操作的时候,喜爱应用匿名外部类 Runnable 来操作。然而,如果某个 Activity 放在线程池中的工作不能及时执行结束,在 Activity 销毁时很容易导致内存透露。因为这个 匿名外部类 Runnable 类持有一个指向 Outer 类的援用,这样一来如果 Activity 外面的 Runnable 不能及时执行,就会使它外围的 Activity 无奈开释,产生内存透露 。从下面的剖析可知, 只有在 Activity 退出时没有这个援用即可 ,那咱们就 通过反射,在 Runnable 进入线程池前先干掉它,代码如下所示:

Field f = job.getClass().getDeclaredField("this$0");
f.setAccessible(true);
f.set(job, null);

这个工作就是咱们的 Runnable 对象,而”this$0“就是下面所指的外部类的援用了。这里留神应用 WeakReference 装起来,要执行了先 get 一下,如果是 null 则阐明 Activity 曾经回收,工作就放弃执行。

九、内存优化常见问题

1、你们内存优化我的项目的过程是怎么做的?

1、剖析现状、确认问题

咱们发现咱们的 APP 在内存方面可能存在很大的问题,第一方面的起因是咱们的线上的 OOM 率比拟高。

第二点呢,咱们常常会看到在咱们的 Android Studio 的 Profiler 工具中内存的抖动比拟频繁。

这是咱们一个初步的现状,而后在咱们晓得了这个初步的现状之后,进行了问题的确认,咱们通过一系列的调研以及深入研究,咱们最终发现咱们的我的项目中存在以下几点大问题,比如说:内存抖动、内存溢出、内存透露,还有咱们的 Bitmap 应用十分粗暴

2、针对性优化

比方 内存抖动的解决 => Memory Profiler 工具的应用(出现了锯齿张图形)=> 剖析到具体代码存在的问题(频繁被调用的办法中呈现了日志字符串的拼接),也能够说说 内存透露或内存溢出的解决

3、效率晋升

为了不减少业务同学的工作量,咱们应用了一些工具类或 ARTHook 这样的 大图检测计划,没有任何的侵入性 。同时,咱们将这些技术教给了大家,而后让大家一起进行 工作效率上的晋升

咱们对内存优化工具 Profiler Memory、MAT 的应用比拟相熟,因而 针对一系列不同问题的状况 ,咱们写了 一系列解决方案的文档 ,分享给大家。这样,咱们 整个团队成员的内存优化意识就变强 了。

2、你做了内存优化最大的感触是什么?

1、磨刀不误砍柴工

咱们一开始并没有间接去剖析我的项目中代码哪些地方存在内存问题,而是先去学习了 Google 官网的一些文档,比如说学习了 Memory Profiler 工具的应用、学习了 MAT 工具的应用,在咱们将这些工具学习纯熟之后,当在咱们的我的项目中遇到内存问题时,咱们就可能很快地进行排查定位问题进行解决。

2、技术优化必须联合业务代码

一开始,咱们做了整体 APP 运行阶段的一个内存上报,而后,咱们在一些重点的内存耗费模块进行了一些监控,然而,前面发现这些监控并没有严密地联合咱们的业务代码,比如说在梳理完我的项目之后,发现咱们我的项目中存在应用多个图片库的状况,多个图片库的内存缓存必定是不专用的 ,所以 导致咱们整个我的项目的内存使用量十分高。所以进行技术优化时必须联合咱们的业务代码。

3、系统化欠缺解决方案

咱们在做内存优化的过程中,不仅做了 Android 端的优化工作,还将咱们 Android 端一些数据的采集上报到了咱们的服务器,而后传到咱们的 APM 后盾,这样,不便咱们的无论是 Bug 跟踪人员或者是 Crash 跟踪人员进行一系列问题的解决。

3、如何检测所有不合理的中央?

比如说 大图片的检测 ,咱们最后的一个计划是通过 继承 ImageView重写 它的 onDraw 办法来实现。然而,咱们在推广它的过程中,发现很多开发人员并不承受,因为很多 ImageView 之前曾经写过了,你当初让他去替换,工作老本是比拟高的。所以说,起初咱们就想,有没有一种计划能够 免替换,最终咱们就找到了 ARTHook 这样一个 Hook 的计划。

十、总结

对于 内存优化的专项优化 而言,咱们要着重留神两点,即 优化大方向 和 优化细节

1、优化大方向

对于 优化的大方向 ,咱们应该 优先去做见效快的中央,次要有以下三局部:

  • 1)、内存透露
  • 2)、内存抖动
  • 3)、Bitmap

2、优化细节

对于 优化细节 ,咱们应该 留神一些零碎属性或内存回调的应用 等等,次要能够细分为如下六局部:

  • 1)、LargeHeap 属性
  • 2)、onTrimMemory / onLowMemory
  • 3)、应用优化过后的汇合:如 SparseArray 类簇
  • 4)、审慎应用 SharedPreference
  • 5)、审慎应用内部库
  • 6)、业务架构设计正当

3、内存优化体系化建设总结

在这篇文章中,咱们除了建设了 内存的监控闭环 这一外围体系之外,还实现了以下 十大组件 / 策略

  • 1)、依据设施分级来应用不同的内存和调配回收策略
  • 2)、针对低端机做了性能或图片加载格局的降级解决
  • 3)、针对缓存滥用的问题实现了对立的缓存治理组件
  • 4)、实现了大图监控和反复图片的监控
  • 5)、在前台每隔肯定工夫去获取以后利用内存占最大内存的比例,当超过设定阈值时则被动开释利用 cache
  • 6)、当 UI 暗藏时开释内存以减少零碎缓存利用过程的能力
  • 7)、高效实现了利用全局内的 Bitmap 监控
  • 8)、实现了全局的线程监控
  • 9)、针对内存应用的重度场景实现了 GC 监控
  • 10)、实现了线下的 native 内存透露监控

最初,当监控到 利用内存超过阈值时 ,还定制了 欠缺的兜底策略 重启利用过程

总的来看,要建设一套 全面且成体系的内存优化及监控 是十分重要也是极具挑战性的一项工作。并且,目前各大公司的 内存优化体系 也正处于 一直演进的历程 之中,其目标不外乎: 实现更健全的性能、更深层次的定位问题、疾速精确地发现线上问题

路漫漫其修远兮,吾将上下而求索

参考链接:

1、国内 Top 团队大牛带你玩转 Android 性能剖析与优化 第四章 内存优化

2、极客工夫之 Android 开发高手课 内存优化

3、微信 Android 终端内存优化实际

4、GMTC-Android 内存透露自动化链路剖析组件 Probe.key

5、Manage your app’s memory

6、Overview of memory management

7、Android 内存优化杂谈

8、Android 性能优化之内存篇

9、治理利用的内存

10、《Android 挪动性能实战》第二章 内存

11、每天一个 linux 命令(44):top 命令

12、Android 内存剖析命令

很感谢您浏览这篇文章,心愿您能将它分享给您的敌人或技术群,这对我意义重大。

本文转自 https://juejin.cn/post/6872919545728729095,如有侵权,请分割删除。

相干视频举荐:

【2021 最新版】Android studio 装置教程 +Android(安卓)零基础教程视频(适宜 Android 0 根底,Android 初学入门)含音视频_哔哩哔哩_bilibili

Android 进阶零碎学习——Jetpack 先天优良的基因能够防止数据内存透露_哔哩哔哩_bilibili

退出移动版