随着Android生态的多年发展,如今4GB 内存的手机都变成了主流,2008 年的手机只有可怜的 140MB 内存,而今年的华为Mate 20 Pro 手机的内存已经达到了 8GB,在之前低内存设备更容易出现内存不足引发的异常和卡顿,咱们也能够经过查看应用中用户的手机内存在 2GB 如下所占的比例来评估,因此在优化前要先定好本身的目标,这一点很是关键。好比针对2GB 以上的设备,彻底是两种不一样的优化思路。
内存优化误区:
一、内存占用越少越好。应用是否占用了过多的内存,跟设备、系统和当时状况有关,而不是 300MB、400MB 这样一个绝对的数值。当系统内存充足的时候,咱们能够多用一些得到更好的性能。当系统内存不足的时候,但愿能够作到“用时分配,及时释放”,当系统内存出现压力时,可以迅速释放各类缓存来减小系统压力。
git
- 在 Android 3.0 以前,Bitmap 对象放在Java 堆,而像素数据是放在 Native 内存中。若是不手动调用 recycle,Bitmap Native 内存的回收彻底依赖finalize 函数回调,熟悉 Java 的同窗应该知道,这个时机不太可控。
- Android 3.0~Android 7.0 将 Bitmap对象和像素数据统一放到 Java 堆中,这样就算咱们不调用 recycle,Bitmap 内存也会随着对象一块儿被回收。不过 Bitmap 是内存消耗的大户,把它的内存放到 Java 堆中彷佛不是那么美妙。即便是最新的华为 Mate 20,最大的 Java 堆限制也才到 512MB,可能个人物理内存还有 5GB,可是应用仍是会由于 Java 堆内存不足致使 OOM。Bitmap 放到 Java 堆的另一个问题会引发大量的GC,对系统内存也没有彻底利用起来。
- 有没有一种实现,能够将 Bitmap 内存放到 Native 中,也能够作到和对象一块儿快速释放,同时 GC 的时候也能考虑这些内存防止被滥用?NativeAllocationRegistry 能够一次知足你这三个要求,Android 8.0 正是使用这个辅助回收 Native内存的机制,来实现像素数据放到 Native 内存中。Android 8.0 还新增了硬件位图 Hardware Bitmap,它能够减小图片内存并提高绘制效率。
二、Native 内存不用管。虽然 Android 8.0 从新将 Bitmap 内存放回到Native中,那么咱们是否是就能够为所欲为地使用图片呢?答案固然是否认的。正如前面所说当系统物理内存不足时,从后台、桌面、服务、前台,直到手机重启。lmk 开始杀进程,系统构想的场景就像下面这张图描述的同样,你们有条不絮的按照优先级排队等着被 kill。low memory killer 的设计,是假定咱们都遵照 Android 规范,但并无考虑到中国国情。国内不少应用就像是打不死的小强,杀死一个拉起五个。频繁的杀死、拉起进程,又会致使 system server 卡死。固然在 Android 8.0 之后应用保活变得困难不少,但依然有一些方法能够突破。只是流程复杂些。
内存形成问题:
以前的崩溃优化中提到“内存优化”是崩溃优化工做中很是重要的一部分,相似 OOM,不少的“异常退出”其实都是由内存问题引发。内存主要会引起两方面问题
程序员
一、异常。异常包括 OOM、内存分配失败这些崩溃,也包括由于总体内存不足致使应用被杀死、设备重启等问题。
github
二、卡顿。Java 内存不足会致使频繁 GC,这个问题在 Dalvik虚拟机会更加明显。而 ART 虚拟机在内存管理跟回收策略上都作大量优化,内存分配和 GC 效率相比提高了 5~10 倍。若是想具体测试 GC 的性能,例如暂停挂起时间、例如暂停挂起时间、总耗时、GC 吞吐量,咱们能够经过发送SIGQUIT 信号得到 ANR 日志。算法
adb shell kill -S QUIT PID
adb pull /data/anr/traces.txt复制代码
它包含一些 ANR 转储信息以及 GC 的详细性能信息,另外咱们还可使用 systrace 来观察 GC 的性能耗。
sticky concurrent mark sweep paused: Sum: 5.491ms 99% C.I. 1.464ms-2.133ms Avg: 1.830ms Max: 2.133ms // GC 暂停时间
Total time spent in GC: 502.251ms // GC 总耗时
Mean GC size throughput: 92MB/s // GC 吞吐量
Mean GC object throughput: 1.54702e+06 objects/s 复制代码
着手内存优化:
一、设备分级。
- 相信你确定遇到过,同一个应用在 4GB 内存的手机运行得很是流畅,但在 1GB 内存的手机就不必定能够作到,并且在系统空闲和繁忙的时候表现也不太同样。因此内存优化首先须要根据设备环境来综合考虑,Facebook 有一个叫device-year-class的开源库,它会用年份来区分设备的性能。使用相似 device-year-class 的策略对设备分级,对于低端机用户能够关闭复杂的动画,或者是某些功能;使用 565 格式的图片,使用更小的缓存内存等。在现实环境下,不是每一个用户的设备都跟咱们的测试机同样高端,在开发过程咱们要学会思考功能要不要对低端机开启、在系统资源吃紧的时候能不能作降级。
- 缓存管理。咱们须要有一套统一的缓存管理机制,能够适当地使用内存;当“系统有难”时,也要责无旁贷地归还。咱们可使用 OnTrimMemory 回调,根据不一样的状态决定释放多少内存。对于大项目来讲,可能存在几十上百个模块,统一缓存管理能够更好地监控每一个模块的缓存大小。
- 安装包大小。安装包中的代码、资源、图片以及 so 库的体积,跟它们占用的内存有很大的关系。一个 80MB 的应用很难在 512MB 内存的手机上流畅运行,这种状况咱们须要考虑针对低端机用户推出 4MB 的轻量版本,例如 Facebook Lite、今日头条极速版都是这个思路。
二、
Bitmap优化。Bitmap 内存通常占应用总内存很大一部分,因此作内存优化永远没法避开图片内存这个“永恒主题”。
- 统一图片库。图片内存优化的前提是收拢图片的调用,这样咱们能够作总体的控制策略。例如低端机使用 565 格式、更加严格的缩放算法,可使用 Glide、Fresco 或者采起自研均可以。并且须要进一步将全部 Bitmap.createBitmap、BitmapFactory 相关的接口也一并收拢。
- 统一监控。第一是大图片监控:咱们须要注意某张图片内存占用是否过大,例如长宽远远大于 View 甚至是屏幕的长宽。在开发过程当中,若是检测到不合规的图片使用,应该当即弹出对话框提示图片所在的 Activity 和堆栈,让开发同窗更快发现并解决问题。在灰度和线上环境下能够将异常信息上报到后台,咱们能够计算有多少比例的图片会超过屏幕的大小,也就是图片的“超宽率”。第二是重复图片监控:重复图片指的是 Bitmap 的像素数据彻底一致,可是有多个不一样的对象存在。这个监控不须要太多的样本量,通常只在内部使用。第三是图片总内存:经过收拢图片使用,咱们还能够统计应用全部图片占用的内存,这样在线上就能够按不一样的系统、屏幕分辨率等维度去分析图片内存的占用状况。在 OOM 崩溃的时候,也能够把图片占用的总内存、Top N 图片的内存都写到崩溃日志中,帮助咱们排查问题。
三、
内存泄漏。内存泄漏简单来讲就是没有回收再也不使用的内存,排查和解决内存泄漏也是内存优化没法避开的工做之一。内存泄漏主要分两种状况,一种是同一个对象泄漏,还有一种状况更加糟糕,就是每次都会泄漏新的对象,可能会出现几百上千个无用的对象。不少内存泄漏都是框架设计不合理所致使,各类各样的单例满天飞,MVC 中 Controller 的生命周期远远大于 View。优秀的框架设计能够减小甚至避免程序员犯错,固然这不是一件容易的事情,因此咱们还须要对内存泄漏创建持续的监控。
- Java内存泄漏。创建相似 LeakCanary 自动化检测方案,至少作到 Activity 和 Fragment 的泄漏检测。在开发过程,咱们但愿出现泄漏时能够弹出对话框,让开发者更加容易去发现和解决问题。内存泄漏监控放到线上并不容易,咱们能够对生成的 Hprof 内存快照文件作一些优化裁剪大部分图片对应的 byte 数组减小文件大小。好比一个 100MB 的文件裁剪后通常只剩下 30MB 左右。使用 7zip 压缩最后小于 10MB,增长了文件上传的成功率。
- OOM监控。美团有一个 Android 内存泄露自动化链路分析组件Probe,它在发生 OOM 的时候生成 Hprof 内存快照,而后经过单独进程对这个文件作进一步的分析。不过在线上使用这个工具风险仍是比较大,在崩溃的时候生成内存快照有可能会致使二次崩溃,并且部分手机生成 Hprof 快照可能会耗时几分钟,对用户形成的体验影响会比较大。另外,部分 OOM 是由于虚拟内存不足致使,这块须要具体问题具体分析。
- Native内存泄漏监控。Malloc 调试(Malloc Debug)和 Malloc钩子(Malloc Hook)彷佛还不是那么稳定。在 WeMobileDev 最近的一篇文章《微信 Android 终端内存优化实践》中,微信也作了一些其余方案上面的尝试。
- 针对没法重编so的状况。使用 PLT Hook 拦截库的内存分配函数,其中 PLT Hook 是 Native Hook 的一种方案,而后重定向到咱们本身的实现后记录分配的内存地址、大小、来源 so 库路径等信息,按期扫描分配与释放是否配对,对于不配对的分配输出咱们记录的信息。
- 针对可重编的so状况。经过 GCC 的“-finstrument-functions"参数给全部函数插桩,桩中模拟调用栈入栈出栈操做;经过 ld 的“–wrap”参数拦截内存分配和释放函数,重定向到咱们本身的实现后记录分配的内存地址、大小、来源 so 以及插桩记录的调用栈此刻的内容,按期扫描分配与释放是否配对,对于不配对的分配输出咱们记录的信息。
坦白地说,除了 Java 泄漏检测方案,目前 OOM 监控和 Native 内存泄漏监控都只能作到实验室自动化测试的水平。微信的 Native 监控方案也遇到一些兼容性的问题,若是想达到灰度和线上部署,须要考虑的细节会很是多。Native 内存泄漏检测在 iOS 会简单一些,不过 Google 也在一直优化 Native 内存泄漏检测的性能和易用性,相信在将来的 Android 版本将会有很大改善。