在 GC 的过程中,其它在工作的线程会暂停,包括负责绘制的 UI 线程,并且在不同区域的内存释放速度也有一定的差异,但不管在哪个区域,都要到这次 GC 内存回收完成后,才会继续执行原来的线程。
虽然一次消耗性能不大,但如果大量这样的重复,就会影响到应用的渲染 工作,造成垃圾回收动作太频繁。这种情况很容易发生在短时间内申请大量 的对象时,并且它们在极少的情况下能得到有效的释放,这样会出现内存泄漏的情况。
一旦达到了剩余内存的阈值,垃圾回收活动就会启动。即使有时内存申请 很小,它们仍然会给应用程序的堆内存造成压力,还是会启动垃圾回收,在 GC 频繁的工作过程中消耗了非常多的时间,并且可能导致卡顿。为了避免这样的情况,设置一个 16ms 界线,只要 GC 消耗的时间超过了 16ms 的阈值,就会有丢帧的情况出现。
使用 Memory Profiler 查看 Java 堆和内存分配可分析内存情况和内存泄露。
内存泄漏就是存在一些被分配的对象,可达但不可用,用不着了但还有链接引用着,导致 GC 无法回收。会导致内存空间不断减少,最终内存耗尽引起 OOM 问题。
mHandler.removeCallbacksAndMessages(null)
,移除消息队列中所有消息和所有的 Runnable。LeakCanary 是 Square 公司的检测内存泄漏的函数库,在 Debug 版本中监控 Activity、Fragment 等的内存泄漏。检测到内存泄漏时会将消息发到系统通知栏,点击后打开 DisplayLeakActivity 的页面,显示泄漏的跟踪消息,还默认保存了最近的 7 个 dump 文件到 APP 的目录中,可以用 MAT 等工具进一步分析。
配置 gradle 文件:
dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
}
只有 Debug 版本使用,Release 和 Test 版本用 no-op 版本,没有实际代码和操作,不会对 APP 体积和性能产生影响。
在 Application 中初始化:
public class ExampleApplication extends Application {
@Override public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
// Normal app init code...
}
}
其中,LeakCanary.install 方法会自动启动一个 ActivityRefWatcher,自动监控应用中调用 Activity.onDestroy 之后发生泄漏的 Activity。
如果想监控其它的对象,比如 Fragment,可以通过 install 方法返回的 RefWatcher 去监控。
public class ExampleApplication extends Application {
@Override public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
refWatcher = LeakCanary.install(this);
// Normal app init code...
}
private RefWatcher refWatcher;
// get 方法返回 RefWatcher 对象
public static RefWatcher getRefWatcher(Context context) {
ExampleApplication application = (ExampleApplication) context.getApplicationContext();
return application.refWatcher;
}
}
然后在 Fragment 的 onDestroy 方法中调用 refWatcher 监控
@Override
public void onDestroy() {
super.onDestroy();
RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity());
refWatcher.watch(this);
}
可以使用 watch 来监控任何你认为已经销毁的对象。
.hprof
文件中,并将文件放在 APP 的文件目录中。由于 Release 版本使用的 leakcanary-android-no-op 库,若自定义 LeakCanary,需确保只影响 Debug 版本,因为可能引用到 leakcanary-android-no-op 中没有的 API。因此需要将 Release 和 Debug 部分的代码分离。例如定义 ExampleApplication 用于 Release 版本,DebugExampleApplication 用于 Debug 版本,继承 ExampleApplication。
public class ExampleApplication extends Application {
public static RefWatcher getRefWatcher(Context context) {
ExampleRefWatcher application = (ExampleRefWatcher) context.getApplicationContext();
return application.refWatcher();
}
private RefWatcher refWatcher;
@Override
public void onCreate() {
super.onCreate();
...
// 不再是调用 install 方法
refWatcher = installLeakCanary();
...
}
protected RefWatcher installLeakCanary() {
return RefWatcher.DISABLED;
}
}
新建 src/debug/java 文件夹,在其中创建 DebugExampleApplication:
// Debug 版本的 Application 类
public class DebugExampleApplication extends ExampleApplication {
protected RefWatcher installLeakCanary() {
RefWatcher refWatcher = LeakCanary.install(this);
return refWatcher;
}
}
在 src/debug 中新建 AndroidManifest.xml 文件:
<?xml version="1.0 encoding="utf-8" ?>
<manifest ...>
<application
tools:replace="android:name"
android:name=".DebugExampleApplication" />
</manifest>
Gradle 构建时,如果是 debug 版本,会将 src/debug/AndroidManifest.xml
的内容合并入 src/main/AndroidManifest.xml
文件中。同时由于使用了 tools:replace
属性,所以 android:name
的值 DebugExampleApplication 会替换 ExampleApplication。
内存泄漏通知页面 DisplayLeakActivity 默认的图标和标签两个值,可以进行覆盖。
图标定义在 res 下的 drawable-hdpi/drawable-mdpi/drawable-xhdpi/drawable-xxhdpi/drawable-xxxhdpi
里,名为 __leak_canary_icon.png
。
标签定义在:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="__leak_canary_display_activity_label">MyLeaks</string>
</resources>
默认情况下,DisplayLeakActivity 在 APP 目录中最多保存 7 个 HeapDump 文件和泄漏堆栈信息,可以在 APP 中定义 R.integer.__leak_canary_max_stored_leaks
来修改。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="__leak_canary_max_stored_leaks">20</string>
</resources>
通过定义 R.integer.leak_canary_watch_delay_millis
来修改弱引用对象被认为出现内存泄漏的延时时间,默认 5 秒,下面修改为 1.5 秒:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="leak_canary_watch_delay_millis">1500</string>
可以通过继承 DisplayLeakService 并重写其中的 afterDefaultHandling 函数来实现定制化操作,例如将 heap dump 文件发送到服务端:
public class LeakUploadService extends DisplayLeakService {
@Override
protected void afterDefaultHandling(HeapDump headDump, AnalysisResult result, String leakInfo) {
if (!result.leakFound || result.excludedLeak) {
return;
}
myServer.uploadLeakBlocking(heapDump.headDumpFile, leakInfo);
}
}
public class DebugExampleApplication extends ExampleApplication {
protected RefWatcher installLeakCanary() {
return LeakCanary.install(app, LeakUploadService.class, AndroidExcludedRefs.createAppDefaults().build());
}
}
为了使 LeakUploadService 生效,需要在 AndroidManifest.xml 中注册。
实现自己的 ExcludedRefs 忽略某些特定的弱引用对象,不对其进行内存泄漏的监视。
public class DebugExampleApplication extends ExampleApplication {
protected RefWatcher installLeakCanary() {
ExcludedRefs excludedRefs = AndroidExcludedRefs.createAppDefaults()
.instanceField("com.example.Example.class", "exampleField")
.build();
return LeakCanary.install(this, DisplayLeakService.class, excludedRefs);
}
}
默认会监视所有 Activity 的内存泄漏,默认只支持 Android 4.0 以上的系统,如果 4.0 以下需要在 onDestroy 中主动 watch。
public class DebugExampleApplication extends ExampleApplication {
@Override
protected RefWatcher installLeakCanary() {
if (LeakCanary.isInAnalyzerProcess(this)) {
return RefWatcher.DISABLED;
} else {
ExcludedRefs excludedRefs = AndroidExcludedRefs.createAppDefaults().build();
LeakCanary.enableDisplayLeakActivity(this);
ServiceHeapDumpListener heapDumpListener = new ServiceHeapDumpListener(this, DisplayLeakService.class);
final RefWatcher refWatcher = LeakCanary.androidWathcer(this, heapDumpListener, exlcudedRefs);
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
public void onActivityDestroyed(Activity activity) {
if (activity instanceof MainActivyt) { // 排除某些 Activity
return;
}
refWatcher.watch(activity);
}
});
return refWatcher;
}
}
}
TRIM_MEMORY_UI_HIDDEN
这个级别时,表明用户已经离开了程序,所有界面都不可见,此时可以进行一些资源释放操作。
@Override public void onTrimMemory(int level) { super.onTrimMemory(level); switch (level) { case TRIM_MEMORY_UI_HIDDEN: // 释放资源 break; } }
Drawable.createFromStream
替换 getResources().getDrawable
来加载,这样就可以绕过 Android 的这套默认适配法则。