一、LeakCanary简介
在Android开发中,内存泄漏一直是一个难题。为了解决这一问题,Square Open Source团队开发了一款内存泄漏检测库:LeakCanary。LeakCanary可以非常方便地在运行时检测应用程序的内存泄漏情况,也因此成为了Android开发中的重要工具之一。
LeakCanary基于一个简单的原理,即通过引用链来判断对象是否被释放。如果被释放的对象在引用链中没有被引用,那么就可以判断出该对象已经被释放,否则就表示该对象存在内存泄漏。
二、LeakCanary原理
下面我们来详细讲解一下LeakCanary的原理。例如我们需要检测Activity是否存在内存泄漏问题:
public class TestActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } }
我们需要为该Activity实现RefWatcher,通过监听其生命周期来判断是否存在内存泄漏:
public class TestActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); RefWatcher refWatcher = LeakCanary.install(this.getApplication()); refWatcher.watch(this); } }
通过调用LeakCanary的install方法,我们可以获取到一个RefWatcher实例。refWatcher.watch(this)就是指定了需要检测的对象,这里我们选择检测当前的Activity。
LeakCanary内部实现原理是,通过覆写Application的onTrimMemory方法,检测当前进程的内存情况。当检测到内存紧张的情况时,会先调用系统的gc()方法,然后再检查所有被注册的对象,看看是否有已经被释放的对象存在引用链中。如果有,则判断为内存泄漏,LeakCanary会将这个内存泄漏的对象和引用链信息记录到HeapAnalyzerService中,再次检测时就会通过HeapAnalyzerService检测是否存在内存泄漏问题。
三、LeakCanary使用
使用LeakCanary非常简单,只需要在项目中添加以下依赖:
dependencies { debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7' }
然后在Application初始化时注册LeakCanary:
public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); if (LeakCanary.isInAnalyzerProcess(this)) { return; } LeakCanary.install(this); } }
这样就可以在应用程序中使用了,如果内存泄漏,LeakCanary会在Logcat输出对应信息:
┬─── │ GC Root: Java local variable │ ├─ com.example.TestActivity instance │ Leaked: YES (ObjectWatcher was watching this because com.example.TestActivity received Activity#onDestroy() callback and Activity#mDestroyed is true) │ key = af5f35c2-dedd-4c86-bf7e-e4b1454881ed │ watchDurationMillis = 10508 │ retainedDurationMillis = -1 │ mContext instance of com.example.MyApplication │ mWindow instance of android.view.Window$1 │ mActionBar instance of android.app.ActionBarImpl │ mActionView instance of com.android.internal.view.menu.ActionMenuView │ mParent instance of android.widget.LinearLayout │ mPresenter instance of com.android.internal.view.menu.ActionMenuPresenter │ ↓ ActionMenuPresenter.mContext │ ~~~~~~ ├─ android.app.Application$1 instance │ Leaked: NO (Application is a singleton) │ ↓ Application$1.this$0 │ ~~~~~~ ├─ com.example.MyApplication instance │ Leaked: NO (Application is a singleton) │ ↓ MyApplication.leakToggle │ ~~~~~~~~~~ ├─ com.example.utils.LeakActivityWatcher instance │ Leaked: NO (ObjectWatcher was watching this) │ ↓ LeakActivityWatcher.context │ ~~~~~~~ ├─ com.example.MyApplication instance │ Leaked: NO (Application is a singleton) │ ↓ MyApplication.mActivityWatcher │ ~~~~~~~~~~~~~~~~ ├─ com.example.utils.LeakActivityWatcher instance │ Leaked: NO (ObjectWatcher was watching this) │ ├─ android.os.HandlerThread instance │ Leaked: NO (Thread-62 is running) │ ↓ HandlerThread.mLooper │ ~~~~~~ ├─ android.os.Looper instance │ Leaked: NO (Thread-62 is running) │ ↓ Looper.mQueue │ ~~~~~ ├─ android.os.MessageQueue instance │ Leaked: NO (Thread-62 is running) │ ↓ MessageQueue.mIdleHandlers │ ~~~~~~~~~~~~~ ├─ java.util.ArrayList instance │ Leaked: NO (Thread-62 is running) │ ↓ ArrayList.elementData │ ~~~~~~~~~~~ ├─ java.lang.Object[] array │ Leaked: NO (Thread-62 is running) │ Array length: 1 │ ↓ Object[0] android.os.BinderProxy instance │ Leaked: NO (Thread-62 is running) │ ├─ android.os.BinderProxy instance │ Leaked: NO (mContext is a system object) │ ↓ BinderProxy.mContext │ ~~~~~~~ ├─ com.android.server.am.ActivityManagerService$MainHandler instance │ Leaked: NO (mThread is a system object) │ ↓ ActivityManagerService$MainHandler.this$0 │ ~~~~~~ ├─ com.android.server.am.ActivityManagerService instance │ Leaked: NO (mContext is a system object) │ ↓ ActivityManagerService.mUiContext │ ~~~~~~~~~~ ╰→ com.android.server.firewall.IntentFirewall instance Leaked: YES (ObjectWatcher was watching this) key = d6bebb1e-13bd-45af-a786-491763d6c3e6 watchDurationMillis = 10808 retainedDurationMillis = -1
通过Logcat输出我们可以看到,LeakCanary成功检测出了内存泄漏信息。我们需要仔细分析这些信息,找到内存泄漏的原因,及时修复Bug。
四、结论
LeakCanary是一个非常实用的内存泄漏检测库,在开发中可以使用它监测应用程序是否存在内存泄漏问题,及时发现并修复内存泄漏问题,从而更好地提升应用程序的性能和稳定性。