RAM 对于软件开发环境而言是有价值的资源,但它对受限于物理内存限制的操作系统具有更大的价值。即使 Android Runtime 和 Dalvik virtual machein 执行常规的垃圾回收,但这并不意味着你可以忽略 app 在何时何地指派和释放内存。你仍然需要去避免产生内存泄露。比如长期持有静态成员变量常常引起内存泄露,你应该在合适的时间,比如在生命周期回调函数处释放一些引用对象,来避免内存泄露的发生。
这篇文章将阐述怎样在 app 中主动的降低内存消耗。关于 java 编程中清理资源的一般实践,请参照其他关于资源引用管理的书籍或者在线文档。如果你想分析一个运行中 app 的内存情况,请阅读文章 "Tools for analyzing RAM usage"。如果你想知道 AndroidRuntime 和 Dalvik virtual machine 如何管理内存,请参阅文章 "Overview of Android Memory Management"。
监控内存的使用情况:
Androidframework 和 Android Studio 能够帮助你分析和调整 app 的内存使用情况。Android framework 暴露了几个在 app 运行时动态降低内存消耗的 API。Android Studio 提供了几个观察 app 内存使用情况的工具。
分析内存使用的工具:
想要解决 app 产生的内存问题,我们首先得知道内存问题由什么引起。Android Studio 提供了如下几个分析内存的工具:
1."MemoryMonitor" 展示 app 在一个单独回话过程中如何指派内存。这个工具会实时绘制包括垃圾回收事件等可用内存和已占用内存的模拟图像。在程序运行时,你仅仅能通过初始化一个垃圾回收事件,获取 Java 堆快照。这个 Memory Monitor 的输出图像能帮你定位到 app 在什么地方因产生过多的垃圾回收事件导致程序变慢。
关于 Memory Monitor 的具体使用方法,请参阅 "ViewingHeap Updates"
2."AllocationTracker" 能够让我们详细的跟踪到 app 如何分配内存。这个工具能够记录 app 的内存分配情况,而且能够列出所有被分配的对象以及分析快照。用这个工具你能够追踪到分配太多对象的代码。
关于 AllocationTracker 的具体使用方法,请参阅 "Allocation Tracker Walkthrough"
在合适的时间释放内存
Android 设备在运行时的可用内存会根据物理内存总量以及用户操作方法不断变化。当系统内存有压力的情况下会发送信号来通知程序。app 应监听这些通知,并依据监听结果来调整内存使之使用适度。
用 APIComponentCallbacks2 来响应 app 生命周期事件和 设备事件,根据监听相关信号的结果调整内存的使用。方法 onTrimMemory() 能够监听 app 运行在前台或后台时候的相关内存事件。
在 Activity 中,实现回调方法 onTrimMemory() 来监听这些内存变化的事件,如下代码片段:
import android.content.ComponentCallbacks2;
// Other import statements ...
publicclassMainActivityextendsAppCompatActivity
implementsComponentCallbacks2{
// Other activity code ...
/**
* Release memory when the UI becomes hidden or whensystem resources become low.
* @param level the memory-related event that wasraised.
*/
publicvoid onTrimMemory(int level){
// Determine which lifecycle or systemevent was raised.
switch(level){
caseComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
/*
Release any UI objects that currently hold memory.
Theuser interface has moved to the background.
*/
break;
caseComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
caseComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
caseComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
/*
Release any memory that your app doesn't need to run.
Thedevice is running low on memory while the app is running.
Theevent raised indicates the severity of the memory-related event.
Ifthe event is TRIM_MEMORY_RUNNING_CRITICAL, then the system will
beginkilling background processes.
*/
break;
caseComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
caseComponentCallbacks2.TRIM_MEMORY_MODERATE:
caseComponentCallbacks2.TRIM_MEMORY_COMPLETE:
/*
Release as much memory as the process can.
Theapp is on the LRU list and the system is running low on memory.
Theevent raised indicates where the app sits within the LRU list.
Ifthe event is TRIM_MEMORY_COMPLETE, the process will be one of
thefirst to be terminated.
*/
break;
default:
/*
Release anynon-critical data structures.
The appreceived an unrecognized memory level value
from thesystem. Treat this as a generic low-memory message.
*/
break;
}
}
}
备注:回调方法 onTrimMemory() 在 Android4.0 中添加。更早的版本,能用回调方法 onLowMemory() 作为替代,大约和 TRIM_MEMORY_COMPLETE 事件相同。
检查你需要用到的内存:
为了支持多进程,android 为每一个 app 设置了一个堆内存上限。不同设备为 app 分配的具体上限值,取决于 RAM 的总大小。如果程序到达了这个上限值还想占用更多的内存,系统将会抛出异常 OutOfMemoryError。
为了避免内存溢出,我们可以查询当前设备为 app 指定的内存上限值。我们可以通过调用方法 getMemoryInfo() 来查询当前系统的这个属性值。这个方法将会返回一个包含可用内存、总内存、内存临界值(如果系统可用内存低于这个临界值将会杀掉部分进程)等当前设备状态的 ActivityManager.MemoryInfo 对象。ActivityManager.MemoryInfo 也提供了一个 boolean 类型的字段 lowMemory,记录当前设备是否运行在低内存情况下。
下面的代码片段展示了一个使用 geMemoryInfo() 方法的例子:
- publicvoid doSomethingMemoryIntensive(){
- // Before doing something that requires a lot of memory,
- // check to see whether the device is in a low memory state.
- ActivityManager.MemoryInfo memoryInfo = getAvailableMemory();
- if(!memoryInfo.lowMemory){
- // Do memory intensive work ...
- }
- }
- // Get a MemoryInfo object for the device's current memory status.
- privateActivityManager.MemoryInfo getAvailableMemory(){
- ActivityManager activityManager =(ActivityManager)this.getSystemService(ACTIVITY_SERVICE);
- ActivityManager.MemoryInfo memoryInfo =newActivityManager.MemoryInfo();
- activityManager.getMemoryInfo(memoryInfo);
- return memoryInfo;
- }
使用高效内存的代码结构
一些 android 特性、java 类、代码结构趋向于用更多的内存。我们可以通过选择更高效的替代方案来使我们 app 用最少的内存。
少用 Services:
在不必要的时候运行一个服务,是 app 最糟糕的内存管理错误之一。如果 app 需要运行一个服务在后台执行工作,除非有必要的任务,否则不要一直运行。当 Service 完成它的任务后一定要记得停这个 Service。否则,可能一不留神就产生一个内存泄露。
开启一个 Service 后,系统喜欢为这个 service 开启一个进程使其持续运行。这种情况使 Service 非常耗费资源,因为其他进程无法使用被 service 所在进程占用的 RAM 资源。系统缓存在 LRU catche 的进程数量减少后,使 app 运行效率降低。更有甚者,当内存紧张,系统无法维持足够的进程支持所有 service 的时候,service 可能被销毁。
通常情况下我们应该避免长期使用 service,因为它们会一直占用内存。我们推荐使用可供选择的类比如 JobScheduler 去调度后台进程。
关于如何使用 JobScheduler 调度后台进程,参阅 "BackgroundOptimizations"
使用最优化的数据容器:
编程语言提供的部分类对于移动设备不是最优化的。例如一般的 HashMap 实现能使很多内存低效率工作,因为它需要为每一个映射生成一个单独的条目对象。
Androidframework 包含几个最优化的数据容器,包括 SparseArray,SparseBooleanArray,LongSparseArray。例如 AparseArray 之所以更高效,是由于它们避免系统的需要,对 key(有时候也对 value)进行自动装箱。
如果有必要,我们可以总是选择原始的数组作为最精炼的数据结构。
谨慎使用抽象代码:
开发者通常仅仅把抽象当成一种好的编程实践,因为抽象可以提升代码的灵活性和可维护性。然而,抽象伴随着巨大的资源耗费而来:通常它们会需要更多的时间和更多的内存,需要执行相当数量的代码,花费更多的时间和内存。所以如果抽象不能带来巨大好处,我们应该尽可能去避免。
例如:枚举需要消耗的内存通常是静态变量的两倍多。我们应该严格避免在 android 上使用枚举。
使用 nanoprotobufs 序列化数据
Protocol buffers 是一个谷歌设计的具有语言中立、平台中立、可扩展、用来序列化结构数据的类似于 XML 的机制,但其比 XML 快,比 XML 小,比 XML 简单。如果你决定使用 protobufs 处理数据,那么我们应该在客户端代码中一直使用 nano protobufs。普通的 protobufs 通常生成非常多的代码,这样会引起 app 产生很多问题,比如占用更多内存、APK 尺寸大幅度增加、运行缓慢。
更多运行,请参阅文章 "protobuf readme" 中 "Nanoversion" 的段落。
避免内存抖动
就像上文提醒到的,GC 事件通常不会影响 app 的性能。然而一些发生在短时间内的 GC 事件能迅速耗尽你的帧像时间。系统花费在 GC 上的时间越多,那么它处理其他诸如渲染或者音频流的事件。
通常情况下下,内存抖动能引起大量的的 GC 事件。在实践中,内存抖动描绘的是,在一个给定时间内,分配临时对象的数目。
例如,你可能通过 for 循环分配多个临时对象。或者在一个 View 的 onDraw 方法中创建一个 Paint 或者 Bitmap 对象。如上两个例子,app 迅速地创建了大量的大体积对象。年青一代的这些行为将会迅速的消耗所有的可用内存,强制一个垃圾回收事件产生。
当然,在你修复内存抖动之前,需要先发现导致内存抖动发生的代码所在地方。请用在文章 "Analyze your RAM usage" 中讨论的工具。
一旦你识别代码出现问题的区域,试着降低处于性能临界区域的配置数目。考虑移除内循环的东西,或者将它们移到基于分配结构的 Factory(参阅文章:)。
降低内存占有量——累赘的资源和库
一些资源和库在你的代码能在你不知道的情况下贪婪的消耗内存。apk 的大小、包括第三方库以及嵌入的资源、能够影响 app 消耗的内存量。我们能通过移除代码中多余的、不必要的、臃肿的组件、资源或者库的方式来改善 app 的内存消耗。
降低 apk 大小
我们能通过降低 apk 大小的方式显著的降低 app 的内存使用情况。bitmap 尺寸、资源、动画的帧、第三方库都能导致 apk 变大。android sdk 和 android studio 提供了多种工具来帮助我们降低资源和外部依赖的尺寸。
关于降低 apk 大小的更多信息,参阅 "ReduceAPK Size"。
如果需要依赖注入,建议使用 Dagger
依赖注入框架能够简化你写的代码以及为测试和测试改变提供一个适配的环境。如果你打算在 app 中用一个依赖注入框架,考虑使用 Dagger2(参阅 https://google.github.io/dagger/).
Dagger 不会用反射扫描 app 代码。它是静态的、编译时实现的框架,这意味着在运行时没有不必要的运行时花费或者内存使用。
其他的那些依赖注入框架通过扫描代码和注解,运用反射去初始化进程。这个产生的进程会需要大量的 CPU 周期以及内存,能在 app 启动时引起一个显而易见的延迟。
谨慎使用依赖库:
外部库的代码通常不是为手机环境所写,使其在手机客户端上工作时可能是很低效的。当我们决定用一个依赖库,可能需要针对移动设备进行优化。在决定真正使用这个依赖库之前,应预先做好针对移动设备优化的计划,依据代码尺寸和内存大小进行分析。
即使一些移动优化的库也能由于不同的实现引起问题。例如,当一个库使用了 micro protobufs 而一个库使用了 nano protobufs,这样 app 中有两个不同的 protobuf 实现。当问题发生时,原因可能是 logging,analytics,image loading frameworks,caching 或者一些其他我们无法预料的事情的不同实现导致的。
尽管 ProGuard(参阅:https://developer.android.com/studio/build/shrink-code.html) 能根据正确的标记移除 APIs 和资源,但是它不能移除一个库大型的依赖。你在库中想要的特性可能需要低版本的依赖....
翻译整理,更多内容,敬请期待....
来源: http://www.bubuko.com/infodetail-1972495.html