leakcanary2源码分析

leakcanary 2.0相比1.6来说多了些骚操作,比如install不需要手动调了。另外代码也切到kotlin了,同时可以监控support包下fragment的内存泄露了。

不用手动install了?

相比于之前的1.6的版本,leakcanary的引入只需要在build.gradle中加:

1
2
3
4
dependencies {
// debugImplementation because LeakCanary should only run in debug builds.
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0-alpha-3'
}

而不需要再手动调用初始化代码了。原先因为不同进程会有各自的application对象,所以在application的onCreate中需要判断进程然后手动调用leakcanary.install把application对象传入,然后利用application给activity注册lifecycle callbacks来监听activity的onDestroyed。现在为啥不用手动install了?

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
➜  leakcanary git:(master) tree -L 1
.
├── LICENSE.txt
├── README-1.6.md
├── README.md
├── build.gradle
├── checkstyle.xml
├── docs
├── gradle
├── gradle.properties
├── gradlew
├── leakcanary-analyzer // memory leak analyzer
├── leakcanary-android
├── leakcanary-android-core // 核心Android代码逻辑,包括界面和dump文件等
├── leakcanary-android-instrumentation // 单元测试相关代码
├── leakcanary-android-process
├── leakcanary-haha // hprof文件解析
├── leakcanary-leaksentry // 监控
├── leakcanary-log
├── leakcanary-sample
├── leakcanary-watcher
├── leakcanary.iml
├── local.properties
├── mkdocs.yml
└── settings.gradle

首先看下leakcanary源码层级,在leakcanary-leaksentry中我们找到了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Content providers are loaded before the application class is created. [LeakSentryInstaller] is
* used to install [leaksentry.LeakSentry] on application start.
*/
internal class LeakSentryInstaller : ContentProvider() {

override fun onCreate(): Boolean {
CanaryLog.logger = DefaultCanaryLog()
val application = context!!.applicationContext as Application
InternalLeakSentry.install(application)
return true
}

......
}

可看到是LeakSentryInstaller这个ContentProvider的onCreate里执行了InternalLeakSentry.install(application),我们知道 ContentProvider的onCreate是在application onCreate之前调的 ,所以才省掉了手动install的操作。具体可以从源码验证:
[ActivityThread.main()]

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
public static void main(String[] args) {
......

Looper.prepareMainLooper();

......

ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);

if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}

......

Looper.loop();

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

@UnsupportedAppUsage
private void attach(boolean system, long startSeq) {
sCurrentActivityThread = this;
mSystemThread = system;
if (!system) {
......

final IActivityManager mgr = ActivityManager.getService();
try {
mgr.attachApplication(mAppThread, startSeq);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
......
}
......
}

接着调到了AMS的attachApplication

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
@Override
public final void attachApplication(IApplicationThread thread, long startSeq) {
synchronized (this) {
int callingPid = Binder.getCallingPid();
final int callingUid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
attachApplicationLocked(thread, callingPid, callingUid, startSeq);
Binder.restoreCallingIdentity(origId);
}
}

@GuardedBy("this")
private final boolean attachApplicationLocked(IApplicationThread thread,
int pid, int callingUid, long startSeq) {

// Tell the process all about itself.

if (DEBUG_ALL) Slog.v(
TAG, "Binding process pid " + pid + " to record " + app);

final String processName = app.processName;
try {
AppDeathRecipient adr = new AppDeathRecipient(
app, pid, thread);
thread.asBinder().linkToDeath(adr, 0);
app.deathRecipient = adr;
} catch (RemoteException e) {
app.resetPackageList(mProcessStats);
startProcessLocked(app, "link fail", processName);
return false;
}

......

if (app.isolatedEntryPoint != null) {
// This is an isolated process which should just call an entry point instead of
// being bound to an application.
thread.runIsolatedEntryPoint(app.isolatedEntryPoint, app.isolatedEntryPointArgs);
} else if (app.instr != null) {
thread.bindApplication(processName, appInfo, providers,
app.instr.mClass,
profilerInfo, app.instr.mArguments,
app.instr.mWatcher,
app.instr.mUiAutomationConnection, testMode,
mBinderTransactionTrackingEnabled, enableTrackAllocation,
isRestrictedBackupMode || !normalMode, app.persistent,
new Configuration(getGlobalConfiguration()), app.compat,
getCommonServicesLocked(app.isolated),
mCoreSettingsObserver.getCoreSettingsLocked(),
buildSerial, isAutofillCompatEnabled);
} else {
thread.bindApplication(processName, appInfo, providers, null, profilerInfo,
null, null, null, testMode,
mBinderTransactionTrackingEnabled, enableTrackAllocation,
isRestrictedBackupMode || !normalMode, app.persistent,
new Configuration(getGlobalConfiguration()), app.compat,
getCommonServicesLocked(app.isolated),
mCoreSettingsObserver.getCoreSettingsLocked(),
buildSerial, isAutofillCompatEnabled);
}
......

return true;
}

接着调到了IApplicationThread这个aidl接口的bindApplication,具体实现是ActivityThread的内部类ApplicationThread:

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
    public final void bindApplication(String processName, ApplicationInfo appInfo,
List<ProviderInfo> providers, 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, boolean autofillCompatibilityEnabled) {
......

AppBindData data = new AppBindData();
data.processName = processName;
data.appInfo = appInfo;
data.providers = providers;
data.instrumentationName = instrumentationName;
data.instrumentationArgs = instrumentationArgs;
data.instrumentationWatcher = instrumentationWatcher;
data.instrumentationUiAutomationConnection = instrumentationUiConnection;
data.debugMode = debugMode;
data.enableBinderTracking = enableBinderTracking;
data.trackAllocation = trackAllocation;
data.restrictedBackupMode = isRestrictedBackupMode;
data.persistent = persistent;
data.config = config;
data.compatInfo = compatInfo;
data.initProfilerInfo = profilerInfo;
data.buildSerial = buildSerial;
data.autofillCompatibilityEnabled = autofillCompatibilityEnabled;
// 发送BIND_APPLICATION
sendMessage(H.BIND_APPLICATION, data);
}

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);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
......

@UnsupportedAppUsage
private void handleBindApplication(AppBindData data) {
......

// 创建app ctx
final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
updateLocaleListFromAppContext(appContext,
mResourcesManager.getConfiguration().getLocales());

// Continue loading instrumentation.
if (ii != null) {
ApplicationInfo instrApp;
try {
instrApp = getPackageManager().getApplicationInfo(ii.packageName, 0,
UserHandle.myUserId());
} catch (RemoteException e) {
instrApp = null;
}
if (instrApp == null) {
instrApp = new ApplicationInfo();
}
ii.copyTo(instrApp);
instrApp.initForUser(UserHandle.myUserId());
final LoadedApk pi = getPackageInfo(instrApp, data.compatInfo,
appContext.getClassLoader(), false, true, false);
final ContextImpl instrContext = ContextImpl.createAppContext(this, pi);

try {
// 反射创建mInstrumentation
final ClassLoader cl = instrContext.getClassLoader();
mInstrumentation = (Instrumentation)
cl.loadClass(data.instrumentationName.getClassName()).newInstance();
} catch (Exception e) {
throw new RuntimeException(
"Unable to instantiate instrumentation "
+ data.instrumentationName + ": " + e.toString(), e);
}
......
}
} else {
mInstrumentation = new Instrumentation();
mInstrumentation.basicInit(this);
}
......
try {
......

if (!data.restrictedBackupMode) {
if (!ArrayUtils.isEmpty(data.providers)) {
// 初始化ContentProviders
installContentProviders(app, data.providers);
// For process that contains content providers, we want to
// ensure that the JIT is enabled "at some point".
mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);
}
}

// Do this after providers, since instrumentation tests generally start their
// test thread at this point, and we don't want that racing.
try {
mInstrumentation.onCreate(data.instrumentationArgs);
}
catch (Exception e) {
throw new RuntimeException(
"Exception thrown in onCreate() of "
+ data.instrumentationName + ": " + e.toString(), e);
}
try {
// 回调application的onCreate
mInstrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
if (!mInstrumentation.onException(app, e)) {
throw new RuntimeException(
"Unable to create application " + app.getClass().getName()
+ ": " + e.toString(), e);
}
}
}
......
}

一路调用到了installContentProviders

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
@UnsupportedAppUsage
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,
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();
}
}

......

/**
* Installs the provider.
*
* Providers that are local to the process or that come from the system server
* may be installed permanently which is indicated by setting noReleaseNeeded to true.
* Other remote providers are reference counted. The initial reference count
* for all reference counted providers is one. Providers that are not reference
* counted do not have a reference count (at all).
*
* This method detects when a provider has already been installed. When this happens,
* it increments the reference count of the existing provider (if appropriate)
* and returns the existing provider. This can happen due to concurrent
* attempts to acquire the same provider.
*/
@UnsupportedAppUsage
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) {
if (DEBUG_PROVIDER || noisy) {
Slog.d(TAG, "Loading provider " + info.authority + ": "
+ info.name);
}
Context c = null;
ApplicationInfo ai = info.applicationInfo;
if (context.getPackageName().equals(ai.packageName)) {
c = context;
} else if (mInitialApplication != null &&
mInitialApplication.getPackageName().equals(ai.packageName)) {
c = mInitialApplication;
} else {
try {
c = context.createPackageContext(ai.packageName,
Context.CONTEXT_INCLUDE_CODE);
} catch (PackageManager.NameNotFoundException e) {
// Ignore
}
}
// ensure ctx nonNull
if (c == null) {
Slog.w(TAG, "Unable to get context for package " +
ai.packageName +
" while loading content provider " +
info.name);
return null;
}

if (info.splitName != null) {
try {
c = c.createContextForSplit(info.splitName);
} catch (NameNotFoundException e) {
throw new RuntimeException(e);
}
}

try {
final java.lang.ClassLoader cl = c.getClassLoader();
LoadedApk packageInfo = peekPackageInfo(ai.packageName, true);
if (packageInfo == null) {
// System startup case.
packageInfo = getSystemContext().mPackageInfo;
}
// 工厂方法实例化ContentProvider,实际上是反射
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);
// 回调ContentProvider的onCreate,同时赋值mContext
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);
}

......
}

1
2
3
4
5
public @NonNull ContentProvider instantiateProvider(@NonNull ClassLoader cl,
@NonNull String className)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
return (ContentProvider) cl.loadClass(className).newInstance();
}

到这里已经把install的骚操作前因后果都讲清楚了。其实就是利用了ContentProvider的生命周期来代替我们做手动install操作。当然这样会拖慢应用的启动速度,但是因为leakcanary只在debug模式下引入,所以对启动速度的影响不是问题。

如何监控内存泄漏?

来看InternalLeakSentry的install方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fun install(application: Application) {
CanaryLog.d("Installing LeakSentry")
checkMainThread()
if (this::application.isInitialized) {
return
}
InternalLeakSentry.application = application

val configProvider = { LeakSentry.config }
// 监控activity的内存泄漏
ActivityDestroyWatcher.install(
application, refWatcher, configProvider
)
// 监控fragment的内存泄漏, 2.0新增
FragmentDestroyWatcher.install(
application, refWatcher, configProvider
)
listener.onLeakSentryInstalled(application)
}

其中activity的监控主要是通过application的registerActivityLifecycleCallbacks注册了onDestroyed的监听

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
internal class ActivityDestroyWatcher private constructor(
private val refWatcher: RefWatcher,
private val configProvider: () -> Config
) {

private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityDestroyed(activity: Activity) {
if (configProvider().watchActivities) {
refWatcher.watch(activity)
}
}
}

companion object {
fun install(
application: Application,
refWatcher: RefWatcher,
configProvider: () -> Config
) {
val activityDestroyWatcher =
ActivityDestroyWatcher(refWatcher, configProvider)
application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
}
}
}

在onDestroyed的时候调用了refWatcher.watch(activity)

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72

/**
* References passed to [watch].
*/
private val watchedInstances = mutableMapOf<String, KeyedWeakReference>()

private val queue = ReferenceQueue<Any>()

.......

/**
* Identical to [.watch] with an empty string reference name.
*/
@Synchronized fun watch(watchedInstance: Any) {
watch(watchedInstance, "")
}

/**
* Watches the provided instances.
*
* @param name A logical identifier for the watched object.
*/
@Synchronized fun watch(
watchedInstance: Any,
name: String
) {
if (!isEnabled()) {
return
}
removeWeaklyReachableInstances()
val key = UUID.randomUUID()
.toString()
val watchUptimeMillis = clock.uptimeMillis()
val reference =
KeyedWeakReference(watchedInstance, key, name, watchUptimeMillis, queue)
if (name != "") {
CanaryLog.d(
"Watching instance of %s named %s with key %s", reference.className,
name, key
)
} else {
CanaryLog.d(
"Watching instance of %s with key %s", reference.className, key
)
}

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

@Synchronized private fun moveToRetained(key: String) {
removeWeaklyReachableInstances()
val retainedRef = watchedInstances[key]
if (retainedRef != null) {
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
onInstanceRetained()
}
}

private fun removeWeaklyReachableInstances() {
// 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) {
watchedInstances.remove(ref.key)
}
} while (ref != null)
}

可以看到首先是调了个removeWeaklyReachableInstances把queue里的引用都从watchedInstances中移除了。其中queue中的对象都是不应该判断为内存泄漏的对象。而watchedInstances是存放着监控的对象。checkRetainedExecutor会在5s后执行moveToRetained(key),moveToRetained(key)会将其引用的retainedUptimeMillis赋值为当前时间,标记为泄漏对象。以及调用onInstanceRetained(),最终调到InternalLeakCanary

1
2
3
4
5
override fun onReferenceRetained() {
if (this::heapDumpTrigger.isInitialized) {
heapDumpTrigger.onReferenceRetained()
}
}

一步步调到HeapDumpTriggercheckRetainedInstances

同理fragment的监控也是通过FragmentSupportManagerregisterFragmentLifecycleCallbacks方法监听fragment destroy进行的。

// TODO 本文只是分析了下监控的原理,但是其中如何解析hprof文件然后生成内存泄漏的调用链这部分并没有涉及。