在 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 的这套默认适配法则。