Android 内存优化

为什么优化

虽然 Java 对内存的释放有垃圾自动回收机制,但是实际开发中,不再用到的对象因为被错误引用会导致无法回收,从而造成内存泄漏,甚至内存溢出 OOM(OutOfMemory),程序崩溃。

Android GC 原理

参考:Android GC 原理探究

Android 内存管理

参考:Android 内存管理

如何检测

使用 LeakCanary

Android 内存泄漏检测库:https://github.com/square/leakcanary ,善于使用 LeakCanary 发现内存泄漏。

Memory Monitor工具

Android Studio 中的 Android Monitor ,选择其中的 Memory,跟踪整个 APP 的内存变化情况
步骤如下:
1、与您的应用交互,在 Memory 监视器中,查看 Free 和 Alloated 内存。
2、点击 Dump Java Heap:,这个会打开 HPROF 查看器,如果没有,在 Captures 标签中,双击堆快照文件以打开 HPROF 查看器。
3、要引起堆分配,请与您的应用交互,然后点击触发 GC 的按钮:

Heap Viewer 工具

实时查看App分配的内存大小和空闲内存大小,发现 Memory Leaks
详细使用教程:Heap Viewer工具

Allocation Tracker

追踪内存对象的来源
详细使用教程:Allocation Tracker(Device Monitor)

优化方案

检查使用多少内存

每个 APP 的堆(heap)内存大小有硬性限制,如果您的 APP 已达到堆内存限制,并尝试分配更多的内存,系统会抛出 OutOfMemoryError 。为了避免 OOM ,您可以查询当前设备有多少堆空间,可以通过调用系统 getMemoryInfo() 查询,返回 一个ActivityManager.MemoryInfo 对象,它提供该设备当前存储器的状态信息,包括可用的存储器,总存储器,和低于该阈值存储器。

1
2
3
4
5
6
7
8
9
private void getMemoryInfo() {
ActivityManager activityManager = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
activityManager.getMemoryInfo(memoryInfo);
LogUtil.d("totalMem=" + memoryInfo.totalMem + ",availMem=" + memoryInfo.availMem);
if (!memoryInfo.lowMemory) {
// 运行在低内存环境
}
}

当界面不可见时释放内存

实现 ComponentCallbacks2 API 中 onTrimMemory()) ,当回调参数 level 为 TRIM_MEMORY_UI_HIDDEN ,是用户点击了Home键或者Back键退出应用,所有UI界面被隐藏,这时候应该释放一些不可见的时候非必须的资源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class MainActivity extends AppCompatActivity
implements ComponentCallbacks2 {
// Other activity code ...
/**
* Release memory when the UI becomes hidden or when system resources become low.
* @param level the memory-related event that was raised.
*/
public void onTrimMemory(int level) {
// Determine which lifecycle or system event was raised.
switch (level) {
case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
/*
Release any UI objects that currently hold memory.
The user interface has moved to the background.
*/
break;
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
/*
Release any memory that your app doesn't need to run.
The device is running low on memory while the app is running.
The event raised indicates the severity of the memory-related event.
If the event is TRIM_MEMORY_RUNNING_CRITICAL, then the system will
begin killing background processes.
*/
break;
case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
/*
Release as much memory as the process can.
The app is on the LRU list and the system is running low on memory.
The event raised indicates where the app sits within the LRU list.
If the event is TRIM_MEMORY_COMPLETE, the process will be one of
the first to be terminated.
*/
break;
default:
/*
Release any non-critical data structures.
The app received an unrecognized memory level value
from the system. Treat this as a generic low-memory message.
*/
break;
}
}
}

该 onTrimMemory() 回调是在搭载Android 4.0(API级别14)加入。对于早期版本,您可以使用 onLowMemory() 回调作为旧版本的回调,这大致相当于 TRIM_MEMORY_COMPLETE事件。

谨慎使用服务

离开了 APP 还在运行服务是最糟糕的内存管理错误之一,当 APP 处在后台,我们应该停止服务,除非它需要运行的任务。我们可以使用 JobScheduler 替代实现,JobScheduler 把一些不是特别紧急的任务放到更合适的时机批量处理。如果必须使用一个服务,最佳方法是使用 IntentService ,限制服务寿命,所有请求处理完成后,IntentService 会自动停止。

使用优化的数据容器

考虑使用优化过数据的容器 SparseArray / SparseBooleanArray / LongSparseArray 代替 HashMap 等传统数据结构,通用 HashMap 的实现可以说是相当低效的内存,因为它需要为每个映射一个单独的条目对象。

避免在 Android 上使用枚举

枚举往往需要两倍多的内存,静态常量更多,我们应该严格避免在 Android 上使用枚举。

使用 nano protobufs 序列化数据

Protocol buffers 是一个语言中立,平台中立的,可扩展的机制,由谷歌进行序列化结构化数据,类似于 XML 设计的,但是更小,更快,更简单。如果需要为您的数据序列化与协议化,建议使用 nano protobufs。

避免内存流失

内存流失可能会导致出现大量的 GC 事件,如自定义组件的 onDraw() ,避免大量创建临时对象,比如 String ,以免频繁触发 GC。GC 事件通常不影响您的 APP 的性能,然而在很短的时间段,发生许多垃圾收集事件可以快速地吃了您的帧时间,系统上时间的都花费在 GC ,就有很少时间做其他的东西像渲染或音频流。

使用ProGuard来剔除不需要的代码

使用 ProGuard 来剔除不需要的代码,移除任何冗余的,不必要的,或臃肿的组件,资源或库完善 APP 的内存消耗。

降低整体尺寸APK

您可以通过减少 APP 的整体规模显著减少 APP 的内存使用情况。文章:Android APK瘦身实践

优化布局层次

通过优化视图层次结构,以减少重叠的 UI 对象的数量来提高性能。文章:Android 渲染优化

使用 Dagger 2依赖注入

依赖注入框架可以简化您写的代码,并提供一个自适应环境测试和便于其他配置的更改。如果打算在您的 APP 使用依赖注入框架,可以考虑用 Dagger 2 ,Dagger 不使用反射扫描 APP 的代码,Dagger 是静态的,意味着它编译时不需要占用运行 Android 应用或内存的使用。

请小心使用外部库

外部库的代码往往是未针对移动环境下编写并用于工作在移动客户端上时,可能是低效的。当您决定使用外部库,您可能需要优化该库为移动设备。

避免Bitmap浪费

Bitmap是内存消耗的大头,当使用时要及时回收。另外配置:
inSampleSize:缩放比例,图片按需加载,避免不必要的大图载入。
decode format:解码格式,选择ARGB_8888/RBG_565/ARGB_4444/ALPHA_8,存在很大差异。

Cursor关闭

如查询数据库的操作,使用到Cursor,也要对Cursor对象及时关闭。

监听器的注销

Android程序里面存在很多需要register与unregister的监听器,手动add的listener,需要记得及时remove这个listener。

参考

官网Overview of Android Memory Management

官网Manage Your App’s Memory

Android内存优化之OOM



联系作者

我的微信公众号:吴小龙同学,欢迎关注交流,公号回复关键字「1024」有惊喜哦。