preface

Core code is actually less than 100 lines, package teaching package will, learn not to compensate girlfriend, compensate boyfriend? Compensate compensate compensate. As a small business, please forgive me. Of course, this is just a simple version, and by the end of it, you already know 70% of LeakCanary. This is enough to deal with the interview, the interview is generally asked this core. If the interviewer asks for more, say there is nothing in the article that teaches…..

The flow chart

Let’s have a look at the flow chart first. Then we write the code as a flowchart.

Prepare a memory leak example carefully

Carefully prepare an edible memory leak chestnut that you can roast or boil, and here you can play, play, play. I’m just going to use a static list to refer to an activity, create a new activity, add a line onCreate, activityManage.get ().add(this). The following

class TestLeakActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test_leak)
        ActivityManage.get().add(this)
    }
Copy the code

Register the global Activity life listener

Check that the onActivityDestroyed is triggered by the onActivityDestroyed. Wait 5 seconds for the onActivityDestroyed to reach the target. (let gc fly for a while), the specific code is as follows.

application.registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks { override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {} override fun onActivityStarted(activity: Activity) {} override fun onActivityResumed(activity: Activity) {} override fun onActivityPaused(activity: Activity) {} override fun onActivityStopped(activity: Activity) {} override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {} override fun onActivityDestroyed(activity: Activity) { val key = UUID.randomUUID().toString() val weakReference = KeyedWeakReference(activity, key, + activity. LocalClassName, SystemClock. UptimeMillis (), queue) retainedKeys[key] = weakReference (); Let gc fly for a while, this one should use the thread pool, for simplicity, I won't write it. Handler().postDelayed( { watchActivity(application, weakReference) }, 5000 ) } })Copy the code

Determines whether the listener reference object is reachable

Here we need to know about WeakReference and ReferenceQueue. Here is a simple understanding, it is best to read the article to understand in detail.

WeakReference

A weakly referenced object will have its memory reclaimed once it is GC, regardless of whether the current memory space is sufficient.

ReferenceQueue

The reference queue to which the GC collector adds registered reference objects after detecting appropriate reachability changes. At this point, memory reclamation is complete.

The functionality

RemoveWeaklyReachableObjects (), it is the same fast with LeakCanary, directly moved to come over. If the reference object does not join the queue, it is leaking.

RunGC (), which basically calls GC to reclaim memory. However, whether to use gc to reclaim memory is determined by the VM. We are just suggesting it. (I don’t know if this is true. Hope big guy correct!

Private fun removeWeaklyReachableObjects () {/ / if the gc recycling the object, the reference object will be put in the queue. var ref: KeyedWeakReference? do { ref = queue.poll() as KeyedWeakReference? if (ref ! = null) { retainedKeys.remove(ref.key) } } while (ref ! = null) } private fun runGC() { Runtime.getRuntime().gc() enqueueReferences() System.runFinalization() }Copy the code

Dump a snapshot of the heap memory

DumpHprofData () for Android is called.

Debug.dumpHprofData(heapDumpFile.absolutePath)
Copy the code

Analyze the memory leak results

The previous version used the HAHA library for analysis. The new rewritten heap memory analysis, called shark, is faster. Here directly call LeakCanary in the shark API, you can go to the official website to understand, but the official website API is not the latest. At this point I always feel like my title needs to be changed to write my own……. using LeakCanary API

shark api

Val heapAnalyzer = heapAnalyzer (OnAnalysisProgressListener NO_OP)/analysis/heap memory snapshot val analysis = heapAnalyzer. Analyze ( heapDumpFile = heapDumpFile, leakingObjectFinder = KeyedWeakReferenceFinder, referenceMatchers = AndroidReferenceMatchers.appDefaults, computeRetainedHeapSize = true, objectInspectors = AndroidObjectInspectors.appDefaults, metadataExtractor = AndroidMetadataExtractor ) Log.d(TAG, "\u200B\n \ analysis result :\u200B\n$analysis");Copy the code

Analysis result Log

Noncomplete log, that’s just the first part. References underlined with “~~~” may be the cause of a memory leak, take a look.

==================================== HEAP ANALYSIS RESULT ==================================== 1 APPLICATION LEAKS References underlined with "~~~" are likely causes. Learn more at https://squ.re/leaks. 161537 bytes retained by leaking Objects Signature: daf62034cd9a555da3c8ed4d565e75d54ca163 ┬ ─ ─ ─ │ GC Root: The System class │ ├ ─ com. The memory. The monitor. The ActivityManage class │ Leaking: NO (a class is never leaking) │ ↓ Static ActivityManage. // See here │ ~~~~ ├─ java.util.ArrayList instance │ Leaking: UNKNOWN │ ├─ Java.lang.Object │ ~~~~~~~~~~~ ├─ Java.lang.Object[] Array │ Leaking: UNKNOWN │ Retaining 161577 bytes in 1383 objects │ left Object [] [0] / / see │ here ~ ~ ~ ╰ - > com. Memory. Monitor. TestLeakActivity instanceCopy the code

The core code

Core code here, demo see the link below.

class MonitorMemory { private val TAG = "MonitorMemory" private val queue = ReferenceQueue<Any>() private val RetainedKeys = mutableMapOf<String, KeyedWeakReference>() /** * * private fun watchActivity(Application: Application, weakReference: KeyedWeakReference) {/ / to determine whether a reference object has reached removeWeaklyReachableObjects ()/run/GC runGC removeWeaklyReachableObjects () () // Make two attempts to see if the object has been reclaimed. If it has been reclaimed, it will be removed from the retainedKeys. Otherwise, the object is leaked. If (retainedkeys.contains (Weakreference.key)) {// Activity leak log.d (TAG, "-- -- -- -- - the leak: activity leak -- $weakReference. Description: : : : : Activity${weakReference.get()}") val storageDirectory = File(application.cacheDir.toString() + "/watchActivity") if (! storageDirectory.exists()) { storageDirectory.mkdir() } val heapDumpFile = File(storageDirectory, UUID. RandomUUID ().tostring () + ".hprof") try {//dump a snapshot of the heap memory. Debug.dumpHprofData(heapDumpFile.absolutePath) val heapAnalyzer = HeapAnalyzer(OnAnalysisProgressListener.NO_OP) Val analysis = heapAnalyzer.analyze(heapDumpFile = heapDumpFile, leakingObjectFinder = KeyedWeakReferenceFinder, referenceMatchers = AndroidReferenceMatchers.appDefaults, computeRetainedHeapSize = true, objectInspectors = AndroidObjectInspectors.appDefaults, MetadataExtractor = AndroidMetadataExtractor) log.d (TAG, "\u200B\n analysis result :\u200B\n$analysis"); } catch (e: IOException) { e.printStackTrace() } } } private fun enqueueReferences() { try { Thread.sleep(100) } catch (e: InterruptedException) {}} private fun removeWeaklyReachableObjects () {/ / if the gc to recycle the object, the reference object will be put in the queue. var ref: KeyedWeakReference? do { ref = queue.poll() as KeyedWeakReference? if (ref ! = null) { retainedKeys.remove(ref.key) } } while (ref ! = null) } private fun runGC() { Runtime.getRuntime().gc() enqueueReferences() System.runFinalization() } fun initRegisterActivityLifecycleCallbacks(application: Application) { application.registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks { override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {} override fun onActivityStarted(activity: Activity) {} override fun onActivityResumed(activity: Activity) {} override fun onActivityPaused(activity: Activity) {} override fun onActivityStopped(activity: Activity) {} override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {} override fun onActivityDestroyed(activity: Activity) { Log.d(TAG, "Destroyed Activity:$activity.javaClass.name") val key = UUID.randomUUID().toString() val weakReference = KeyedWeakReference(activity, key, "description :") + Activity. LocalClassName, SystemClock. UptimeMillis (), Queue) retainedKeys[key] = weakReference // After 5 seconds to observe, let gc fly for a while, this should use thread pool, for the purpose of simple, do not write. Handler().postDelayed( { watchActivity(application, weakReference) }, 5000 ) } }) } }Copy the code

The demo code

The complete code is here

conclusion

Look at thisLike you owe me a thumbs up.

To summarize the process: basically listen for an activity’s Destroy, wait five seconds to observe it, dump the memory snapshot of the heap if it finds a leak, and then analyze the heap memory to find the GC reference path. For more details, see the source analysis of some of the nuggets’ bigwigs or see the source code.

I have a slight cold. I hope it will get better soon. Then, thank you. Your praise is like the warm sun in winter, warming my heart.