之前简单介绍了一下使用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) var ref = referenceQueue.poll() println("gcBefore:${ref} " ) obj = null System.gc() Thread.sleep(2000 ) ref = referenceQueue.poll() 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 } 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 ?) { 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 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都进行了生命周期监听,并且对泄漏对象的通报和分析都是在内部进行的。
我们现在来看看它的源码,在看他的源码之前我们先问几个问题
在LeakCanary1.0版本之前是需要在Application里面初始化的,2.0版本之后直接添加依赖就可以用了,那之后的版本它在哪初始化的捏?
LeakCanary是怎么对Activity、fragment(view和fragment本身)、Service、RootView、Viewmodel进行生命周期的监听的捏?
检测到泄漏之后是怎么处理的呢?
相信带着这几个问题去看他的源码会更好理解。
首先看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()); final IActivityManager mgr = ActivityManager.getService(); try { 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); Binder.restoreCallingIdentity(origId); } } private boolean attachApplicationLocked (@NonNull IApplicationThread thread, int pid, int callingUid, long startSeq) { ... if (app.isolatedEntryPoint != null ) { thread.runIsolatedEntryPoint(app.isolatedEntryPoint, app.isolatedEntryPointArgs); } else if (instr2 != null ) { thread.bindApplication(processName, appInfo, providerList, 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); }
我们发现这里调用了mH的sendMessage方法,mH会不会就是Handler?跟进mH看一下
是H的对象,那H是否继承了Handler方法呢?点进去
class H extends 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); 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) { ... final ContextImpl appContext = ContextImpl.createAppContext(this , data.info); ... try { app = data.info.makeApplication(data.restrictedBackupMode, null ); ... if (!data.restrictedBackupMode) { if (!ArrayUtils.isEmpty(data.providers)) { installContentProviders(app, data.providers); } } ... try { 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, false , true , true ); 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 ) { 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); 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 <>(); 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(); } }
最终我们在这发现回调了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会合并所有的清单文件,所有文件优先级如下
Product flavors 和构建类型所指定的清单文件。
应用程序的主清单文件。
类库的清单文件。
具体的合并逻辑就涉及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 ) , 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() } LeakCanaryDelegate.loadLeakCanary(application) watchersToInstall.forEach { it.install() } 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), FragmentAndViewModelWatcher(application, reachabilityWatcher), RootViewWatcher(reachabilityWatcher), ServiceWatcher(reachabilityWatcher) ) }
创建各个检测器,传入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) } override fun uninstall () { application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks) } }
可以看到,通过Activity生命周期对的Application.ActivityLifecycleCallbacks回调来达到监听Activity的结束。其实就是在Activity快onDestroy的时候调用了Application.ActivityLifecycleCallbacks的onActivityDestroyed。然后在onActivityDestroyed里面调用了reachabilityWatcher的expectWeaklyReachable,reachabilityWatcher就是刚刚创建的时候传进来的,我们返回去看看reachabilityWatcher对expectWeaklyReachable的实现。跟踪发现,传进来的是objectWatcher,点击跟踪
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 () { 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, 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 ? ) { 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 ? ) { ViewModelClearedWatcher.install(fragment, reachabilityWatcher) } override fun onFragmentViewDestroyed ( fm: FragmentManager , fragment: Fragment ) { val view = fragment.view if (view != null ) { reachabilityWatcher.expectWeaklyReachable( view, "${fragment::class.java.name} received Fragment#onDestroyView() callback " + "(references to its views should be cleared to prevent leaks)" ) } } 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 supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true ) ViewModelClearedWatcher.install(activity, reachabilityWatcher) } } }
其实跟activity的差不多,不过是通过activity设置fragment的监听,这里可能有个高阶函数大家没见过,也是invoke的一种用法吧,可以了解一下。好,我们现在来看看对viewmodel的监听过程:
ViewModelClearedWatcher internal class ViewModelClearedWatcher ( storeOwner: ViewModelStoreOwner, private val reachabilityWatcher: ReachabilityWatcher ) : ViewModel() { private val viewModelMap: Map<String, ViewModel>? = try { 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 () { viewModelMap?.values?.forEach { viewModel -> reachabilityWatcher.expectWeaklyReachable( viewModel, "${viewModel::class.java.name} received ViewModel#onCleared() callback" ) } } companion object { fun install ( storeOwner: ViewModelStoreOwner , reachabilityWatcher: ReachabilityWatcher ) { 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 { swapActivityThreadHandlerCallback { mCallback -> uninstallActivityThreadHandlerCallback = { swapActivityThreadHandlerCallback { mCallback } } Handler.Callback { msg -> if (msg.obj !is IBinder) { return @Callback false } if (msg.what == STOP_SERVICE) { val key = msg.obj as IBinder activityThreadServices[key]?.let { onServicePreDestroy(key, it) } } mCallback?.handleMessage(msg) ?: false } } swapActivityManager { activityManagerInterface, activityManagerInstance -> uninstallActivityManager = { swapActivityManager { _, _ -> activityManagerInstance } } Proxy.newProxyInstance( activityManagerInterface.classLoader, arrayOf(activityManagerInterface) ) { _, method, args -> 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 ) { servicesToBeDestroyed.remove(token)?.also { serviceWeakReference -> serviceWeakReference.get ()?.let { service -> 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 ) , 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() } LeakCanaryDelegate.loadLeakCanary(application) watchersToInstall.forEach { it.install() } installCause = RuntimeException("manualInstall() first called here" ) }
看看LeakCanaryDelegate.loadLeakCanary(application)
internal object LeakCanaryDelegate { @Suppress("UNCHECKED_CAST" ) val loadLeakCanary by lazy { try { 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 checkRunningInDebuggableBuild() AppWatcher.objectWatcher.addOnObjectRetainedListener(this ) val gcTrigger = GcTrigger.Default val configProvider = { LeakCanary.config } val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME) handlerThread.start() val backgroundHandler = Handler(handlerThread.looper) heapDumpTrigger = HeapDumpTrigger( application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, configProvider ) application.registerVisibilityListener { applicationVisible -> this .applicationVisible = applicationVisible heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible) } registerResumedActivityListener(application) addDynamicShortcut(application) mainHandler.post { 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 = 0 L ) { val checkCurrentlyScheduledAt = checkScheduledAt if (checkCurrentlyScheduledAt > 0 ) { return } checkScheduledAt = SystemClock.uptimeMillis() + delayMillis backgroundHandler.postDelayed({ checkScheduledAt = 0 checkRetainedObjects() }, delayMillis) }
通过记录时间避免重复检测,然后向子线程post了一Runnable,瞅瞅checkRetainedObjects
private fun checkRetainedObjects () { val iCanHasHeap = HeapDumpControl.iCanHasHeap() val config = configProvider() if (iCanHasHeap is Nope) { if (iCanHasHeap is NotifyingNope) { var retainedReferenceCount = objectWatcher.retainedObjectCount if (retainedReferenceCount > 0 ) { gcTrigger.runGc() retainedReferenceCount = objectWatcher.retainedObjectCount } val nopeReason = iCanHasHeap.reason() 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" 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 { 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 = 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) { 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源码算简单分析了一下,不算细致,可以借鉴一下。下面是一些常见的内存泄漏
单例模式引发的内存泄漏
原因:单例模式里的静态实例持有对象的引用,导致对象无法被回收,常见为持有Activity的引用
优化:改为持有Application的引用,或者不持有使用的时候传递。
集合操作不当引发的内存泄漏
原因:集合只增不减
优化:有对应的删除或卸载操作
线程的操作不当引发的内存泄漏
原因:线程持有对象的引用在后台执行,与对象的生命周期不一致
优化:静态实例+弱引用(WeakReference)方式,使其生命周期一致
匿名内部类/非静态内部类操作不当引发的内存泄漏
原因:内部类持有对象引用,导致无法释放,比如各种回调
优化:保持生命周期一致,改为静态实例+对象的弱引用方式(WeakReference)
常用的资源未关闭回收引发的内存泄漏
原因:BroadcastReceiver,File,Cursor,IO流,Bitmap等资源使用未关闭
优化:使用后有对应的关闭和卸载机制
Handler使用不当造成的内存泄漏
原因:Handler持有Activity的引用,其发送的Message中持有Handler的引用,当队列处理Message的时间过长会导致Handler无法被回收
优化:静态实例+弱引用(WeakReference)方式
好了,LeakCanary源码算简单分析了一下,不算细致,可以借鉴一下。