1. LeakCanary Detects the leak principle
1.1 Java Reference Classification
- Strong reference: a reference type that is commonly used by the JVM.
- SoftReference: the reference will be reclaimed before OOM. You can use a SoftReference to cache
LruCache
“Instead of SoftReference - WeakReference: garbage collection occurs when GC occurs.
- PhantomReference: The get method returns null and cannot fetch the value.
1.2 WeakReference and ReferenceQueue
class P
fun main(a) {
val referenceQueue = ReferenceQueue<P>()
val weak = WeakReference(P(), referenceQueue)
println(weak)
System.gc()
Thread.sleep(2000)
println(weak.get())
println(referenceQueue.poll())
}
/ / output:
java.lang.ref.WeakReference@2f0e140b
null
java.lang.ref.WeakReference@2f0e140b
Copy the code
When the object referenced by WeakReference has no other object reference, the object will be recycled during GC. If the ReferenceQueue is passed in when WeakReference is created, WeakReference will be added to the ReferenceQueue queue after object recycling.
1.3 Detection principle of LeakCanary
After the Activity executes onDestroy(), place the Activity object into a WeakReference object with ReferenceQueue. After a period of time, it will detect whether the ReferenceQueue queue contains the WeakReference object just created. If not, it indicates that the activity object has not been recovered, and then it will actively trigger a GC to detect again. If it has not been recovered, a leak has occurred. Start using debug.dumphprofData () to generate the hprof file, analyze the hprof file, and get the reference chain.
2. LeakCanary source code analysis
Source Version 2.7
Com. Squareup. Leakcanary: leakcanary - android: 2.7Copy the code
2.1 the initialization
Use the ContentProvider, which is automatically initialized.
internal sealed class AppWatcherInstaller : ContentProvider() {
override fun onCreate(a): Boolean {
valapplication = context!! .applicationContextas Application
AppWatcher.manualInstall(application)
return true}}Copy the code
fun manualInstall(
application: Application,
retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
) {
checkMainThread()
// Check after 5s by default
this.retainedDelayMillis = retainedDelayMillis
// Initialize some configuration, InternalLeakCanary class invoke method, configure leak Listener, GC trigger, Dump class.
LeakCanaryDelegate.loadLeakCanary(application)
// Monitor categories, Activity, Fragment, View, Service
watchersToInstall.forEach {
it.install()
}
}
Copy the code
Monitoring category
fun appDefaultWatchers(
application: Application,
reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
return listOf(
ActivityWatcher(application, reachabilityWatcher),
FragmentAndViewModelWatcher(application, reachabilityWatcher),
RootViewWatcher(reachabilityWatcher),
ServiceWatcher(reachabilityWatcher)
)
}
Copy the code
Use ActivityLifecycleCallbacks to listen callback onActivityDestroyed method.
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(a) {
application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
}
override fun uninstall(a) {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
}
}
Copy the code
ObjectWatcher is created to perform detection of objects.
val objectWatcher = ObjectWatcher(
clock = { SystemClock.uptimeMillis() },
checkRetainedExecutor = {
check(isInstalled) {
"AppWatcher not installed"
}
// Run the runnable post 5s later
mainHandler.postDelayed(it, retainedDelayMillis)
},
isEnabled = { true})Copy the code
2.2 Adding WeakReference to detect whether the object is reclaimed
ExpectWeaklyReachable method will be called after onActivityDestroyed, and the KeyedWeakReference object will be created and put into watchedObjects map. After 5s delay, remove the corresponding KeyedWeakReference in map according to the object in the ReferenceQueue. If it is removed (ReferenceQueue has a corresponding KeyedWeakReference), it indicates that the activity is recovered. If it fails to be removed, The map also contains the KeyedWeakReference, which indicates that a leak may have occurred.
ObjectWatcher.class
private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()
private val queue = ReferenceQueue<Any>()
@Synchronized override fun expectWeaklyReachable(
watchedObject: Any,
description: String
) {
if(! isEnabled()) {return
}
removeWeaklyReachableObjects()
val key = UUID.randomUUID()
.toString()
val watchUptimeMillis = clock.uptimeMillis()
// Create KeyedWeakReference, KeyedWeakReference inherits from WeakReference.
val reference =
KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
// Add the map based on the key
watchedObjects[key] = reference
// There will be a 5s delay
checkRetainedExecutor.execute {
moveToRetained(key)
}
}
Copy the code
@Synchronized private fun moveToRetained(key: String) {
// Remove objects from the map according to queue
removeWeaklyReachableObjects()
val retainedRef = watchedObjects[key]
// If not removed, a leak may have occurred. Notifies a registered Listener
if(retainedRef ! =null) { retainedRef.retainedUptimeMillis = clock.uptimeMillis() onObjectRetainedListeners.forEach { it.onObjectRetained() } }}private fun removeWeaklyReachableObjects(a) {
// 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 {
// If the activity is reclaimed, the KeyedWeakReference containing the activity will be in the queue and objects in the map will be removed according to the queue
ref = queue.poll() as KeyedWeakReference?
if(ref ! =null) {
watchedObjects.remove(ref.key)
}
} while(ref ! =null)}}Copy the code
2.3 Initiate GC recheck
ObjectWatcher callback to InternalLeakCanary scheduleRetainedObjectCheck method, finally not recycling number object call checkRetainedObjects method detection, trigger GC, initiate the Dump.
InternalLeakCanary.class
override fun onObjectRetained(a) = scheduleRetainedObjectCheck()
fun scheduleRetainedObjectCheck(a) {
if (this::heapDumpTrigger.isInitialized) {
heapDumpTrigger.scheduleRetainedObjectCheck()
}
}
Copy the code
HeapDumpTrigger.class
private fun checkRetainedObjects(a){...// The number of remaining objects
var retainedReferenceCount = objectWatcher.retainedObjectCount
// Trigger GC and get the number of remaining objects
if (retainedReferenceCount > 0) {
gcTrigger.runGc()
retainedReferenceCount = objectWatcher.retainedObjectCount
}
// Do not dump if the number is less than 5
if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return. dumpHeap( retainedReferenceCount = retainedReferenceCount, retry =true,
reason = "$retainedReferenceCount retained objects, app is $visibility")}Copy the code
3 Analyze and handle memory leaks
3.1 Analysis Tools
- LeakCanary
- Memory Analyzer Tool: The Hprof files generated by Android need to be converted by the SDK hprof-conv Tool
- Android Studio Profiler: You can import hprof files for analysis
3.2 Processing according to the reference chain
- After using LeakCanary to detect a memory leak, resolve the leak according to the reference chain if it is clear.
- If LeakCanary cannot provide the reference chain, you can export the hprof file to Android Studio and use Profiler analysis to locate the problematic page according to the operation, and then find out the related objects of the page according to the Profiler. For example, an Activity has 10 objects in memory. There could have been a leak. After guessing potentially problematic code from the page, use dichotomy, comment that part of the code, and test again.
- Some problems may require multiple uses, so Espresso can be used to automate testing of some interfaces.
4 Memory leakage may occur
- Singletons use the Activity Context
- Incorrect use of static toasts. Custom toasts use the Activity’s layoutInflater to parse layout files
- Incorrect use of Handler
- The animation does not call cancel