写点什么

LeakCanary 源码分析以及 ContentProvider 的优化方案,android 移动开发

用户头像
Android架构
关注
发布于: 刚刚
  • data.instrumentationName + ": " + e.toString(), e);}try {//app 的 onCreate 方法调用 mInstrumentation.callApplicationOnCreate(app);} catch (Exception e) {


具体调用 contentprovider 的 onCreate 代码逻辑如下


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


for (ProviderInfo cpi : providers) {···//installProvider 方法 ContentProviderHolder cph = installProvider(context, null, cpi,false /noisy/, true /noReleaseNeeded/, true /stable/);if (cph != null) {cph.noReleaseNeeded = true;results.add(cph);}}//installProvider 方法,然后一步步跟进//1//XXX Need to create the correct context for this provider.localProvider.attachInfo(c, info);//2public void attachInfo(Context context, ProviderInfo info) {attachInfo(context, info, false);}//3private void attachInfo(Context context, ProviderInfo info, boolean testing) {mNoPerms = testing;mCallingPackage = new ThreadLocal<>();if (mContext == null) {···ContentProvider.this.onCreate();}}


通过上面的分析,可以知道在我们引入依赖后,依赖包中的 AndroidMainfest.xml 文件便会主动合并到主 AndroidManifest.xml 文件中,然后在程序启动过程中便会自动创建 ContentProvider,然后进行 InternalLeakSentry.install(application),接下来进行一些列的监控和 dump 操作等。


#####2.1 InternalLeakSentry.install(application)


下面来分析 InternalLeakSentry.install(application)里面都做了一些什么,可以看到


fun install(application: Application) {CanaryLog.d("Installing LeakSentry")checkMainThread()if (this::application.isInitialized) {return}InternalLeakSentry.application = application


val configProvider = { LeakSentry.config }// 1.监听 Activity.onDestroy()ActivityDestroyWatcher.install(application, refWatcher, configProvider)// 2.监听 Fragment.onDestroy()FragmentDestroyWatcher.install(application, refWatcher, configProvider)// 3.监听完成后进行一些初始化工作 listener.onLeakSentryInstalled(application)}


从命名上可以看到在 Activity 和 Fragment 进行 destory 的时候进行 watch


  1. ActivityDestroyWatcher


internal class ActivityDestroyWatcher private constructor(private val refWatcher: RefWatcher,private val configProvider: () -> Config) {


private val lifecycleCallbacks = object : ActivityLifecycleCallbacksAdapter() {override fun onActivityDestroyed(activity: Activity) {if (configProvider().watchActivities) {// 监听到 onDestroy() 之后,通过 refWatcher 监测 ActivityrefWatcher.watch(activity)}}}


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


  1. FragmentDestroyWatcher


internal interface FragmentDestroyWatcher {


fun watchFragments(activity: Activity)


companion object {


private const val SUPPORT_FRAGMENT_CLASS_NAME = "androidx.fragment.app.Fragment"


fun install(application: Application,refWatcher: RefWatcher,configProvider: () -> LeakSentry.Config) {val fragmentDestroyWatchers = mutableListOf<FragmentDestroyWatcher>()


//大于等于 android O


if (SDK_INT >= O) {fragmentDestroyWatchers.add(AndroidOFragmentDestroyWatcher(refWatcher, configProvider))}


if (classAvailable(SUPPORT_FRAGMENT_CLASS_NAME)) {// androidx 使用 SupportFragmentDestroyWatcherfragmentDestroyWatchers.add(SupportFragmentDestroyWatcher(refWatcher, configProvider))}


if (fragmentDestroyWatchers.size == 0) {return}


application.registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacksAdapter() {override fun onActivityCreated(activity: Activity,savedInstanceState: Bundle?) {for (watcher in fragmentDestroyWatchers) {watcher.watchFragments(activity)}}})}


private fun classAvailable(className: String): Boolean {return try {Class.forName(className)true} catch (e: ClassNotFoundException) {false}}}}


Android O 及以后,androidx 都具备对 Fragment 生命周期的监听功能。为什么不监听 Android O 之前的呢???(待解决)在版本为 1.5.4 之前是不支持 Fragment 内存泄漏监听的,后面版本才加了进来。


  1. listener.onLeakSentryInstalled(application)


该 listener 的最终实现类是 leakcanary-android-core 中的 InternalLeakCanary 类


override fun onLeakSentryInstalled(application: Application) {this.application = application


val heapDumper = AndroidHeapDumper(application, leakDirectoryProvider)//用于发现可能的内存泄漏之后手动调用 GC 确认是否真的为内存泄露 val gcTrigger = GcTrigger.Default


val configProvider = { LeakCanary.config }


val handlerThread = HandlerThread(HeapDumpTrigger.LEAK_CANARY_THREAD_NAME)handlerThread.start()val backgroundHandler = Handler(handlerThread.looper)//用于确认内存泄漏之后进行 heap dump 工作。heapDumpTrigger = HeapDumpTrigger(application, backgroundHandler, LeakSentry.refWatcher, gcTrigger, heapDumper, configProvider)application.registerVisibilityListener { applicationVisible ->this.applicationVisible = applicationVisibleheapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)}addDynamicShortcut(application)}


这里有个关于 GC 回收的知识点,我们可以看看优秀的第三方框架都是怎么写的


interface GcTrigger {fun runGc()object Default : GcTrigger {override fun runGc() {// Code taken from AOSP FinalizationTest:// https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/// java/lang/ref/FinalizationTester.java// System.gc() does not garbage collect every time. Runtime.gc() is// more likely to perform a gc.Runtime.getRuntime().gc()enqueueReferences()System.runFinalization()}private fun enqueueReferences() {// Hack. We don't have a programmatic way to wait for the reference queue daemon to move// references to the appropriate queues.try {Thread.sleep(100)} catch (e: InterruptedException) {throw AssertionError()}}}}


可以看到,它使用了 Runtime.getRuntime().gc()而不是 System.gc(),进入 System.gc 源码一看


public static void gc() {boolean shouldRunGC;synchronized (LOCK) {shouldRunGC = justRanFinalization;if (shouldRunGC) {justRanFinalization = false;} else {runGC = true;}}if (shouldRunGC) {Runtime.getRuntime().gc();}}


可以看到 System.gc 源码的还是最终实现是 Runtime.getRuntime().gc();但是需要一系列的判断条件,我们手动调用 System.runFinalization()可以使 gc 方法中的 justRanFinalizationw 为 true,从而保证 Runtime.getRuntime().gc()会被执行。


####3.如何判断对象可能泄露:ReferenceQueue 含义及作用


在 Activity/Fragment 销毁后,会进行一系列的对象回收,我们把这些对象分别和引用队列进行关联,当某个对象被回收时,**(弱引用一旦变成弱可达(可达性算法分析),引用就会加到引用队列中,然后再进行回收)**我们对象的引用就会被加入到引用队列中。根据该原理进行一系列的操作,最终判断是否内存泄漏。


#####3.1 引用队列


通常我们将其 ReferenceQueue 翻译为引用队列,换言之就是存放引用的队列,保存的是 Reference 对象。其作用在于 Reference 对象所引用的对象被 GC 回收时,该 Reference 对象将会被加入引用队列中(ReferenceQueue)的队列末尾。


ReferenceQueue 常用的方法:


public Reference poll():从队列中取出一个元素,队列为空则返回 null;


public Reference remove():从队列中出对一个元素,若没有则阻塞至有可出队元素;


public Reference remove(long timeout):从队列中出对一个元素,若没有则阻塞至有可出对元素或阻塞至超过 timeout 毫秒;


  1. 强引用

  2. 软引用

  3. 弱引用

  4. 虚引用(Phantom Reference)


虚引等同于没有引用,这意味着在任何时候都可能被 GC 回收,设置虚引用的目的是为了被虚引用关联的对象在被垃圾回收器回收时,能够收到一个系统通知。(被用来跟踪对象被 GC 回收的活动)虚引用和弱引用的区别在于:虚引用在使用时必须和引用队列(ReferenceQueue)联合使用,其在 GC 回收期间的活动如下:


ReferenceQueue queue=new ReferenceQueue();


PhantomReference pr=new PhantomReference(object,queue);


也即是 GC 在回收一个对象时,如果发现该对象具有虚引用,那么在回收之前会首先该对象的虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入虚引用来了解被引用的对象是否被 GC 回收。


#####3.2 GC Root 对象


![image-20200723182524190](/Users/jackie/Library/Application Support/typora-user-images/image-20200723182524190.png)


#####3.3 内存是否泄漏


知道引用队列的原理后,先大概描述一下如何判断是否泄漏,首先创建三个队列


/**


  • References passed to [watch] that haven't made it to [retainedReferences] yet.

  • watch() 方法传进来的引用,尚未判定为泄露/private val watchedReferences = mutableMapOf<String, KeyedWeakReference>()/*

  • References passed to [watch] that we have determined to be retained longer than they should

  • have been.

  • watch() 方法传进来的引用,已经被判定为泄露*/private val retainedReferences = mutableMapOf<String, KeyedWeakReference>()private val queue = ReferenceQueue<Any>() // 引用队列,配合弱引用使用


//KeyedWeakReference,对象和引用队列进行弱引用关联,所以这个对象一定会被回收


class KeyedWeakReference(referent: Any,val key: String,val name: String,val watchUptimeMillis: Long,referenceQueue: ReferenceQueue<Any>) : WeakReference<Any>(referent, referenceQueue) {@Volatilevar retainedUptimeMillis = -1L


companion object {@Volatile@JvmStatic var heapDumpUptimeMillis = 0L}


}


如果一个 obj 对象,它和队列 queue 进行弱引用关联,在进行垃圾收集时,发现该对象具有弱引用,会把引用加入到引用队列中,我们如果在该队列中拿到引用,则说明该对象被回收了,如果拿不到,则说明该对象还有强/软引用未释放,那么就说明对象还未回收,发生内存泄漏了,然后 dump 内存快照,使用第三方库进行引用链分析


这里重点强调一点一个对象可能被多个引用持有,比如强引用,软引用,弱引用,只要这个对象还有强引用/软引用,与这个对象关联的任意引用队列就拿不到引用,引用队列就相当于一个通知,多个引用队列和一个对象关联,对象被回收时,多个队列都会受到通知


#####3.4 watch()


@Synchronized fun watch(watchedReference: Any,referenceName: String) {if (!isEnabled()) {return}//移除队列中将要被 GC 的引用 removeWeaklyReachableReferences()val key = UUID.randomUUID().toString()val watchUptimeMillis = clock.uptimeMillis()val reference = // 构建当前引用的弱引用对象,并关联引用队列 queueKeyedWeakReference(watchedReference, key, referenceName, watchUptimeMillis, queue)if (referenceName != "") {CanaryLog.d("Watching instance of %s named %s with key %s", reference.className,referenceName, key)} else {CanaryLog.d("Watching instance of %s with key %s", reference.className, key)}


watchedReferences[key] = referencecheckRetainedExecutor.execute {//如果引用未被移除,则可能存在内存泄漏 moveToRetained(key)}}


removeWeaklyReachableReferences()


private fun removeWeaklyReachableReferences() {// 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.// 弱引用一旦变得弱可达,就会立即入队。这将在 finalization 或者 GC 之前发生。var ref: KeyedWeakReference?do {ref = queue.poll() as KeyedWeakReference? // 队列 queue 中的对象都是会被 GC 的 if (ref != null) {val removedRef = watchedReferences.remov


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


e(ref.key)if (removedRef == null) {retainedReferences.remove(ref.key)}// 移除 watchedReferences 队列中的会被 GC 的 ref 对象,剩下的就是可能泄露的对象}} while (ref != null)}


moveToRetained()


@Synchronized private fun moveToRetained(key: String) {removeWeaklyReachableReferences() // 再次调用,防止遗漏 val retainedRef = watchedReferences.remove(key)if (retainedRef != null) {retainedReferences[key] = retainedRefonReferenceRetained()}}


最后会回调到 InternalLeakCanary 的 onReferenceRetained()方法


override fun onReferenceRetained() {if (this::heapDumpTrigger.isInitialized) {heapDumpTrigger.onReferenceRetained()}}


//1.HeapDumpTrigger 的 onReferenceRetained()fun onReferenceRetained() {scheduleRetainedInstanceCheck("found new instance retained")}


//2.scheduleRetainedInstanceCheckprivate fun scheduleRetainedInstanceCheck(reason: String) {backgroundHandler.post {checkRetainedInstances(reason)}}


//3.checkRetainedInstancesprivate fun checkRetainedInstances(reason: String) {CanaryLog.d("Checking retained instances because %s", reason)val config = configProvider()// A tick will be rescheduled when this is turned back on.if (!config.dumpHeap) {return}

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
LeakCanary源码分析以及ContentProvider的优化方案,android移动开发