之前简单介绍了一下使用LeakCanary排查内存泄漏的使用,那么它为什么这么”智能”呢?为啥我们就加了一行代码它就可以监测内存泄漏了捏?这里就涉及LeakCanary的源码了。

源码分析

上面简单介绍了一下使用LeakCanary排查内存泄漏的使用,那么它为什么这么”智能”呢?为啥我们就加了一行代码它就可以监测内存泄漏了捏?这里就涉及LeakCanary的源码了。

在理解LeakCanary源码之前我们先来了解一下java虚拟机里的强引用,软引用,弱引用和虚引用。

  • 强引用(Strong Reference):强引用在代码中普遍的存在,类似于“Object obj = new Object()”,只要某个对象有强引用与之关联,JVM则无法回收该对象,即使在内存不足的情况下,JVM宁愿抛出OOM错误,也不会回收这种对象。
  • 软引用(Soft Reference):软引用常常用来描述一些有用但是非必需的对象。对于软引用关联的对象,会在JVM内存不足时既OOM之前将这些对象列入回收范围,进行二次回收。如果这时回收还是没有足够的内存才会造成内存溢出异常。在JDK1.2之后,提供了SoftReference类来实现软引用。软引用一般用于网页的缓存图片的缓存等等比较耗时的操作,但是这些操作目前一般使用LruChche来实现,因此目前代码中很少见到SoftReference。
  • 弱引用(Weak Reference):被弱引用关联的对象只能生产到下一次垃圾收集发生之前。当垃圾收集器工作室,无论内存是否足够,都会回收弱引用关联的对象。可以使用WeakReference类来实现弱引用。
  • 虚引用(Phantom Reference):虚引用也称之为幽灵引用,或者幻影引用,它是最弱的一种引用关系,一个对象是否有虚引用的存在,完全不会对其生存时间构成影响。也无法通过虚引用来取得一个对象的实例,为一个对象设置为虚引用的唯一目的是希望这个对象被回收器回收时能收到一个系统通知,在JDK1.2之后,提供了Phantom Reference类来实现虚引用。

而LeakCanary核心原理就是利用弱引用和引用队列ReferenceQueue来检测Activity/Fragment被销毁后是否被回收。如果弱引用的引用对象被垃圾回收器回收,虚拟机就会把这个弱引用加入到与之关联的引用队列中,我们就可以此特性来检查一个对象是否被垃圾回收器回收成功。我们将上述用代码简单实现就是下面这样

fun main() {
var obj: Any? = Object()
val referenceQueue = ReferenceQueue<Any?>()
val weakReference = WeakReference<Any?>(obj, referenceQueue)//将对象obj与软引用关联,再将软引用与引用队列关联

var ref = referenceQueue.poll()//从引用队列取出软引用,此时obj未被回收,取出的是null
println("gcBefore:${ref}")

obj = null//将obj与Object()的引用断开,此时obj将被GC回收

System.gc()
Thread.sleep(2000)

ref = referenceQueue.poll()//obj被回收,软引用入列,取出不为null
println("gcAfter:${ref}")
}

输出结果:

gcBefore:null
gcAfter:java.lang.ref.WeakReference@60f82f98

Process finished with exit code 0

恰好印证。那么我们知道了核心原理,是否可以实现一个简单版的LeakCanary捏?说干就干!

首先,我们需要封装一下我们的弱引用,因为往往我们观测的是多个对象,那么就有多个弱引用,所以我们需要给每个弱引用设置一个Key方便查找。

class KeyWeakCanary<T> : WeakReference<T> {
private var key: String

constructor(referent: T, key: String) : super(referent) {
this.key = key
}

constructor(referent: T, queue: ReferenceQueue<in T?>, key: String) : super(referent, queue) {
this.key = key
}

//重写toString,方便观察值
override fun toString(): String {
return "KeyWeakReference(key=$key)"
}
}

弱引用简单封装了一下,那么还差个观察者,你要监测肯定需要一个观察者嘛。

class Watcher {

//监测列表
private val watchedReferences = mutableMapOf<String, KeyWeakReference<Any?>>()

//保留列表
private val retainedReferences = mutableMapOf<String, KeyWeakReference<Any?>>()

//引用队列
private val queue = ReferenceQueue<Any?>()

fun watch(obj: Any?) {
//生成UUID Key,便于从列表取出相应的引用
val key = UUID.randomUUID().toString()

val reference = KeyWeakReference(obj, queue, key)//弱引用引用对象,引用对象与弱引用关联

//登记
watchedReferences[key] = reference

//开启子线程监测对象是否泄漏
val executor = Executors.newSingleThreadExecutor()
executor.execute {
Thread.sleep(1000)
moveToRetain(key)
}
}

//将符合泄漏的对象转移到留存区
private fun moveToRetain(key: String) {

var ref: KeyWeakReference<Any?>? = null

do {
queue.poll()?.also { ref = it as KeyWeakReference<Any?> }
//回收成功,没有发生内存泄漏的情况
ref?.key.let {
watchedReferences.remove(it)
retainedReferences.remove(it)
}
ref = null
} while (ref != null)

//如果没有回收成功,那这个对象那这个对象肯定还在观察区,将其转移到留存区
watchedReferences.remove(key)?.also {
retainedReferences[key] = it
}
}
//供外部取出内存泄漏对象的值
fun getReferences():MutableMap<String,KeyWeakReference<Any?>>{
return retainedReferences
}
}

首先,我们创建了观察列表和留存列表,这就好比现在的疫情,从外省回来会被隔离(观察列表),如果观察你有新冠就将你运往隔离医院治疗(留存列表)。然后就是观察方法,传入观察对象,之后用java自带的UUID工具生成唯一的UUID给弱引用方便根据key查询弱引用,之后是弱引用引用对象,引用队列与弱引用关联,再把该弱引用记录进观察区,之后用线程池开启子线程进行内存监测。在内存检测方法里先从引用队列拿出一个引用对象,若为空,则表明它没有被回收,观察区也就不会将它移除,之后下面remove它的时候就不为null,它就会被转移进留存区。之后外界通过这个留存区取出内存泄漏的对象进行分析通知一系列操作。

然后在主线程测试一下

fun main() {
var obj:Any? = Object()
val watcher = Watcher()
watcher.watch(obj)

obj = null//改变obj为null的状态来模拟内存泄漏与否
System.gc()

Thread.sleep(2000)

watcher.getReferences().forEach { (key, reference) ->
println("key:$key,$reference")
}
}

将obj置为null此时调用GC obj将被回收,无输出。再将这行注释,模拟obj被引用的情况,此时GC无法将其回收,留存区有值,打印:

key:ca1485f4-929a-45f2-8977-483f05245f0d,KeyWeakReference(key=ca1485f4-929a-45f2-8977-483f05245f0d)

Process finished with exit code 0

证明我们这个简易版的(可以说很丑陋)内存泄漏检测工具是成功的。但实际上,LeakCanary的做法比我们这个更加细节,我们这个只能检测单个对象,而LeakCanary对Activity、Fragment、Service、RootView、Viewmodel都进行了生命周期监听,并且对泄漏对象的通报和分析都是在内部进行的。

我们现在来看看它的源码,在看他的源码之前我们先问几个问题

  1. 在LeakCanary1.0版本之前是需要在Application里面初始化的,2.0版本之后直接添加依赖就可以用了,那之后的版本它在哪初始化的捏?
  2. LeakCanary是怎么对Activity、fragment(view和fragment本身)、Service、RootView、Viewmodel进行生命周期的监听的捏?
  3. 检测到泄漏之后是怎么处理的呢?

相信带着这几个问题去看他的源码会更好理解。

首先看LeakCanary是在哪里初始化的?其实,在1.0版本之前,LeakCanary都是在自定义的Application里面初始化的,在2.0之后只需添加一行依赖即可。之前也提到过ContentProvider得onCreate()是在Application.onCreate前面执行的,不难想到LeakCanary就是这么许哦的。

为什么ContentProvider的onCreate方法是在Application前面执行的呢?

这就要看看ActivityThread的源码了,要知道他们两个的onCreate先后顺序肯定要先理清Application的创建流程,因为Application是伴随整个app的生命周期的。看到ActivityThread的main函数,我们看到

public static void main(String[] args) {
...
Looper.prepareMainLooper();
...
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
...
Looper.loop();

throw new RuntimeException("Main thread loop unexpectedly exited");
}

Application和整个app的生命周期那肯定也伴随着主线程的启动与消亡,那肯定跟thread.attach有关,跟进看一看

private void attach(boolean system, long startSeq) {
sCurrentActivityThread = this;
mSystemThread = system;
if (!system) {
android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
UserHandle.myUserId());
RuntimeInit.setApplicationObject(mAppThread.asBinder());
// mgr为ActivityManagerService实例
final IActivityManager mgr = ActivityManager.getService();
try {
// mAppThread为ApplicationThread实例,ApplicationThread是ActivityThread与AMS交互的桥梁
mgr.attachApplication(mAppThread, startSeq);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
...
}
...
}

这里获取AMS之后调用了attachApplication(mAppThread, startSeq),跟进看一下

public void attachApplication(IApplicationThread thread, long startSeq) {
if (thread == null) {
throw new SecurityException("Invalid application interface");
}
synchronized (this) {
int callingPid = Binder.getCallingPid();
final int callingUid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
attachApplicationLocked(thread, callingPid, callingUid, startSeq);//这里又调用了AMS的内部方法,跟踪下去
Binder.restoreCallingIdentity(origId);
}
}

private boolean attachApplicationLocked(@NonNull IApplicationThread thread, int pid, int callingUid, long startSeq) {
...
if (app.isolatedEntryPoint != null) {
//判断是否是isolato进程,这个可通过设置service的android:isolatedProcess开启,设置该服务是否
//作为一个单独的进程运行,如果设置为true,此服务将在与系统其余部分隔离的特殊进程下运行,并且没有自己的权限,与它唯一 //的通信是通过服务API(绑定和启动),这个我们一般不会去这么做,直接略过
thread.runIsolatedEntryPoint(app.isolatedEntryPoint, app.isolatedEntryPointArgs);
} else if (instr2 != null) {
thread.bindApplication(processName, appInfo, providerList,//跟踪到者发现调用了bindApplication
instr2.mClass,
profilerInfo, instr2.mArguments,
instr2.mWatcher,
instr2.mUiAutomationConnection, testMode,
mBinderTransactionTrackingEnabled, enableTrackAllocation,
isRestrictedBackupMode || !normalMode, app.isPersistent(),
new Configuration(app.getWindowProcessController().getConfiguration()),
app.compat, getCommonServicesLocked(app.isolated),
mCoreSettingsObserver.getCoreSettingsLocked(),
buildSerial, autofillOptions, contentCaptureOptions,
app.mDisabledCompatChanges);
} else {
thread.bindApplication(processName, appInfo, providerList, null, profilerInfo,//同样是这个方法的重载
null, null, null, testMode,
mBinderTransactionTrackingEnabled, enableTrackAllocation,
isRestrictedBackupMode || !normalMode, app.isPersistent(),
new Configuration(app.getWindowProcessController().getConfiguration()),
app.compat, getCommonServicesLocked(app.isolated),
mCoreSettingsObserver.getCoreSettingsLocked(),
buildSerial, autofillOptions, contentCaptureOptions,
app.mDisabledCompatChanges);
}
...
}

那么我们继续点进thread.bindApplication看看它做了什么

public final void bindApplication(String processName, ApplicationInfo appInfo,
ProviderInfoList providerList, ComponentName instrumentationName,
ProfilerInfo profilerInfo, Bundle instrumentationArgs,
IInstrumentationWatcher instrumentationWatcher,
IUiAutomationConnection instrumentationUiConnection, int debugMode,
boolean enableBinderTracking, boolean trackAllocation,
boolean isRestrictedBackupMode, boolean persistent, Configuration config,
CompatibilityInfo compatInfo, Map services, Bundle coreSettings,
String buildSerial, AutofillOptions autofillOptions,
ContentCaptureOptions contentCaptureOptions, long[] disabledCompatChanges,
SharedMemory serializedSystemFontMap) {
...

sendMessage(H.BIND_APPLICATION, data);//看这里
}

找来找去发现没有关于application的函数了,但是我们可以看到最后一行调用了sendMessage函数,并传了H.BIND_APPLICATION这个参数,那么我们点进去看看它是否跟bindApplication有关捏?

void sendMessage(int what, Object obj) {
sendMessage(what, obj, 0, 0, false);
}

继续跟进

private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
if (DEBUG_MESSAGES) {
Slog.v(TAG,
"SCHEDULE " + what + " " + mH.codeToString(what) + ": " + arg1 + " / " + obj);
}
Message msg = Message.obtain();
msg.what = what;
msg.obj = obj;
msg.arg1 = arg1;
msg.arg2 = arg2;
if (async) {
msg.setAsynchronous(true);
}
mH.sendMessage(msg);//发现这里发送了一个消息,传入的message携带的信息就是上面传入的H.BIND_APPLICATION
}

我们发现这里调用了mH的sendMessage方法,mH会不会就是Handler?跟进mH看一下

final H mH = new H();

是H的对象,那H是否继承了Handler方法呢?点进去

class H extends Handler {
...
//通过Handler进入
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case BIND_APPLICATION://根据我们传入的值进入这个分支
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
AppBindData data = (AppBindData)msg.obj;
handleBindApplication(data);//在这里处理了Application的绑定
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case EXIT_APPLICATION:
if (mInitialApplication != null) {
mInitialApplication.onTerminate();
}
Looper.myLooper().quit();
break;
case RECEIVER:
...
}
...
}
...
}

发现果然是这样。并且实现了handle Message方法,那上面我们sendMessage最终通过Handler肯定执行handle Message方法,并且根据我们传入的H.BIND_APPLICATION进入了第一个分支,继续跟进handleBindApplication(data);

@UnsupportedAppUsage
private void handleBindApplication(AppBindData data) {
...
//创建appContext
final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);//6660
...
try {
//在这创建了application
app = data.info.makeApplication(data.restrictedBackupMode, null);//6723

...
if (!data.restrictedBackupMode) {
if (!ArrayUtils.isEmpty(data.providers)) {//6747
installContentProviders(app, data.providers);//可以看到这里应该就是启动ContentProvider的地方
}
}

...
try {
//看函数名不难发现这儿就是回调Applicatio.onCreate的地方 6762
mInstrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
if (!mInstrumentation.onException(app, e)) {
throw new RuntimeException(
"Unable to create application " + app.getClass().getName()
+ ": " + e.toString(), e);
}
}
...
}

可以看到,先创建appContext和application,具体创建过程这里就不细细分析了,这里我们只是简单看看ContentProvier和Application的onCreate执行顺序。然后是installContentProviders,那这里肯定就和ContentProvider有关了,等会再分析,先看下面的mInstrumentation.callApplicationOnCreate(app),我们跟进发现

public void callApplicationOnCreate(Application app) {
app.onCreate();
}

其实就是调用了application的onCreate方法,那我们再跟进前面的installContentProviders看看

private void installContentProviders(
Context context, List<ProviderInfo> providers) {
final ArrayList<ContentProviderHolder> results = new ArrayList<>();

for (ProviderInfo cpi : providers) {
if (DEBUG_PROVIDER) {
StringBuilder buf = new StringBuilder(128);
buf.append("Pub ");
buf.append(cpi.authority);
buf.append(": ");
buf.append(cpi.name);
Log.i(TAG, buf.toString());
}
ContentProviderHolder cph = installProvider(context, null, cpi,//在这里进行了ContentProvider的一些创建
false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
if (cph != null) {
cph.noReleaseNeeded = true;
results.add(cph);
}
}

try {
ActivityManager.getService().publishContentProviders(
getApplicationThread(), results);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}

继续跟进installProvider

private ContentProviderHolder installProvider(Context context,
ContentProviderHolder holder, ProviderInfo info,
boolean noisy, boolean noReleaseNeeded, boolean stable) {
ContentProvider localProvider = null;
IContentProvider provider;
if (holder == null || holder.provider == null) {
...
try {
final java.lang.ClassLoader cl = c.getClassLoader();
LoadedApk packageInfo = peekPackageInfo(ai.packageName, true);
if (packageInfo == null) {
// System startup case.
packageInfo = getSystemContext().mPackageInfo;
}
localProvider = packageInfo.getAppFactory()
.instantiateProvider(cl, info.name);
provider = localProvider.getIContentProvider();
if (provider == null) {
Slog.e(TAG, "Failed to instantiate class " +
info.name + " from sourceDir " +
info.applicationInfo.sourceDir);
return null;
}
if (DEBUG_PROVIDER) Slog.v(
TAG, "Instantiating local provider " + info.name);
// XXX Need to create the correct context for this provider.
localProvider.attachInfo(c, info);
} catch (java.lang.Exception e) {
if (!mInstrumentation.onException(null, e)) {
throw new RuntimeException(
"Unable to get provider " + info.name
+ ": " + e.toString(), e);
}
return null;
}
} else {
provider = holder.provider;
if (DEBUG_PROVIDER) Slog.v(TAG, "Installing external provider " + info.authority + ": "
+ info.name);
}
...
return retHolder;
}

看到这句英文注释Need to create the correct context for this provider.需要为此提供者创建正确的上下文,那肯定就是他了,跟进localProvider.attachInfo(c, info)

private void attachInfo(Context context, ProviderInfo info, boolean testing) {
mNoPerms = testing;
mCallingAttributionSource = new ThreadLocal<>();

/*
* Only allow it to be set once, so after the content service gives
* this to us clients can't change it.
*/
if (mContext == null) {
mContext = context;
if (context != null && mTransport != null) {
mTransport.mAppOpsManager = (AppOpsManager) context.getSystemService(
Context.APP_OPS_SERVICE);
}
mMyUid = Process.myUid();
if (info != null) {
setReadPermission(info.readPermission);
setWritePermission(info.writePermission);
setPathPermissions(info.pathPermissions);
mExported = info.exported;
mSingleUser = (info.flags & ProviderInfo.FLAG_SINGLE_USER) != 0;
setAuthorities(info.authority);
}
if (Build.IS_DEBUGGABLE) {
setTransportLoggingEnabled(Log.isLoggable(getClass().getSimpleName(),
Log.VERBOSE));
}
ContentProvider.this.onCreate();//在这里回调了contentProvider的onCreate
}
}

最终我们在这发现回调了onCreate。这里我们只是根据函数名+源码注释+猜测去验证了他们两个的执行顺序,更多细枝末节就没看(其实是看不懂),如果大家想真正搞明白ContentProvider和Application完整的创建流程,大家可以去研究一下Android的源码。

知道了这个之后我们就能理解LeakCanary的做法了,翻其源码也确实是这样。

<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.squareup.leakcanary.objectwatcher"
>

<application>
<provider
android:name="leakcanary.internal.MainProcessAppWatcherInstaller"
android:authorities="${applicationId}.leakcanary-installer"
android:enabled="@bool/leak_canary_watcher_auto_install"
android:exported="false"/>
</application>
</manifest>

那可能有人会疑问,为啥它那定义的清单文件我这也能用捏?其实这是Gradle的功劳,一个app是只能有一个清单文件的,在构建应用的时候,Gradle会合并所有的清单文件,所有文件优先级如下

  1. Product flavors 和构建类型所指定的清单文件。
  2. 应用程序的主清单文件。
  3. 类库的清单文件。

具体的合并逻辑就涉及Gradle的东西了,这里就不说了。那好,那我们的第一个问题就解决了。

LeakCanary是如何监听的

现在我们看MainProcessAppWatcherInstaller

internal class MainProcessAppWatcherInstaller : ContentProvider() {

override fun onCreate(): Boolean {
val application = context!!.applicationContext as Application
AppWatcher.manualInstall(application)
return true
}
}

初始化了AppWatcher

@JvmOverloads
fun manualInstall(
application: Application,
retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),//对检测对象延迟5秒检测
watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)//默认的一些观察器
) {
checkMainThread()
if (isInstalled) {
throw IllegalStateException(
"AppWatcher already installed, see exception cause for prior install call", installCause
)
}
check(retainedDelayMillis >= 0) {
"retainedDelayMillis $retainedDelayMillis must be at least 0 ms"
}
this.retainedDelayMillis = retainedDelayMillis
if (application.isDebuggableBuild) {
LogcatSharkLog.install()//日志的初始化
}
//核心组件,用于检测泄漏和对堆转储(head dump)的分析
LeakCanaryDelegate.loadLeakCanary(application)

//对每一个检测器进行初始化
watchersToInstall.forEach {
it.install()
}
// Only install after we're fully done with init.
installCause = RuntimeException("manualInstall() first called here")
}

我们先不看核心组件,它比较复杂,我们先看看默认的检测器是如何对Activity、Fragment、Viewmodel等的监听的,先看appDefaultWatchers(application)这个生成默认检测器的方法

fun appDefaultWatchers(
application: Application,
reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
return listOf(
ActivityWatcher(application, reachabilityWatcher),//对Activity的检测器
FragmentAndViewModelWatcher(application, reachabilityWatcher),//对Fragment和Viewmodel的检测器
RootViewWatcher(reachabilityWatcher),//对RootView的检测器
ServiceWatcher(reachabilityWatcher)//对Service的检测器
)
}

创建各个检测器,传入objectWatcher和application,这四个监听器我们一个一个来看

ActivityWatcher
class ActivityWatcher(
private val application: Application,
private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {

private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityDestroyed(activity: Activity) {
reachabilityWatcher.expectWeaklyReachable(
activity, "${activity::class.java.name} received Activity#onDestroy() callback"
)
}
}

override fun install() {
application.registerActivityLifecycleCallbacks(lifecycleCallbacks)//注册Activity的生命周期监听
}

override fun uninstall() {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
}
}

可以看到,通过Activity生命周期对的Application.ActivityLifecycleCallbacks回调来达到监听Activity的结束。其实就是在Activity快onDestroy的时候调用了Application.ActivityLifecycleCallbacks的onActivityDestroyed。然后在onActivityDestroyed里面调用了reachabilityWatcher的expectWeaklyReachable,reachabilityWatcher就是刚刚创建的时候传进来的,我们返回去看看reachabilityWatcher对expectWeaklyReachable的实现。跟踪发现,传进来的是objectWatcher,点击跟踪

/**
* The [ObjectWatcher] used by AppWatcher to detect retained objects.
* Only set when [isInstalled] is true.
*/
val objectWatcher = ObjectWatcher(
clock = { SystemClock.uptimeMillis() },
checkRetainedExecutor = {
check(isInstalled) {
"AppWatcher not installed"
}
mainHandler.postDelayed(it, retainedDelayMillis)
},
isEnabled = { true }
)

我们这里看到传入时间、一个Executor任务执行器,该任务执行器将传入的任务交给主线程的Handler延时处理,延时retainedDelayMillis就是刚刚初始化AppWatcher默认设置的5秒,看看ObjectWatcher对expectWeaklyReachable的实现,

@Synchronized override fun expectWeaklyReachable(
watchedObject: Any,
description: String
) {
if (!isEnabled()) {
return
}
removeWeaklyReachableObjects()//先清空观察区里已经被回收的对象
val key = UUID.randomUUID()
.toString()
val watchUptimeMillis = clock.uptimeMillis()
val reference =
KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
SharkLog.d {
"Watching " +
(if (watchedObject is Class<*>) watchedObject.toString() else "instance of ${watchedObject.javaClass.name}") +
(if (description.isNotEmpty()) " ($description)" else "") +
" with key $key"
}

watchedObjects[key] = reference
checkRetainedExecutor.execute {
moveToRetained(key)
}
}

其实之前仿写的LeakCanary有点故意模仿LeakCanary的意思,这不LeakCanary里面也有观察区和留存区,只不过我们为了好获取泄漏对象而用了一个列表存储,这里没有列表存储,而是直接回调出去通知。每次观测之前先清除观察区已经被回收的对象

private fun removeWeaklyReachableObjects() {
// WeakReferences are enqueued as soon as the object to which they point to becomes weakly
// reachable. This is before finalization or garbage collection has actually happened.
var ref: KeyedWeakReference?
do {
ref = queue.poll() as KeyedWeakReference?
if (ref != null) {//不为空说明已经被回收
watchedObjects.remove(ref.key)
}
} while (ref != null)
}

然后就和我们刚仿写的差不多,生成一个唯一的UUID,将弱引用引用对象,再登记,只不过这里它记录了时间。最后执行moveToRetained(key)

@Synchronized private fun moveToRetained(key: String) {
removeWeaklyReachableObjects()
val retainedRef = watchedObjects[key]
if (retainedRef != null) {
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
//责任链模式
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}

这时候如果还有对象存在观察区,说明已经是可能内存泄漏的对象,然后记录此时的时间,再回调监听,onObjectRetainedListeners在哪设置的呢,在刚刚的核心组件里面,等会分析。

至此我们分析完LeakCanary是如何监听Activity生命周期和检测是否泄露的。接下来看Fragment和Viewmodel

FragmentAndViewModelWatcher
class FragmentAndViewModelWatcher(
private val application: Application,
private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {

private val fragmentDestroyWatchers: List<(Activity) -> Unit> = run {
val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit>()

//版本的适配
if (SDK_INT >= O) {
fragmentDestroyWatchers.add(
AndroidOFragmentDestroyWatcher(reachabilityWatcher)
)
}

getWatcherIfAvailable(
ANDROIDX_FRAGMENT_CLASS_NAME,
ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,//对应androidx版本
reachabilityWatcher
)?.let {
fragmentDestroyWatchers.add(it)
}

getWatcherIfAvailable(
ANDROID_SUPPORT_FRAGMENT_CLASS_NAME,
ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,//对应之前的老版本
reachabilityWatcher
)?.let {
fragmentDestroyWatchers.add(it)
}
fragmentDestroyWatchers
}

private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityCreated(
activity: Activity,
savedInstanceState: Bundle?
) {
//不同版本的fragmentDestroyWatcher进行监听注册
for (watcher in fragmentDestroyWatchers) {
watcher(activity)
}
}
}

override fun install() {
application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
}

override fun uninstall() {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
}
...
}

这里给activity注册一个监听,然后遍历里面的fragmentwatcher调用incoke设置fragment的监听,其他版本的fragmentDestroyWatcher就不看了和ActivityWatcher差不多,我们看看AndroidXFragmentDestroyWatcher

internal class AndroidXFragmentDestroyWatcher(
private val reachabilityWatcher: ReachabilityWatcher
) : (Activity) -> Unit {

private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {

override fun onFragmentCreated(
fm: FragmentManager,
fragment: Fragment,
savedInstanceState: Bundle?
) {
//监听viewmodel相关
ViewModelClearedWatcher.install(fragment, reachabilityWatcher)
}

override fun onFragmentViewDestroyed(
fm: FragmentManager,
fragment: Fragment
) {
val view = fragment.view
if (view != null) {
//对view泄漏的回调
reachabilityWatcher.expectWeaklyReachable(
view, "${fragment::class.java.name} received Fragment#onDestroyView() callback " +
"(references to its views should be cleared to prevent leaks)"
)
}
}

//和activitywatcher一样,不多说
override fun onFragmentDestroyed(
fm: FragmentManager,
fragment: Fragment
) {
reachabilityWatcher.expectWeaklyReachable(
fragment, "${fragment::class.java.name} received Fragment#onDestroy() callback"
)
}
}

override fun invoke(activity: Activity) {
if (activity is FragmentActivity) {
val supportFragmentManager = activity.supportFragmentManager
//通过fragmentmanager监听fragment生命周期
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
//初始化viewmodel
ViewModelClearedWatcher.install(activity, reachabilityWatcher)
}
}
}

其实跟activity的差不多,不过是通过activity设置fragment的监听,这里可能有个高阶函数大家没见过,也是invoke的一种用法吧,可以了解一下。好,我们现在来看看对viewmodel的监听过程:

ViewModelClearedWatcher
internal class ViewModelClearedWatcher(
storeOwner: ViewModelStoreOwner,
private val reachabilityWatcher: ReachabilityWatcher
) : ViewModel() {

// We could call ViewModelStore#keys with a package spy in androidx.lifecycle instead,
// however that was added in 2.1.0 and we support AndroidX first stable release. viewmodel-2.0.0
// does not have ViewModelStore#keys. All versions currently have the mMap field.
private val viewModelMap: Map<String, ViewModel>? = try {
//通过反射获取viewmodelstore
val mMapField = ViewModelStore::class.java.getDeclaredField("mMap")
mMapField.isAccessible = true
@Suppress("UNCHECKED_CAST")
mMapField[storeOwner.viewModelStore] as Map<String, ViewModel>
} catch (ignored: Exception) {
null
}

override fun onCleared() {
//当此viewmodel销毁时,意味着viewmodelstore里其他的viewmodel也将被销毁
viewModelMap?.values?.forEach { viewModel ->
reachabilityWatcher.expectWeaklyReachable(
viewModel, "${viewModel::class.java.name} received ViewModel#onCleared() callback"
)
}
}

companion object {
fun install(
storeOwner: ViewModelStoreOwner,
reachabilityWatcher: ReachabilityWatcher
) {
//这就很巧妙,通过将自己插入viewmodelstore来监控通一宿主的viewmodel
val provider = ViewModelProvider(storeOwner, object : Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T =
ViewModelClearedWatcher(storeOwner, reachabilityWatcher) as T
})
provider.get(ViewModelClearedWatcher::class.java)
}
}
}

我们发现它居然是个viewmodel,而且它还把它自己插入了宿主的viewmodelstore。把自己当作间谍插入敌军来达到监听的目的。具体viewmodestore是如何管理同一宿主的多个viewmodel的这里就不解释了,大家可以看看viewmodel的源码(其实是我不会)。

好,再来看看rootView

RootViewWatcher

有人可能会问rootview也会发生内存泄露吗?会的,只是不常见,比如,此时我自定义了一个Toast弹窗,弹的是xml的布局,然后Toast被我声明成了静态方法,这时候如果我一弹窗,消失,之后LeakCanary就提醒你了,内存泄漏。自定义的toast代码如下

object ToastUtil {
var mToast: Toast? = null

fun show() {
val inflater = LayoutInflater.from(App.appContext)
val toastView: View = inflater.inflate(R.layout.toast, null)
if (mToast == null) {
mToast = Toast(App.appContext)
}
mToast!!.setGravity(Gravity.TOP, 0, 0)
mToast!!.duration = Toast.LENGTH_SHORT
mToast!!.view = toastView
mToast!!.show()
}
}

这样的自定义Toast工具是内存泄漏的,为什么捏,因为object单例内部的变量是静态的,所以mToast是静态的,而它又引用了toastView,而toastView是rootView绘制的xml布局(原理见view的绘制),因此一系列下来导致弹完吐司之后,rootView绘制完了而还在被mToastView引用导致内存泄漏。事实上,你会发现,mToast.view已经被废弃,谷歌官方也不建议我们自定义toast,因为这样确实容易造成内存泄漏,更推荐我们自定义snackBar。

我们再看RootViewWatcher

class RootViewWatcher(
private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {

private val listener = OnRootViewAddedListener { rootView ->
...
if (trackDetached) {
rootView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {

val watchDetachedView = Runnable {
reachabilityWatcher.expectWeaklyReachable(
rootView, "${rootView::class.java.name} received View#onDetachedFromWindow() callback"
)
}
override fun onViewAttachedToWindow(v: View) {
mainHandler.removeCallbacks(watchDetachedView)
}

override fun onViewDetachedFromWindow(v: View) {
mainHandler.post(watchDetachedView)
}
})
}
}

override fun install() {
Curtains.onRootViewsChangedListeners += listener
}

override fun uninstall() {
Curtains.onRootViewsChangedListeners -= listener
}
}

也不过是对rootview注册监听,具体的原理不说了,跟Activity的方式差不多。

ServiceWatcher

我们再看看ServiceWatcher

class ServiceWatcher(private val reachabilityWatcher: ReachabilityWatcher) : InstallableWatcher {
...
override fun install() {
checkMainThread()
check(uninstallActivityThreadHandlerCallback == null) {
"ServiceWatcher already installed"
}
check(uninstallActivityManager == null) {
"ServiceWatcher already installed"
}
try {
// hook ActivityThread 里面的 mH 的 mCallback
swapActivityThreadHandlerCallback { mCallback ->
uninstallActivityThreadHandlerCallback = {
swapActivityThreadHandlerCallback {
mCallback
}
}
// 代理对象,替换原来的Callback
Handler.Callback { msg ->
// https://github.com/square/leakcanary/issues/2114
// On some Motorola devices (Moto E5 and G6), the msg.obj returns an ActivityClientRecord
// instead of an IBinder. This crashes on a ClassCastException. Adding a type check
// here to prevent the crash.
if (msg.obj !is IBinder) {
return@Callback false
}

// 拦截 STOP_SERVICE 消息,这里主要是预处理获取到即将要被 destroy 的 service 对象
if (msg.what == STOP_SERVICE) {
val key = msg.obj as IBinder
activityThreadServices[key]?.let {
onServicePreDestroy(key, it)
}
}
// 执行原有逻辑
mCallback?.handleMessage(msg) ?: false
}
}

// hook 替换原来的ActivityManageService 对象
swapActivityManager { activityManagerInterface, activityManagerInstance ->
uninstallActivityManager = {
swapActivityManager { _, _ ->
activityManagerInstance
}
}
// 动态代理对象
Proxy.newProxyInstance(
activityManagerInterface.classLoader, arrayOf(activityManagerInterface)
) { _, method, args ->
// hook 到 service 真正 destroy 的时机,这里没法获取到servcie对象,所以要前面的预操作:onServicePreDestroy
if (METHOD_SERVICE_DONE_EXECUTING == method.name) {
val token = args!![0] as IBinder
if (servicesToBeDestroyed.containsKey(token)) {
// 回调监测
onServiceDestroyed(token)
}
}
// 执行原有逻辑
try {
if (args == null) {
method.invoke(activityManagerInstance)
} else {
method.invoke(activityManagerInstance, *args)
}
} catch (invocationException: InvocationTargetException) {
throw invocationException.targetException
}
}
}
} catch (ignored: Throwable) {
SharkLog.d(ignored) { "Could not watch destroyed services" }
}
}
...
private fun onServiceDestroyed(token: IBinder) {
// 通过 token 匹配到预处理时获取到的 service 对象
servicesToBeDestroyed.remove(token)?.also { serviceWeakReference ->
serviceWeakReference.get()?.let { service ->
// 将 service 对象加入到 watchedObjects 里面,之后就和activity一样了
reachabilityWatcher.expectWeaklyReachable(
service, "${service::class.java.name} received Service#onDestroy() callback"
)
}
}
}
...
}

对Service的监控可能就比较奇怪了,因为Service没有对外公开Service生命周期的监听方式,所以只能hook ,所以这里面就涉及了Service的底层原理和hook技术,要真正理解的话得知道Service的底层原理的hook,这里简单说一下:启动Service的时候,在ActivityThread里面都会有记录,启动的Service都会存进mServices里面,onDestory的时候,AMS会用Handler发给ActivityThread告诉某某Service要销毁了,此时ActivityThread就会通过IActivityManger回调Service的onDestory方法,IActivityManger存储了四大组件的周期函数,所以通过它来调用Service的周期函数。因此原理也是这样,通过hook AMS发过来Service onDestory的信息记录Service,进而在IActivityManger回调Service onDestory的时候找到这个Service并监测。

监听之后是怎么处理的呢?

现在几个监测的类讲完了,那么第二个问题也解决了,接下来我们看看刚刚还没呢分析的核心组件

InternalLeakCanary
@JvmOverloads
fun manualInstall(
application: Application,
retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),//对检测对象延迟5秒检测
watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)//默认的一些观察器
) {
checkMainThread()
if (isInstalled) {
throw IllegalStateException(
"AppWatcher already installed, see exception cause for prior install call", installCause
)
}
check(retainedDelayMillis >= 0) {
"retainedDelayMillis $retainedDelayMillis must be at least 0 ms"
}
this.retainedDelayMillis = retainedDelayMillis
if (application.isDebuggableBuild) {
LogcatSharkLog.install()//日志的初始化
}
//核心组件,用于检测泄漏和对堆转储(head dump)的分析
LeakCanaryDelegate.loadLeakCanary(application)

//对每一个检测器进行初始化
watchersToInstall.forEach {
it.install()
}
// Only install after we're fully done with init.
installCause = RuntimeException("manualInstall() first called here")
}

看看LeakCanaryDelegate.loadLeakCanary(application)

internal object LeakCanaryDelegate {

@Suppress("UNCHECKED_CAST")
val loadLeakCanary by lazy {
try {
//通过反射实例化InternalLeakCanary,并调用了invoke方法
val leakCanaryListener = Class.forName("leakcanary.internal.InternalLeakCanary")
leakCanaryListener.getDeclaredField("INSTANCE")
.get(null) as (Application) -> Unit
} catch (ignored: Throwable) {
NoLeakCanary
}
}

object NoLeakCanary : (Application) -> Unit, OnObjectRetainedListener {
override fun invoke(application: Application) {
}

override fun onObjectRetained() {
}
}
}

为什么要通过反射实例化咧?因为InternalLeakCanary在另一个模块,而他又是internal,所以只能通过反射了。那我们看看InternalLeakCanary复写的invoke方法

override fun invoke(application: Application) {
_application = application//传入application

checkRunningInDebuggableBuild()

//哦~原来是在这设置的OnObjectRetainedListener
AppWatcher.objectWatcher.addOnObjectRetainedListener(this)

//创建gc触发器,这样的GC更容易触发垃圾回收
val gcTrigger = GcTrigger.Default

val configProvider = { LeakCanary.config }

val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
handlerThread.start()
val backgroundHandler = Handler(handlerThread.looper)

// 创建分析heap dump的启动器,heap dump堆转储,上面提过
heapDumpTrigger = HeapDumpTrigger(
application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger,
configProvider
)
// 应用前后台监听,前后台监听逻辑差异化处理
application.registerVisibilityListener { applicationVisible ->
this.applicationVisible = applicationVisible
heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
}
registerResumedActivityListener(application)
//桌面添加图标
addDynamicShortcut(application)

// We post so that the log happens after Application.onCreate()
mainHandler.post {
// https://github.com/square/leakcanary/issues/1981
// We post to a background handler because HeapDumpControl.iCanHasHeap() checks a shared pref
// which blocks until loaded and that creates a StrictMode violation.
backgroundHandler.post {
SharkLog.d {
when (val iCanHasHeap = HeapDumpControl.iCanHasHeap()) {
is Yup -> application.getString(R.string.leak_canary_heap_dump_enabled_text)
is Nope -> application.getString(
R.string.leak_canary_heap_dump_disabled_text, iCanHasHeap.reason()
)
}
}
}
}
}

原来我们之前在moveToRetain里回调的方法在这注册的。

@Synchronized private fun moveToRetained(key: String) {
removeWeaklyReachableObjects()
val retainedRef = watchedObjects[key]
if (retainedRef != null) {
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
//责任链模式
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}

那我们看看InternalLeakCanary对onObjectRetained()的实现,发现其调用的是scheduleRetainedObjectCheck()

fun scheduleRetainedObjectCheck() {
if (this::heapDumpTrigger.isInitialized) {
heapDumpTrigger.scheduleRetainedObjectCheck()
}
}

调用的是scheduleRetainedObjectCheck()

fun scheduleRetainedObjectCheck(
delayMillis: Long = 0L
) {
val checkCurrentlyScheduledAt = checkScheduledAt
if (checkCurrentlyScheduledAt > 0) {//通过记录时间戳来避免重复检测
return
}
checkScheduledAt = SystemClock.uptimeMillis() + delayMillis//记录时间
backgroundHandler.postDelayed({
checkScheduledAt = 0
checkRetainedObjects()//检测留存的对象
}, delayMillis)
}

通过记录时间避免重复检测,然后向子线程post了一Runnable,瞅瞅checkRetainedObjects

private fun checkRetainedObjects() {
//是否能够heap dump
val iCanHasHeap = HeapDumpControl.iCanHasHeap()

val config = configProvider()

if (iCanHasHeap is Nope) {
if (iCanHasHeap is NotifyingNope) {//发送一个通知,用户点击后通过
// Before notifying that we can't dump heap, let's check if we still have retained object.
var retainedReferenceCount = objectWatcher.retainedObjectCount

if (retainedReferenceCount > 0) {
gcTrigger.runGc()//分析前再确保一次是否真的泄漏,调用一次GC
retainedReferenceCount = objectWatcher.retainedObjectCount
}

val nopeReason = iCanHasHeap.reason()
//主要是判断是否达到阈值,前台的时候是>=5个会触发,后台是>=1个就会触发
val wouldDump = !checkRetainedCount(
retainedReferenceCount, config.retainedVisibleThreshold, nopeReason
)

if (wouldDump) {
val uppercaseReason = nopeReason[0].toUpperCase() + nopeReason.substring(1)
onRetainInstanceListener.onEvent(DumpingDisabled(uppercaseReason))
showRetainedCountNotification(//通知
objectCount = retainedReferenceCount,
contentText = uppercaseReason
)
}
} else {
SharkLog.d {
application.getString(
R.string.leak_canary_heap_dump_disabled_text, iCanHasHeap.reason()
)
}
}
return
}
//如果不能堆转储,进行了一些列操作,然后再尝试分析
var retainedReferenceCount = objectWatcher.retainedObjectCount

if (retainedReferenceCount > 0) {
gcTrigger.runGc()
retainedReferenceCount = objectWatcher.retainedObjectCount
}

if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return

val now = SystemClock.uptimeMillis()
val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) {
onRetainInstanceListener.onEvent(DumpHappenedRecently)
showRetainedCountNotification(
objectCount = retainedReferenceCount,
contentText = application.getString(R.string.leak_canary_notification_retained_dump_wait)
)
scheduleRetainedObjectCheck(
delayMillis = WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis
)
return
}

dismissRetainedCountNotification()
val visibility = if (applicationVisible) "visible" else "not visible"
//分析hprof文件
dumpHeap(
retainedReferenceCount = retainedReferenceCount,
retry = true,
reason = "$retainedReferenceCount retained objects, app is $visibility"
)
}

先看看能不能堆转储,如果可以,就发送通知,如果不能,做了一系列操作,再尝试分析,其实这跟MAT有点类似,MAT也是不能直接就解析hprof文件,最后你会发现,最终调用的都是dumpHeap()方法,我们看dumpHeap方法

HeapDumpTrigger.kt
private fun dumpHeap(
retainedReferenceCount: Int,
retry: Boolean,
reason: String
) {
val directoryProvider =
InternalLeakCanary.createLeakDirectoryProvider(InternalLeakCanary.application)
val heapDumpFile = directoryProvider.newHeapDumpFile()//创建导出的文件夹

val durationMillis: Long
if (currentEventUniqueId == null) {
currentEventUniqueId = UUID.randomUUID().toString()
}
try {
InternalLeakCanary.sendEvent(DumpingHeap(currentEventUniqueId!!))
if (heapDumpFile == null) {
throw RuntimeException("Could not create heap dump file")
}
saveResourceIdNamesToMemory()
val heapDumpUptimeMillis = SystemClock.uptimeMillis()
KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
durationMillis = measureDurationMillis {
configProvider().heapDumper.dumpHeap(heapDumpFile)//主要方法,用系统导出堆转储文件
}
if (heapDumpFile.length() == 0L) {
throw RuntimeException("Dumped heap file is 0 byte length")
}
lastDisplayedRetainedObjectCount = 0
lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()
objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
currentEventUniqueId = UUID.randomUUID().toString()
//将导出情况回调出去,根据失败和成功给用户弹通知(一般是成功)
InternalLeakCanary.sendEvent(HeapDump(currentEventUniqueId!!, heapDumpFile, durationMillis, reason))
} catch (throwable: Throwable) {
InternalLeakCanary.sendEvent(HeapDumpFailed(currentEventUniqueId!!, throwable, retry))
if (retry) {
scheduleRetainedObjectCheck(
delayMillis = WAIT_AFTER_DUMP_FAILED_MILLIS
)
}
showRetainedCountNotification(
objectCount = retainedReferenceCount,
contentText = application.getString(
R.string.leak_canary_notification_retained_dump_failed
)
)
return
}
}

首先创建了文件夹然后是保存时间,ID啊一些的操作,最主要的是configProvider().heapDumper.dumpHeap(heapDumpFile),查找它的实现其实就是

object AndroidDebugHeapDumper : HeapDumper {
override fun dumpHeap(heapDumpFile: File) {
Debug.dumpHprofData(heapDumpFile.absolutePath)
}
}

用系统的Debug工具调出hprof文件。之后我们看导出成功回调做了什么事情

object RemoteWorkManagerHeapAnalyzer : EventListener {

private const val REMOTE_SERVICE_CLASS_NAME = "leakcanary.internal.RemoteLeakCanaryWorkerService"

internal val remoteLeakCanaryServiceInClasspath by lazy {//返回true
try {
Class.forName(REMOTE_SERVICE_CLASS_NAME)
true
} catch (ignored: Throwable) {
false
}
}

override fun onEvent(event: Event) {
if (event is HeapDump) {
val application = InternalLeakCanary.application
val heapAnalysisRequest =//构建一个workmanagerRequest,执行一次
OneTimeWorkRequest.Builder(RemoteHeapAnalyzerWorker::class.java).apply {
val dataBuilder = Data.Builder()
.putString(ARGUMENT_PACKAGE_NAME, application.packageName)
.putString(ARGUMENT_CLASS_NAME, REMOTE_SERVICE_CLASS_NAME)
setInputData(event.asWorkerInputData(dataBuilder))
with(WorkManagerHeapAnalyzer) {//任务内容在WorkManagerHeapAnalyzer里面
addExpeditedFlag()
}
}.build()
SharkLog.d { "Enqueuing heap analysis for ${event.file} on WorkManager remote worker" }
val workManager = WorkManager.getInstance(application)
workManager.enqueue(heapAnalysisRequest)
}
}
}

我们发现根据回调的Event类型,回调到了这里,传进来的Event就是HeapDump,可以看到用WorkManager创建了一个异步任务WorkManagerHeapAnalyzer,然后执行

internal class RemoteHeapAnalyzerWorker(appContext: Context, workerParams: WorkerParameters) :
RemoteListenableWorker(appContext, workerParams) {

override fun startRemoteWork(): ListenableFuture<Result> {
val heapDump = inputData.asEvent<HeapDump>()
val result = SettableFuture.create<Result>()
heapAnalyzerThreadHandler.post {//开启子线程执行分析任务
val doneEvent = AndroidDebugHeapAnalyzer.runAnalysisBlocking(heapDump, isCanceled = {
result.isCancelled
}) { progressEvent ->
if (!result.isCancelled) {//将分析进度发送出去
InternalLeakCanary.sendEvent(progressEvent)
}
}
if (result.isCancelled) {
SharkLog.d { "Remote heap analysis for ${heapDump.file} was canceled" }
} else {
InternalLeakCanary.sendEvent(doneEvent)//结束
result.set(Result.success())
}
}
return result
}

override fun getForegroundInfoAsync(): ListenableFuture<ForegroundInfo> {
return applicationContext.heapAnalysisForegroundInfoAsync()
}
}

heapAnalyzerThreadHandler实际上就是开启一个子线程然后执行runAnalysisBlocking,runAnalysisBlocking里面就是把文件交给了HeapAnalyzer去分析,HeapAnalyzer然后用Shark 分析hprof文件,具体Shark分析的原理就不说了,我也没怎么研究过,我觉得也没有用,会用就行了。Shark是LeakCanary的一个分析hprof文件的模块,因此我们也可以用shark开发一个用于线上的SDK,LeakCanary现在是不支持线上检测的。

用一张图总结一下它的原理其实就是

AppWatcher -> objectWatcher:检测到泄漏
objectWatcher -> InternalLeakCanary:5秒后gc还是没回收
InternalLeakCanary->HeapDumpTrigger:scheduleRetainedObjectCheck()
HeapDumpTrigger->HeapAnalyzerThreadHandler:再强制GC一次,还是泄漏
HeapAnalyzerThreadHandler->HeapAnalyzer:dumpHeap()
HeapAnalyzer->Shark分析:
Shark分析 -> dataBase:发出通知,数据存进数据库

可能还有人会疑另一个叫Leaks的app图标怎么生成的,其实它也不算是个app,它是你app的一部分,不信你把Leaks卸载了,你的app也会跟着卸载。实现这个很简单,用标签就能实现。示例如下

<application
. . .>

. . .

<activity
android:name=".SecondActivity"/>

<activity-alias
android:name="com.jason.demo.dynamicshortcut.ShortcutLauncherActivity"
android:enabled="true"
android:icon="@mipmap/ic_launcher_alias"
android:label="ActivityAlias"
android:targetActivity=".SecondActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>

</application>

其中,android:targetActivity 指定所必须的打开的目标 Activity,对应着一个在 AndroidManifest.xml 中申明的 <activity>android:name 是别名的唯一名称,不引用实际类android:icon 以及 android:label 指定一个新的图标和标签给用户。之后就能看见你的app生成了两个图标。

你如果不想LeakCanary自动初始化也是可以的。设置如下

<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="leak_canary_watcher_auto_install">false</bool>
</resources>

然后在你想要初始化的地方AppWatcher.manualInstall即可

好了,LeakCanary源码算简单分析了一下,不算细致,可以借鉴一下。下面是一些常见的内存泄漏

  1. 单例模式引发的内存泄漏

    原因:单例模式里的静态实例持有对象的引用,导致对象无法被回收,常见为持有Activity的引用

    优化:改为持有Application的引用,或者不持有使用的时候传递。

  2. 集合操作不当引发的内存泄漏

    原因:集合只增不减

    优化:有对应的删除或卸载操作

  3. 线程的操作不当引发的内存泄漏

    原因:线程持有对象的引用在后台执行,与对象的生命周期不一致

    优化:静态实例+弱引用(WeakReference)方式,使其生命周期一致

  4. 匿名内部类/非静态内部类操作不当引发的内存泄漏

    原因:内部类持有对象引用,导致无法释放,比如各种回调

    优化:保持生命周期一致,改为静态实例+对象的弱引用方式(WeakReference)

  5. 常用的资源未关闭回收引发的内存泄漏

    原因:BroadcastReceiver,File,Cursor,IO流,Bitmap等资源使用未关闭

    优化:使用后有对应的关闭和卸载机制

  6. Handler使用不当造成的内存泄漏

    原因:Handler持有Activity的引用,其发送的Message中持有Handler的引用,当队列处理Message的时间过长会导致Handler无法被回收

    优化:静态实例+弱引用(WeakReference)方式

好了,LeakCanary源码算简单分析了一下,不算细致,可以借鉴一下。