本篇是 Android 内存优化的进阶篇,难度能够说达到了炼狱级别,倡议对内存优化不是十分相熟的认真看看前篇文章: Android性能优化之内存优化,其中详细分析了以下几大模块:
如果你对以上根底内容都比拟理解了,那么咱们便开始 Android 内存优化的探索之旅吧。
本篇文章十分长,倡议珍藏后缓缓享受~
Android给每个利用过程调配的内存都是十分无限的,那么,为什么不能把图片下载下来都放到磁盘中呢?那是因为放在 内存 中,展示会更 “快”,快的起因有两点,如下所示:
这里说一下解码的概念。Android零碎要在屏幕上展现图片的时候只认 “像素缓冲”,而这也是大多数操作系统的特色。而咱们 常见的jpg,png等图片格式,都是把 “像素缓冲” 应用不同的伎俩压缩后的后果,所以这些格局的图片,要在设施上 展现,就 必须通过一次解码,它的 执行速度会受图片压缩比、尺寸等因素影响。(官网倡议:把从内存中淘汰的图片,升高压缩比后存储到本地,以备后用,这样能够最大限度地升高当前复用时的解码开销。)
上面,咱们来理解一下内存优化的一些重要概念。
手机不应用 PC 的 DDR内存,采纳的是 LPDDR RAM,即 ”低功耗双倍数据速率内存“。其计算规定如下所示:
LPDDR系列的带宽 = 时钟频率 ✖️内存总线位数 / 8 LPDDR4 = 1600MHZ ✖️64 / 8 ✖️双倍速率 = 25.6GB/s。
当零碎 内存短缺 的时候,咱们能够 多用 一些取得 更好的性能。当零碎 内存不足 的时候,咱们心愿能够做到 ”用时调配,及时开释“。
对于Android内存优化来说又能够细分为如下两个维度,如下所示:
次要是 升高运行时内存。它的 目标 有如下三个:
升高利用占ROM的体积,进行APK瘦身。它的 目标 次要是为了 升高利用占用空间,防止因ROM空间有余导致程序无奈装置。
那么,内存问题次要是有哪几类呢?内存问题通常来说,能够细分为如下 三类:
上面,咱们来理解下它们。
内存稳定图形呈 锯齿张、GC导致卡顿。
这个问题在 Dalvik虚拟机 上会 更加显著,而 ART虚拟机 在 内存治理跟回收策略 上都做了 大量优化,内存调配和GC效率相比晋升了5~10倍,所以 呈现内存抖动的概率会小很多。
Android零碎虚拟机的垃圾回收是通过虚拟机GC机制来实现的。GC会抉择一些还存活的对象作为内存遍历的根节点GC Roots,通过对GC Roots的可达性来判断是否须要回收。内存透露就是 在以后利用周期内不再应用的对象被GC Roots援用,导致不能回收,使理论可应用内存变小。简言之,就是 对象被持有导致无奈开释或不能依照对象失常的生命周期进行开释。一般来说,可用内存缩小、频繁GC,容易导致内存透露。
即OOM,OOM时会导致程序异样。Android设施出厂当前,java虚拟机对单个利用的最大内存调配就确定下来了,超出这个值就会OOM。单个利用可用的最大内存对应于 /system/build.prop 文件中的 dalvik.vm.heapgrowthlimit。
此外,除了因内存透露累积到肯定水平导致OOM的状况以外,也有一次性申请很多内存,比如说 一次创立大的数组或者是载入大的文件如图片的时候会导致OOM。而且,理论状况下 很多OOM就是因图片处理不当 而产生的。
在 Android性能优化之内存优化中咱们曾经介绍过了相干的优化工具,这里再简略回顾一下。
弱小的 Java Heap 剖析工具,查找 内存透露及内存占用, 生成 整体报告、剖析内存问题 等等。倡议 线下深刻应用。
自动化 内存透露检测神器。倡议仅用于线下集成。
它的 毛病 比拟显著,具体有如下两点:
ART 和 Dalvik 虚拟机应用 分页和内存映射 来治理内存。上面咱们先从Java的内存调配开始说起。
Java的 内存调配区域 分为如下 五局部:
流程可简述为 两步:
实现比较简单。
流程可简述为 三步:
实现简略,运行高效,每次仅需遍历标记一半的内存区域。
会节约一半的空间,代价大。
流程可简述为 三步:
当初 支流的虚拟机 个别用的比拟多的还是分代收集算法,它具备如下 特点:
Android 中的内存是 弹性调配 的,调配值 与 最大值 受具体设施影响。
对于 OOM场景 其实能够细分为如下两种:
咱们须要着重留神一下这两种的辨别。
以Android中虚拟机的角度来说,咱们要分明 Dalvik 与 ART 区别,Dalvik 仅固定一种回收算法,而 ART 回收算法可在 运行期按需抉择,并且,ART 具备 内存整理 能力,缩小内存空洞。
最初,LMK(Low Memory killer) 机制保障了过程资源的正当利用,它的实现原理次要是 依据过程分类和回收收益来综合决定的一套算法集。
当 内存频繁调配和回收 导致内存 不稳固,就会呈现内存抖动,它通常体现为 频繁GC、内存曲线呈锯齿状。
并且,它的危害也很重大,通常会导致 页面卡顿,甚至造成 OOM。
次要起因有如下两点:
这里咱们假如有这样一个场景:点击按钮应用 handler 发送一个空音讯,handler 的 handleMessage 接管到音讯后创立内存抖动,即在 for 循环创立 100个容量为10万 的 strings 数组并在 30ms 后持续发送空音讯。
个别应用 Memory Profiler (体现为 频繁GC、内存曲线呈锯齿状)联合代码排查即可找到内存抖动呈现的中央。
通常的技巧就是着重查看 循环或频繁被调用 的中央。
上面列举一些导致内存抖动的常见案例,如下所示:
应用 SparseArray类族、ArrayMap 来代替 HashMap。
在开始咱们明天正式的主题之前,咱们先来回归一下内存透露的概念与解决技巧。
所谓的内存透露就是 内存中存在曾经没有用的对象。它的 体现 个别为 内存抖动、可用内存逐步缩小。 它的 危害 即会导致 内存不足、GC频繁、OOM。
而对于 内存透露的剖析 个别可简述为如下 两步:
对于MAT来说,其惯例的查找内存透露的形式能够细分为如下三步:
此外,在Android性能优化之内存优化还有几种进阶的应用形式,这里就不一一赘述了,上面,咱们来看看对于 MAT 应用时的一些要害细节。
要全面把握MAT的用法,必须要先理解 暗藏在 MAT 应用中的四大细节,如下所示:
除此之外,MAT 共有 5个要害组件 帮忙咱们去剖析内存方面的问题,别离如下所示:
上面咱们这里再简略地回顾一下它们。
如果从GC Root达到对象A的门路上必须通过对象B,那么B就是A的支配者。
查看 线程数量 和 线程的 Shallow Heap、Retained Heap、Context Class Loader 与 is Daemon。
通过 图形 的模式列出 占用内存比拟多的对象。
在下方的 Biggest Objects 还能够查看其 绝对比拟具体的信息,例如 Shallow Heap、Retained Heap。
列出有内存透露的中央,点击 Details 能够查看其产生内存透露的援用链。
在介绍图片监控体系的搭建之前,首先咱们来回顾下 Android Bitmap 内存调配的变动。
将 Bitmap对象 和 像素数据 对立放到 Java Heap 中,即便不调用 recycle,Bitmap 像素数据也会随着对象一起被回收。
然而,Bitmap 全副放在 Java Heap 中的毛病很显著,大抵有如下两点:
将图片内存寄存在Native中的步骤有 四步,如下所示:
咱们都晓得的是,当 零碎内存不足 的时候,LMK 会依据 OOM_adj 开始杀过程,从 后盾、桌面、服务、前台,直到手机重启。并且,如果频繁申请开释 Java Bitmap 也很容易导致内存抖动。对于这种种问题,咱们该 如何评估内存对利用性能的影响 呢?
对此,咱们能够次要从以下 两个方面 进行评估,如下所示:
对于具体的优化策略与伎俩,咱们能够从以下 七个方面 来搭建一套 成体系化的图片优化 / 监控机制。
在我的项目中,咱们须要 收拢图片的调用,防止应用 Bitmap.createBitmap、BitmapFactory 相干的接口创立 Bitmap,而应该应用本人的图片框架。
内存优化首先须要依据 设施环境 来综合思考,让高端设施应用更多的内存,做到 针对设施性能的好坏应用不同的内存调配和回收策略。
因而,咱们能够应用相似device-year-class的策略对设施进行分级,对于低端机用户能够敞开简单的动画或”重性能“,应用565格局的图片或更小的缓存内存 等等。
业务开发人员须要 思考性能是否对低端机开启,在系统资源不够时被动去做降级解决。
建设对立的缓存治理组件,并正当应用 OnTrimMemory / LowMemory 回调,依据零碎不同的状态去开释相应的缓存与内存。
在实现过程中,须要 解决应用 static LRUCache 来缓存大尺寸 Bitmap 的问题。
并且,在通过理论的测试后,发现 onTrimMemory 的 ComponetnCallbacks2.TRIM_MEMORY_COMPLETE 并不等价于 onLowMemory,因而倡议依然要去监听 onLowMemory 回调。
一个 空过程 也会占用 10MB 内存,低端机应该尽可能减少应用多过程。
针对低端机用户能够推出 4MB 的轻量级版本,现在日头条极速版、Facebook Lite。
在开发过程中,如果检测到不合规的图片应用(如图片宽度超过View的宽度甚至屏幕宽度),应该立即提醒图片所在的Activity和堆栈,让开发人员更快发现并解决问题。在灰度和线上环境,能够将异样信息上报到后盾,还能够计算超宽率(图片超过屏幕大小所占图片总数的比例)。
上面,咱们介绍下如何实现对大图片的检测。
继承 ImageView,重写实现计算图片大小。然而侵入性强,并且不通用。
因而,这里咱们介绍一种更好的计划:ARTHook。
ARTHook,即 挂钩,用额定的代码勾住原有的办法,以批改执行逻辑,次要能够用于以下四个方面:
具体咱们是应用 Epic 来进行 Hook,Epic 是 一个虚拟机层面,以 Java 办法为粒度的运行时 Hook 框架。简略来说,它就是 ART 上的 Dexposed,并且它目前 反对 Android 4.0~10.0。
Epic通常的应用步骤为如下三个步骤:
1、在我的项目 moudle 的 build.gradle 中增加
compile 'me.weishu:epic:0.6.0'
2、继承 XC_MethodHook,实现 Hook 办法前后的逻辑。如 监控Java线程的创立和销毁:
class ThreadMethodHook extends XC_MethodHook{ @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { super.beforeHookedMethod(param); Thread t = (Thread) param.thisObject; Log.i(TAG, "thread:" + t + ", started.."); } @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { super.afterHookedMethod(param); Thread t = (Thread) param.thisObject; Log.i(TAG, "thread:" + t + ", exit.."); } }
3、注入 Hook 好的办法:
DexposedBridge.findAndHookMethod(Thread.class, "run", new ThreadMethodHook());
晓得了 Epic 的根本应用办法之后,咱们便能够利用它来实现大图片的监控报警了。
以 Awesome-WanAndroid我的项目为例,首先,在 WanAndroidApp 的 onCreate 办法中增加如下代码:
DexposedBridge.hookAllConstructors(ImageView.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { super.afterHookedMethod(param); // 1 DexposedBridge.findAndHookMethod(ImageView.class, "setImageBitmap", Bitmap.class, new ImageHook()); } });
在正文1处,咱们 通过调用 DexposedBridge 的 findAndHookMethod 办法找到所有通过 ImageView 的 setImageBitmap 办法设置的切入点,其中最初一个参数 ImageHook 对象是继承了 XC_MethodHook 类,其目标是为了 重写 afterHookedMethod 办法拿到相应的参数进行监控逻辑的判断。
接下来,咱们来实现咱们的 ImageHook 类,代码如下所示:
public class ImageHook extends XC_MethodHook { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { super.afterHookedMethod(param); // 1 ImageView imageView = (ImageView) param.thisObject; checkBitmap(imageView,((ImageView) param.thisObject).getDrawable()); } private static void checkBitmap(Object thiz, Drawable drawable) { if (drawable instanceof BitmapDrawable && thiz instanceof View) { final Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap(); if (bitmap != null) { final View view = (View) thiz; int width = view.getWidth(); int height = view.getHeight(); if (width > 0 && height > 0) { // 2、图标宽高都大于view的2倍以上,则正告 if (bitmap.getWidth() >= (width <<1) && bitmap.getHeight() >= (height <<1)) { warn(bitmap.getWidth(), bitmap.getHeight(), width, height, new RuntimeException("Bitmap size too large")); } } else { // 3、当宽高度等于0时,阐明ImageView还没有进行绘制,应用ViewTreeObserver进行大图检测的解决。 final Throwable stackTrace = new RuntimeException(); view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { int w = view.getWidth(); int h = view.getHeight(); if (w > 0 && h > 0) { if (bitmap.getWidth() >= (w <<1) && bitmap.getHeight() >= (h <<1)) { warn(bitmap.getWidth(), bitmap.getHeight(), w, h, stackTrace); } view.getViewTreeObserver().removeOnPreDrawListener(this); } return true; } }); } } } } private static void warn(int bitmapWidth, int bitmapHeight, int viewWidth, int viewHeight, Throwable t) { String warnInfo = "Bitmap size too large: " + "\n real size: (" + bitmapWidth + &#039;,&#039; + bitmapHeight + &#039;)&#039; + "\n desired size: (" + viewWidth + &#039;,&#039; + viewHeight + &#039;)&#039; + "\n call stack trace: \n" + Log.getStackTraceString(t) + &#039;\n&#039;; LogHelper.i(warnInfo); } }
首先,在正文1处,咱们重写了 ImageHook 的 afterHookedMethod 办法,拿到了以后的 ImageView 和要设置的 Bitmap 对象。而后,在正文2处,如果以后 ImageView 的宽高大于0,咱们便进行大图检测的解决:ImageView 的宽高都大于 View 的2倍以上,则正告。接着,在正文3处,如果以后 ImageView 的宽低等于0,则阐明 ImageView 还没有进行绘制,则应用 ImageView 的 ViewTreeObserver 获取其宽高进行大图检测的解决。至此,咱们的大图检测检测组件就曾经实现了。
首先咱们来理解一下这里的 反复图片 所指的概念: 即 Bitmap 像素数据完全一致,然而有多个不同的对象存在。
反复图片检测的原理其实就是 应用内存 Hprof 剖析工具,主动将反复 Bitmap 的图片和援用堆栈输入。
应用非常简单,只须要批改 Main 类的 main 办法的第一行代码,如下所示:
// 设置咱们本人 App 中对应的 hprof 文件门路 String dumpFilePath = "//Users//quchao//Documents//heapdump//memory-40.hprof";
而后,咱们执行 main 办法即可在 //Users//quchao//Documents//heapdump 这个门路下看到生成的 images 文件夹,外面保留了我的项目中检测进去的反复的图片。images 目录如下所示:
留神:须要应用 8.0 以下的机器,因为 8.0 及当前 Bitmap 中的 buffer 已保留在 native 内存之中。
具体的实现能够细分为如下三个步骤:
其中,获取堆栈 的信息也能够间接应用 haha 库来进行获取。这里简略说一下 应用 haha 库获取堆栈的流程,其具体能够细分为八个步骤,如下所示:
为了建设全局的 Bitmap 监控,咱们必须 对 Bitmap 的调配和回收 进行追踪。咱们先来看看 Bitmap 有哪些特点:
依据以上特点,咱们能够建设一套 Bitmap 的高性价比监控组件:
这个计划的 性能耗费很低,能够在 正式环境 中进行。然而,须要留神的一点是,正式与测试环境须要采纳不同水平的监控。
要建设线上利用的内存监控体系,咱们须要 先获取 App 的 DalvikHeap 与 NativeHeap,它们的获取形式可归结为如下四个步骤:
对于监控场景,咱们须要将其划分为两大类,如下所示:
依据 斐波那契数列 每隔一段时间(max:30min)获取内存的应用状况。惯例内存的监控办法有多种实现形式,上面,咱们依照 我的项目晚期 => 壮大期 => 成熟期 的惯例内存监控形式进行 演进式 解说。
具体应用 Debug.dumpHprofData() 实现。
其实现的流程为如下四个步骤:
然而,这种形式有如下几个毛病:
在应用 LeakCanary 的时候咱们须要 预设透露狐疑点,一旦发现透露进行回传。但这种实现形式毛病比拟显著,如下所示:
定制 LeakCanary 其实就是对 haha组件 来进行 定制。haha库是 square 出品的一款 主动剖析Android堆栈的java库。
对于haha库,它的 根本用法 个别遵循为如下四个步骤:
File heapDumpFile = ... Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
DataBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
Snapshot snapshot = Snapshot.createSnapshot(buffer);
ClassObj someClass = snapshot.findClass("com.example.SomeClass");
咱们在实现线上版的LeakCanary的时候次要要解决的问题有三个,如下所示:
在实现了线上版的 LeakCanary 之后,就须要 将线上版的 LeakCanary 与服务器和前端页面联合 起来。具体的 内存透露监控闭环流程 如下所示:
此外,在实现 图片内存监控 的过程中,应留神 两个关键点,如下所示:
对于低内存的监控,通常有两种形式,别离如下所示:
为了精确掂量内存性能,咱们须要引入一系列的内存监控指标,如下所示:
内存 UV 异样率 = PSS 超过 400MB 的 UV / 采集UV PSS 获取:调用 Debug.MemoryInfo 的 API 即可
如果呈现 新的内存使用不当或内存透露 的场景,这个指标会有所 上涨。
内存 UV 触顶率 = Java 堆占用超过最大堆限度的 85% 的 UV / 采集UV
计算触顶率的代码如下所示:
long javaMax = Runtime.maxMemory(); long javaTotal = Runtime.totalMemory(); long javaUsed = javaTotal - runtime.freeMemory(); float proportion = (float) javaUsed / javaMax;
如果超过 85% 最大堆 的限度,GC 会变得更加 频发,容易造成 OOM 和 卡顿。
在具体实现的时候,客户端 尽量只负责 上报数据,而 指标值的计算 能够由 后盾 来计算。这样便能够通过 版本比照 来监控是否有 新增内存问题。因而,建设线上内存监控的残缺计划 至多须要蕴含以下四点:
每个线程初始化都须要 mmap 肯定的栈大小,在默认状况下初始化一个线程须要 mmap 1MB 左右的内存空间。
在 32bit 的利用中有 4g 的 vmsize,理论能应用的有 3g+,这样一个过程 最大能创立的线程数 能够达到 3000个,然而,linux 对每个过程可创立的线程数也有肯定的限度(/proc/pid/limits),并且,不同厂商也能批改这个限度,超过该限度就会 OOM。
因而,对线程数量的限度,在肯定水平上能够 无效地防止 OOM 的产生。那么,实现一套 全局的线程监控组件 便是 迫不及待 的了。
在线下或灰度的环境下通过一个定时器每隔 10分钟 dump 出利用所有的线程相干信息,当线程数超过以后阈值时,则将以后的线程信息上报并预警。
通过 Debug.startAllocCounting 来监控 GC 状况,留神有肯定 性能影响。
在 Android 6.0 之前 能够拿到 内存调配次数和大小以及 GC 次数,其对应的代码如下所示:
long allocCount = Debug.getGlobalAllocCount(); long allocSize = Debug.getGlobalAllocSize(); long gcCount = Debug.getGlobalGcInvocationCount();
并且,在 Android 6.0 及之后 能够拿到 更精准 的 GC 信息:
Debug.getRuntimeStat("art.gc.gc-count"); Debug.getRuntimeStat("art.gc.gc-time"); Debug.getRuntimeStat("art.gc.blocking-gc-count"); Debug.getRuntimeStat("art.gc.blocking-gc-time");
对于 GC 信息的排查,咱们个别关注 阻塞式GC的次数和耗时,因为它会 暂停线程,可能导致利用产生 卡顿。倡议 仅对重度场景应用。
美团的 Android 内存透露自动化链路剖析组件 Probe 在 OOM 时会生成 Hprof 内存快照,而后,它会通过 独自过程 对这个 文件 做进一步 剖析。
它的毛病比拟多,具体为如下几点:
在实现自动化链路剖析组件 Probe 的过程中次要要解决两个问题,如下所示:
剖析过程占用的内存 跟 内存快照文件的大小 不成正相干,而跟 内存快照文件的 Instance 数量 呈 正相干。所以在开发过程中咱们应该 尽可能排除不须要的Instance实例。
Prope 的 总体架构图 如下所示:
而它的整个剖析流程具体能够细分为八个步骤,如下所示:
解析后的 Snapshot 中的 Heap 有四种类型,具体为:
解析完 后应用了 计数压缩策略,对 雷同的 Instance 应用 计数,以 缩小占用内存。超过计数阈值的须要计入计数桶(计数桶记录了 抛弃个数 和 每个 Instance 的大小)。
如果对象是 根底数据类型,会将 本身的 RetainSize 累加到父节点 上,将 怀疑对象 替换为它的 父节点。
应用计数弥补策略计算 RetainSize,次要是 判断对象是否在计数桶中,如果在的话则将 抛弃的个数和大小弥补到对象上,累积计算RetainSize,最初对 RetainSize 排序以查找可疑对象。
在配置的时候要留神两个问题:
具体的应用步骤如下所示:
12-26 10:54:03.963 30450-30450/com.dodola.alloctrack I/AllocTracker: ====current alloc count 388=====
12-26 10:54:03.963 30450-30450/com.dodola.alloctrack I/AllocTracker: ====current alloc count 388===== 12-26 10:56:45.103 30450-30450/com.dodola.alloctrack I/AllocTracker: saveARTAllocationData write file to /storage/emulated/0/crashDump/1577329005
java -jar tools/DumpPrinter-1.0.jar dump文件门路 > dump_log.txt
Found 4949 records: tid=1 byte[] (94208 bytes) dalvik.system.VMRuntime.newNonMovableArray (Native method) android.graphics.Bitmap.nativeCreate (Native method) android.graphics.Bitmap.createBitmap (Bitmap.java:975) android.graphics.Bitmap.createBitmap (Bitmap.java:946) android.graphics.Bitmap.createBitmap (Bitmap.java:913) android.graphics.drawable.RippleDrawable.updateMaskShaderIfNeeded (RippleDrawable.java:776) android.graphics.drawable.RippleDrawable.drawBackgroundAndRipples (RippleDrawable.java:860) android.graphics.drawable.RippleDrawable.draw (RippleDrawable.java:700) android.view.View.getDrawableRenderNode (View.java:17736) android.view.View.drawBackground (View.java:17660) android.view.View.draw (View.java:17467) android.view.View.updateDisplayListIfDirty (View.java:16469) android.view.ViewGroup.recreateChildDisplayList (ViewGroup.java:3905) android.view.ViewGroup.dispatchGetDisplayList (ViewGroup.java:3885) android.view.View.updateDisplayListIfDirty (View.java:16429) android.view.ViewGroup.recreateChildDisplayList (ViewGroup.java:3905)
在 Android 8.0 及之后,能够应用 Address Sanitizer、Malloc 调试和 Malloc 钩子 进行 native 内存剖析,
对于线下 Native 内存透露监控的建设,次要针对 是否能重编 so 的状况 来记录调配的内存信息。
设置内存兜底策略的目标,是为了 在用户无感知的状况下,在靠近触发零碎异样前,抉择适合的场景杀死过程并将其重启,从而使得利用内存占用回到失常状况。
通常执行内存兜底策略时至多须要满足六个条件,如下所示:
只有在满足了以上条件之后,咱们才会去杀死以后主过程并通过 push 过程从新拉起及初始化。
除了在 Android性能优化之内存优化 => 优化内存空间中解说过的一些惯例的内存优化策略以外,在上面列举了一些更深刻的内存优化策略。
对于 Android 2.x 零碎,应用反射将 BitmapFactory.Options 外面暗藏的 inNativeAlloc 关上。
对于 Android 4.x 零碎,应用或借鉴 Fresco 将 bitmap 资源在 native 中调配的形式。
应用 Glide、Fresco 等图片加载库,通过定制,在加载 bitmap 时,若产生 OOM,则应用 try catch 将其捕捉,而后革除图片 cache,尝试升高 bitmap format(ARGB8888、RGB565、ARGB4444、ALPHA8)。
须要留神的是,OOM 是能够捕捉的,只有 OOM 是由 try 语句中的对象申明所导致的,那么在 catch 语句中,是能够开释掉这些对象,解决 OOM 的问题的。
计算以后利用内存占最大内存的比例的代码如下:
max = Runtime.getRuntime().maxMemory(); available = Runtime.getRuntime.totalMemory() - Runtime.getFreeMemory(); ratio = available / max;
显示地除去利用的 memory,以减速内存收集过程的代码如下所示:
WindowManagerGlobal.getInstance().startTrimMemory(TRIM_MEMORY_COMPLETE);
当用户切换到其它利用并且你的利用 UI 不再可见时,应该开释利用 UI 所占用的所有内存资源。这可能显著减少零碎缓存过程的能力,可能晋升用户体验。
在所有 UI 组件都暗藏的时候会接管到 Activity 的 onTrimMemory() 回调并带有参数 TRIM_MEMORY_UI_HIDDEN。
在 Activity 的 onDestory 中递归开释其援用到的 Bitmap、DrawingCache 等资源,以升高产生内存透露时对利用内存的压力。
LeakCanary 的 AndroidExcludeRefs 列出了一些因为零碎起因导致援用无奈开释的例子,可应用相似 Hack 的形式去修复。
文章转自 https://juejin.cn/post/6844904099998089230 如有侵权,请分割删除。
Android 性能优化学习【一】:APK瘦身优化_哔哩哔哩_bilibili
Android 性能优化学习【二】:APP启动速度优化_哔哩哔哩_bilibili
Android 性能优化学习【三】:如何解决OOM问题_哔哩哔哩_bilibili
Android 性能优化学习【四】:UI卡顿优化_哔哩哔哩_bilibili