这篇文章的内容是我回顾和再学习 Android 内存优化的过程当中整理出来的,整理的目的是让我本身对 Android 内存优化相关知识的认识更全面一些,分享的目的是但愿你们也能从这些知识中获得一些启发。java
Android 应用运行在 Dalvik 虚拟机上,而 Dalvik 虚拟机是基于 JVM 优化而来的,所以只有了解了 Java 的内存管理机制,才能更好地理解 Android 的内存管理机制,若是你对这一块还不熟悉的话,能够看个人上一篇文章 探索 Java 内存管理机制。android
本文的内容可分为两部分,第一部分讲的是 Android 内存管理机制相关的一些知识,第二部分讲的是内存问题的解决与内存优化方法,你们能够根据本身的须要选择性地阅读。面试
内存优化就是对内存问题的一个预防和解决,作内存优化能让应用挂得少、活得好和活得久。算法
“挂”指的是 Crash,假如一个满分的应用是 100 分,那么一个会 Crash 的应用在用户内心会扣掉 90 分。shell
就像是咱们在一家店吃到了一盘很难吃的小龙虾,哪怕别人说这家店再好吃,咱们之后都不想吃这家店了。数据库
致使 Android 应用 Crash 的缘由有不少种,而作内存优化就能让咱们的应用避免由内存问题引发的 Crash。缓存
内存问题致使 Crash 的具体表现就是内存溢出异常 OOM,引发 OOM 的缘由有多种,在后面我会对它们作一个更详细的介绍。服务器
活得好指的是使用流畅,Android 中形成界面卡顿的缘由有不少种,其中一种就是由内存问题引发的。网络
内存问题之因此会影响到界面流畅度,是由于垃圾回收(GC,Garbage Collection),在 GC 时,全部线程都要中止,包括主线程,当 GC 和绘制界面的操做同时触发时,绘制的执行就会被搁置,致使掉帧,也就是界面卡顿。数据结构
关于 GC 的更多介绍,能够看个人上一篇文章。
活得久指的是咱们的应用在后台运行时不会被干掉。
Android 会按照特定的机制清理进程,清理进程时优先会考虑清理后台进程。
清理进程的机制就是低杀,关于低杀在后面会有更详细的介绍。
假如如今有个用户小张想在咱们的电商应用买一个商品,千辛万苦挑到了一个本身喜欢的商品后,当他准备购买时,小张的老婆叫他去给孩子换尿布,等小张再打开应用时,发现商品页已经被关闭了,也就是应用被干掉了,这时小张又想起了孩子的奶粉钱,可能就放弃此次购买了。
用户在移动设备上使用应用的过程当中被打断是很常见的,若是咱们的应用不能活到用户回来的时候,要用户再次进行操做的体验就会不好。
要了解 Android 应用的内存管理机制,就要了解承载着 Android 应用的虚拟机 Dalvik,虽然 Android 如今是使用的 ART 来承载应用的执行,可是 ART 也是基于 Dalvik 优化而来的。
Dalvik 是 Dalvik Virtual Machine(Dalvik 虚拟机)的简称,是 Android 平台的核心组成部分之一,Dalvik 与 JVM 的区别有以下几个。
JVM 是基于栈的,也就是须要在栈中读取数据,所需的指令会更多,这样会致使速度慢,不适合性能优先的移动设备。
Dalvik 是基于寄存器的,指令更紧凑和简洁。
因为显式指定了操做数,因此基于寄存器的指令会比基于栈的指令要大,可是因为指令数的减小,总的代码数不会增长多少。
在 Java SE 程序中,Java 类会被编译成一个或多个 .class 文件,而后打包成 jar 文件,JVM 会经过对应的 .class 文件和 jar 文件获取对应的字节码。
而 Dalvik 会用 dx 工具将全部的 .class 文件转换为一个 .dex 文件,而后会从该 .dex 文件读取指令和数据。
Dalvik 由 Zygote 孵化器建立,Zygote 自己也是一个 Dalvik VM 进程,当系统须要建立一个进程时,Zygote 就会进行 fork,快速建立和初始化一个 DVM 实例。
对于一些只读的系统库,全部的 Dalvik 实例都能和 Zygote 共享一块内存区域,这样能节省内存开销。
在 Androd 中,每个应用都运行在一个 Dalvik VM 实例中,每个 Dalvik VM 都运行在一个独立的进程空间,这种机制使得 Dalvik 运行在有限的内存中同时运行多个进程。
Dalvik 拥有预加载—共享机制,不一样应用之间在运行时能够共享相同的类,拥有更高的效率。
而 JVM 不存在这种共享机制,不一样的程序,打包后的程序都是彼此独立的,即便包中使用了一样的类,运行时也是单独加载和运行的,没法进行共享。
Dalvik 不是 Java 虚拟机,它并非按照 Java 虚拟机规范实现的,二者之间并不兼容。
每个手机厂商均可以设定设备中每个进程可以使用的堆大小,设置进程堆大小的值有下面三个。
堆分配的初始值大小,这个值越小,系统内存消耗越慢,可是当应用扩展这个堆,致使 GC 和堆调整时,应用会变慢。
这个值越大,应用越流畅,可是可运行的应用也会相对减小。
若是在清单文件中声明 largeHeap 为 true,则 App 使用的内存到 heapsize 才会 OOM,不然达到 heapgrowthlimit 就会 OOM。
进程可用的堆内存最大值,一旦应用申请的内存超过这个值,就会 OOM。
假如咱们想看其中的一个值,咱们能够经过命令查看,好比下面这条命令。
adb shell getprop dalvik.vm.heapsize
ART 的全称是 Android Runtime,是从 Android 4.4 开始新增的应用运行时环境,用于替代 Dalvik 虚拟机。
Dalvik VM 和 ART 均可以支持已转换为 .dex(Dalvik Executable)格式的 Java 应用程序的运行。
ART 与 Dalvik 的区别有下面几个。
Dalvik 中的应用每次运行时,字节码都须要经过即时编译器 JIT 转换为机器码,这会使得应用的运行效率下降。
在 ART 中,系统在安装应用时会进行一次预编译,将字节码预先编译成机器码并存储在本地,这样应用就不用在每次运行时执行编译了,运行效率也大大提升。
在 Dalvik 采用的垃圾回收算法是标记-清除算法,启动垃圾回收机制会形成两次暂停(一次在遍历阶段,另外一次在标记阶段)。
而在 ART 下,GC 速度比 Dalvik 要快,这是由于应用自己作了垃圾回收的一些工做,启动 GC 后,再也不是两次暂停,而是一次暂停。
并且 ART 使用了一种新技术(packard pre-cleaning),在暂停前作了许多事情,减轻了暂停时的工做量。
Dalvik 是为 32 位 CPU 设计的,而 ART 支持 64 位并兼容 32 位 CPU,这也是 Dalvik 被淘汰的主要缘由。
在 Android 中有一个心狠手辣的杀手,要想让咱们的应用活下来,就要在开发应用时格外当心。
不过咱们也不用太担忧,由于它只杀“坏蛋”,只要咱们不使坏,那它就不会对咱们下手。
这个杀手叫低杀,它的全名是 Low Memory Killer。
低杀跟垃圾回收器 GC 很像,GC 的做用是保证应用有足够的内存可使用,而低杀的做用是保证系统有足够的内存可使用。
GC 会按照引用的强度来回收对象,而低杀会按照进程的优先级来回收资源,下面咱们就来看看 Android 中的几种进程优先级。
在 Android 中不一样的进程有着不一样的优先级,当两个进程的优先级相同时,低杀会优先考虑干掉消耗内存更多的进程。
也就是若是咱们应用占用的内存比其余应用少,而且处于后台时,咱们的应用能在后台活下来,这也是内存优化为咱们应用带来竞争力的一个直接体现。
当用户经过屡次点击达到一个页面,而后又打开了其余应用时,这时咱们的应用处于后台,若是咱们的应用在后台能活下来,意味着当用户再次启动咱们的应用时,不须要再次进行这个繁琐的操做。
前台进程(Foreground Process)是优先级最高的进程,是正在于用户交互的进程,若是知足下面一种状况,则一个进程被认为是前台进程。
进程持有一个与用户交互的 Activity(该 Activity 的 onResume 方法被调用)
进程持有一个 BroadcastReceiver,这个 BroadcastReceiver 正在执行它的 onReceive() 方法
可见进程(Visible Process)不含有任何前台组件,但用户还能再屏幕上看见它,当知足一下任一条件时,进程被认定是可见进程。
进程持有一个 Activity,这个 Activity 处于 pause 状态,好比前台 Activity 打开了一个对话框,这样后面的 Activity 就处于 pause 状态
进程持有一个 Service 这个 Service 和一个可见的 Activity 绑定。
可见进程是很是重要的进程,除非前台进程已经把系统的可用内存耗光,不然系统不会终止可见进程。
服务进程(Service Process)可能在播放音乐或在后台下载文件,除非系统内存不足,不然系统会尽可能维持服务进程的运行。
当一个进程知足下面一个条件时,系统会认定它为服务进程。
若是一个进程中运行着一个 Service,而且这个 service 是经过 startService 开启的,那这个进程就是一个服务进程。
当一个进程知足下面条件时,系统会认定它为后台进程。
当进程持有一个用户不可见的 Activity(Activity 的 onStop() 方法被调用),可是 onDestroy 方法没有被调用,这个进程就会被系统认定为后台进程。
系统会把后台进程保存在一个 LruCache 列表中,由于终止后台进程对用户体验影响不大,因此系统会酌情清理部分后台进程。
你能够在 Activity 的 onSaveInstanceState() 方法中保存一些数据,以避免在应用被系统清理掉后,用户已输入的信息被清空,致使要从新输入。
当一个进程不包含任何活跃的应用组件,则被系统认定为是空进程。
系统保留空进程的目的是为了加快下次启动进程的速度。
大部分 App 都免不了使用大量的图片,好比电商应用和外卖应用等。
图片在 Android 中对应的是 Bitmap 和 Drawable 类,咱们从网络上加载下来的图片最终会转化为 Bitmap。
图片会消耗大量内存,若是使用图片不当,很容易就会形成 OOM。
下面咱们来看下 Bitmap 与内存有关的一些内容。
Bitmap 提供了一个 getByteCount() 方法获取图片占用的内存大小,可是这个方法只能在程序运行时动态计算。
图片占用内存公式:宽 高 一个像素占用的内存。
假如咱们如今有一张 2048 2048 的图片,而且编码格式为 ARGB_8888,那么这个图片的大小为 2048 2048 * 4 = 16, 777, 216 个字节,也就是 16M。
若是厂商给虚拟机设置的堆大小是 256M,那么像这样的图片,应用最极限的状况只能使用 16 张。
咱们的应用在运行时,不只仅是咱们本身写的代码须要消耗内存,还有库中建立的对象一样须要占用堆内存,也就是别说 16 张,多来几张应用就挂了。
一张图片中每个像素的大小取决于它的解码选项,而 Android 中可以选择的 Bitmap 解码选项有四种。
下面四种解码选项中的的 ARGB 分别表明透明度和三原色 Alpha、Red、Green、Blue。
ARGB 四个通道的值都是 8 位,加起来 32 位,也就是每一个像素占 4 个字节
ARGB 四个通道的值都是 4 位,加起来 16 位,也就是每一个像素占 2 个字节
RGB 三个通道分别是 5 位、6 位、5 位,加起来 16 位,也就是每一个像素占 2 个字节
只有 A 通道,占 8 位,也就是每一个像素占 1 个字节
若是服务器返回给咱们的图片是 200 200,可是咱们的 ImageView 大小是 100 100,若是直接把图片加载到 ImageView 中,那就是一种内存浪费。
可是使用的 Glide 的话,那这个问题就不用担忧了,由于 Glide 会根据 ImageView 的大小把图片大小调整成 ImageView 的大小加载图片,而且 Glide 有三级缓存,在内存缓存中,Glide 会根据屏幕大小选择合适的大小做为图片内存缓存区的大小。
内存泄漏指的是,当一块内存没有被使用,但没法被 GC 时的状况。
堆中一块泄漏的内存就像是地上一块扫不掉的口香糖,都很让人讨厌。
一个典型的例子就是匿名内部类持有外部类的引用,外部类应该被销毁时,GC 却没法回收它,好比在 Activity 中建立 Handler 就有可能出现这种状况。
内存泄漏的表现就是可用内存逐渐减小,好比下图中是一种比较严重的内存泄漏现象,没法被回收的内存逐渐累积,直到无更多可用内存可申请时,就会致使 OOM。
常见的形成内存泄漏的缘由有以下几个。
非静态内部类会持有外部类的实例,好比匿名内部类。
匿名内部类指的是一个没有人类可识别名称的类,可是在字节码中,它仍是会有构造函数的,而它的构造函数中会包含外部类的实例。
好比在 Activity 中以匿名内部类的方式声明 Handler 或 AsyncTask,当 Activity 关闭时,因为 Handler 持有 Activity 的强引用,致使 GC 没法对 Activity 进行回收。
当咱们经过 Handler 发送消息时,消息会加入到 MessageQueue 队列中交给 Looper 处理,当有消息还没发送完毕时,Looper 会一直运行,在这个过程当中会一直持有 Handler,而 Handler 又持有外部类 Activity 的实例,这就致使了 Activity 没法被释放。
咱们能够把 Handler 或 AsyncTask 声明为静态内部类,而且使用 WeakReference 包住 Activity,这样 Handler 拿到的就是一个 Activity 的弱引用,GC 就能够回收 Activity。
这种方式适用于全部匿名内部类致使的内存泄漏问题。
public static class MyHandler extends Handler { Activity activity; public MyHandler(Activity activity) { activity = new WeakReference<>(activity).get(); } @Override public void handleMessage(Message message) { // ... } }
静态变量致使内存泄漏的缘由是由于长生命周期对象持有了短生命周期对象的引用,致使短生命周期对象没法被释放。
好比一个单例持有了 Activity 的引用,而 Activity 的生命周期可能很短,用户一打开就关闭了,可是单例的生命周期每每是与应用的生命周期相同的。
若是单例须要 Context, 能够考虑使用 ApplicationContext,这样单例持有的 Context 引用就是与应用的生命周期相同的了。
不一样的 Android 版本的 Webview 会有差别,加上不一样厂商定制 ROM 的 Webview 的差别,致使 Webview 存在很大的兼容问题。
通常状况下,在应用中只要使用一次 Webview,它占用的内存就不会被释放。
WebView内存泄漏--解决方法小结
当咱们在短期内频繁建立大量临时对象时,就会引发内存抖动,好比在一个 for 循环中建立临时对象实例。
下面这张图就是内存抖动时的一个内存图表现,它的形状是锯齿形的,而中间的垃圾桶表明着一次 GC。
这个是 Memory Profiler 提供的内存实时图,后面会对 Memory Profiler 进行一个更详细的介绍。
Profiler 是 Android Studio 为咱们提供的性能分析工具,它包含了 CPU、内存、网络以及电量的分析信息,而 Memory Profiler 则是 Profiler 中的其中一个版块。
打开 Profiler 有下面三种方式。
打开 Profiler 后,能够看到下面这样的面板,而在左边的 SESSIONS 面板的右上角,有一个加号,在这里能够选择咱们想要进行分析的应用。
打开了高级选项后,咱们在 Memory Profiler 中就能看到用一个白色垃圾桶表示的 GC 动做。
打开 Profiler 的方式:Run > Edit Configucation > Profiling > Enable advanced profiling
Memory Profiler 是 Profiler 的其中一个功能,点击 Profiler 中蓝色的 Memory 面板,咱们就进入了 Memory Profiler 界面。
在堆转储(Dump Java Heap)面板中有 Instance View(实例视图)面板,Instance View 面板的下方有 References 和 Bitmap Preview 两个面板,经过 Bitmap Preview,咱们能查看该 Bitmap 对应的图片是哪一张,经过这种方式,很容易就能找到图片致使的内存问题。
要注意的是,Bitmap Preview 功能只有在 7.1 及如下版本的设备中才能使用。
在 7.1 及如下版本的设备中,能够经过 Record 按钮记录一段时间内的内存分配状况。
而在 8.0 及以上版本的设别中,能够经过拖动时间线来查看一段时间内的内存分配状况。
点击 Record 按钮后,Profiler 会为咱们记录一段时间内的内存分配状况。在内存分配面板中,咱们能够查看对象的分配的位置,好比下面的 Bitmap 就是在 onCreate 方法的 22 行建立的。
对于内存泄漏问题,Memory Profiler 只能给咱们提供一个简单的分析,不可以帮咱们确认具体发生问题的地方。
而 MAT 就能够帮咱们作到这一点,MAT 的全称是 Memory Analyzer Tool,它是一款功能强大的 Java 堆内存分析工具,能够用于查找内存泄漏以及查看内存消耗状况。
要想经过 MAT 分析内存泄漏,咱们作下面几件事情。
使用命令将 Memory Profiler 中导出来的 hprof 文件转换为 MAT 能够解析的 hprof 文件,命令以下
platform-tools hprof-conv ../原始文件.hprof ../输出文件.hprof
若是在 mac 上配置 platform-tools 不成功的话,能够直接定位到 Android SDK 下的 platform-tools 目录,直接使用 hprof-conv 工具,命令以下
hprof-conv -z ../原始文件.hprof ../输出文件.hprof
我在项目中定义了一个静态的回调列表 sCallbacks,而且把 MemoryLeakActivity 添加到了这个列表中,而后反复进出这个 Activity,咱们能够看到这个 Activity 的实例有 8 个,这就属于内存泄漏现象,下面咱们来看下怎么找出这个内存泄漏。
首先,按 8.3 小节的步骤打开咱们的堆转储文件,打开后,咱们能够看到 MAT 为咱们分析的一个预览页。
打开左上角的直方图,咱们能够看到一个类列表,输入咱们想搜索的类,就能够看到它的实例数。
咱们右键 MemoryLeakActivity 类,选择 List Objects > with incoming references 查看这个 Activity 的实例。
点击后,咱们能看到一个实例列表,再右键其中一个实例,选择 Path to GC Roots > with all references 查看该实例被谁引用了,致使没法回收。
选择 with all references 后,咱们能够看到该实例被静态对象 sCallbacks 持有,致使没法被释放。
这样就完成了一次内存泄漏的分析。
若是使用 MAT 来分析内存问题,会有一些难度,并且效率也不是很高。
为了能迅速发现内存泄漏,Square 公司基于 MAT 开源了 LeakCanary。
LeakCanary 是一个内存泄漏检测框架。
LeakCanary 是基于 LeakSentry 开发的,LeakSentry 会 hook Android 声明周期,而且会自动检测当 Activity 或 Fragment 被销毁时,它们的实例是否被回收了。
销毁的实例会传给 RefWatcher,RefWatcher 会持有它们的弱引用。
你也能够观察全部再也不须要的实例,好比一个再也不使用的 View,再也不使用的 Presenter 等。
若是等待了 5 秒,而且 GC 触发了以后,弱引用尚未被清理,那么 RefWatcher 观察的实例就可能处于内存泄漏状态了。
当保留实例(Retained Instance)的数量达到了一个阈值,LeakCanary 会进行堆转储,并把数据放进 hprof 文件中。
当 App 可见时,这个阈值是 5 个保留实例,当 App 不可见时,这个阈值是 1 个保留实例。
LeakCanary 会解析 hprof 文件,而且找出致使 GC 没法回收实例的引用链,这也就是泄漏踪影(Leak Trace)。
泄漏踪影也叫最短强引用路径,这个路径是 GC Roots 到实例的路径。
当有两个泄漏分析结果相同时,LeakCanary 会根据子引用链来判断它们是不是同一个缘由致使的,若是是的话,LeakCanary 会把它们归为同一组,以避免重复显示一样的泄漏信息。
添加依赖
dependencies { // 使用 debugImplementation 是由于 LeakCanary 通常不用于发布版本
}
2. 监控特定对象
// 1. 在 Application 中定义一个 RefWatcher 的静态变量companion object { val refWatcher = LeakSentry.refWatcher} // 2. 使用 RefWatcher 监控该对象MyApplication.refWatcher.watch(object); ```
配置监控选项
private fun initLeakCanary() { LeakSentry.config = LeakSentry.config.copy(watchActivities = false)}
添加依赖
dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'
// 只有在你使用了 support library fragments 的时候才须要下面这一项
debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3'
}
2. 初始化 LeakCanary
public class MyApplication extends Application {
@Override public void onCreate() {
super.onCreate(); // 不须要再 LeakCanary 用来作堆分析的进程中初始化 LeakCanary if (!LeakCanary.isInAnalyzerProcess(this)) { LeakCanary.install(this); return; } ```
监控特定对象
// 1. 在 Application 中定义一个获取 RefWatcher 的静态方法
public static RefWatcher getRefWatcher() {
return LeakCanary.installedRefWatcher();
}
// 2. 使用 RefWatcher 监控该对象
MyApplication.getRefWatcher().watch(object);
4. 配置监控选项
public class MyApplication extends Application { private void installLeakCanary() { RefWatcher refWatcher = LeakCanary.refWatcher(this) .watchActivities(false) .buildAndInstall(); }
}
当安装完成,而且从新安装了应用后,咱们能够在桌面看到 LeakCanary 用于分析内存泄漏的应用。 下面这两张图中,第一个是 LeakCanary 为非 AndroidX 项目安装的应用,第二个是 LeakCanary 为 AndroidX 项目安装的应用。  ### **11.4 使用 LeakCanary 分析内存泄漏** 下面是一个静态变量持有 Activity 致使 Activity 没法被释放的一个例子。
public class MemoryLeakActivity extends AppCompatActivity {
public static List<Activity> activities = new ArrayList<>(); @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); activities.add(this); }
}
咱们能够在 Logcat 中看到泄漏实例的引用链。  除了 Logcat,咱们还能够在 Leaks App 中看到引用链。 点击桌面上 LeakCanary 为咱们安装的 Leaks 应用后,能够看到 activities 变量,之因此在这里会显示这个变量,是由于 LeakCanary 分析的结果是这个变量持有了某个实例,致使该实例没法被回收。  点击这一项泄漏信息,咱们能够看到一个泄漏信息概览页。  咱们点击第一项 MemoryActivity Leaked,能够看到泄漏引用链的详情。  经过上面这些步骤,很简单地就能找到 LeakCanary 为咱们分析的致使内存泄漏的地方。 ## **12\. 怎么获取和监听系统内存状态?** Android 提供了两种方式让咱们能够监听系统内存状态,下面咱们就来看看这两种方式的用法。 ### **12.1 ComponentCallback2** 在 Android 4.0 后,Android 应用能够经过在 Activity 中实现 ComponentCallback2 接口获取系统内存的相关事件,这样就能在系统内存不足时提早知道这件事,提早作出释放内存的操做,避免咱们本身的应用被系统干掉。 ComponentCallnback2 提供了 onTrimMemory(level) 回调方法,在这个方法里咱们能够针对不一样的事件作出不一样的释放内存操做。
import android.content.ComponentCallbacks2
class MainActivity : AppCompatActivity(), ComponentCallbacks2 {
/** * 当应用处于后台或系统资源紧张时,咱们能够在这里方法中释放资源, * 避免被系统将咱们的应用进行回收 * @param level 内存相关事件 */ override fun onTrimMemory(level: Int) { // 根据不一样的应用生命周期和系统事件进行不一样的操做 when (level) { // 应用界面处于后台 ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN -> { // 能够在这里释放 UI 对象 } // 应用正常运行中,不会被杀掉,可是系统内存已经有点低了 ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE, // 应用正常运行中,不会被杀掉,可是系统内存已经很是低了, // 这时候应该释放一些没必要要的资源以提高系统性能 ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW, // 应用正常运行,可是系统内存很是紧张, // 系统已经开始根据 LRU 缓存杀掉了大部分缓存的进程 // 这时候咱们要释放全部没必要要的资源,否则系统可能会继续杀掉全部缓存中的进程 ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL -> { // 释放资源 } // 系统内存很低,系统准备开始根据 LRU 缓存清理进程, // 这时咱们的程序在 LRU 缓存列表的最近位置,不太可能被清理掉, // 可是也要去释放一些比较容易恢复的资源,让系统内存变得充足 ComponentCallbacks2.TRIM_MEMORY_BACKGROUND, // 系统内存很低,而且咱们的应用处于 LRU 列表的中间位置, // 这时候若是还不释放一些没必要要资源,那么咱们的应用可能会被系统干掉 ComponentCallbacks2.TRIM_MEMORY_MODERATE, // 系统内存很是低,而且咱们的应用处于 LRU 列表的最边缘位置, // 系统会有限考虑干掉咱们的应用,若是想活下来,就要把全部能释放的资源都释放了 ComponentCallbacks2.TRIM_MEMORY_COMPLETE -> { /* * 把全部能释放的资源都释放了 */ } // 应用从系统接收到一个没法识别的内存等级值, // 跟通常的低内存消息提醒同样对待这个事件 else -> { // 释放全部不重要的数据结构。 } } }
}
### **12.2 ActivityManager.getMemoryInfo()** Android 提供了一个 ActivityManager.getMemoryInfo() 方法给咱们查询内存信息,这个方法会返回一个 ActivityManager.MemoryInfo 对象,这个对象包含了系统当前内存状态,这些状态信息包括可用内存、总内存以及低杀内存阈值。 MemoryInfo 中包含了一个 lowMemory 布尔值,这个布尔值用于代表系统是否处于低内存状态。
fun doSomethingMemoryIntensive() {
// 在作一些须要不少内存的任务前, // 检查设备是否处于低内存状态、 if (!getAvailableMemory().lowMemory) { // 作须要不少内存的任务 }
}
// 获取 MemoryInfo 对象
private fun getAvailableMemory(): ActivityManager.MemoryInfo {
val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager return ActivityManager.MemoryInfo().also { memoryInfo -> activityManager.getMemoryInfo(memoryInfo) }
}
## **13\. 还有哪些内存优化技巧?** ### **13.1 使用更高效的代码结构** #### **13.1.1 谨慎使用 Service** (下面这些内容是我在 Andorid 官网上翻译的,从咱们的应用角度来讲,固然但愿是应用一直运行,这样用户每次打开都不用从新走各类初始化流程,可是对于系统来讲,咱们的这种行为伤害挺大的。) 让一个没用的 Service 在后台运行对于一个应用的内存管理来讲是一件最糟糕的事情。 要在 Service 的任务完成后中止它,否则 Service 占用的这块内存会泄漏。 当你的应用中运行着一个 Service,除非系统内存不足,不然它不会被干掉。 这就致使对于系统来讲 Service 的运行成本很高,由于 Service 占用的内存其余的进程是不能使用的。 Android 有一个缓存进程列表,当可用内存减小时,这个列表也会随之缩小,这就会致使应用间的切换变得很慢。 若是咱们是用 Service 监听一些系统广播,能够考虑使用 JobScheduler。 若是你真的要用 Service,能够考虑使用 IntentService,IntentService 是 Service 的一个子类,在它的内部有一个工做线程来处理耗时任务,当任务执行完后,IntentService 就会自动中止。 #### **13.1.2 选择优化后的数据容器** Java 提供的部分数据容器并不适合 Android,好比 HashMap,HashMap 须要中存储每个键值对都须要一个额外的 Entry 对象。 Android 提供了几个优化后的数据容器,包括 SparseArray、SparseBooleanArray 以及 LongSparseArray。 SparseArray 之因此更高效,是由于它的设计是只能使用整型做为 key,这样就避免了自动装箱的开销。 #### **13.1.3 当心代码抽象** 抽象能够优化代码的灵活性和可维护性,可是抽象也会带来其余成本。 抽象会致使更多的代码须要被执行,也就是须要更多的时间和把更多的代码映射到内存中。 若是某段抽象代码带来的好处不大,好比一个地方能够直接实现而不须要用到接口的,那就不用接口。 #### **13.1.4 使用 protobuf 做为序列化数据** Protocol buffers 是 Google 设计的,它能够对结构化的数据序列化,与 XML 相似,不过比 XML 更小,更快,并且更简单。 若是你决定使用 protobuf 做为序列化数据格式,那在客户端代码中应该使用轻量级的 protobuf。 由于通常的 protobuf 会生成冗长的代码,这样会致使内存增长、APK 大小增长,执行速度变慢等问题。 更多关于 protobuf 的信息能够查看 protobuf readme 中的 “轻量级版本” 。 ### **13.2 删除内存消耗大的资源和第三方库** 有些资源和第三方库会在咱们不知情的状况下大量消耗内存。 APK 大小,第三方库和嵌入式资源,会影响咱们应用的内存消耗,咱们能够经过删除冗余和没必要要的资源和第三方库来减小应用的内存消耗。 #### **13.2.1 Apk 瘦身** Bitmap 大小、资源、动画以及第三方库会影响到 APK 的大小,Android Studio 提供了 R8 和 ProGuard 帮助咱们缩小 Apk,去掉没必要要的资源。 若是你使用的 Android Studio 版本是 3.3 如下的,可使用 ProGuard,3.3 及以上版本的可使用 R8。 #### **13.2.2 使用 Dagger2 进行依赖注入** 依赖注入框架不只能够简化咱们的代码,并且能让咱们在测试代码的时候更方便。 若是咱们想在应用中使用依赖注入,能够考虑使用 Dagger2。 Dagger2 是在编译期生成代码,而不是用反射实现的,这样就避免了反射带来的内存开销,而是在编译期生成代码, #### **13.2.3 谨慎使用第三方库** 当你决定使用一个不是为移动平台设计的第三方库时,你须要对它进行优化,让它能更好地在移动设备上运行。 这些第三方库包括日志、分析、图片加载、缓存以及其余框架,都有可能带来性能问题。 ### Android开发资料+面试架构资料 免费分享 点击连接 便可领取 [《Android架构师必备学习资源免费领取(架构视频+面试专题文档+学习笔记)》](https://shimo.im/docs/VqoXZ2j9E04V4nhk/ )