Leakcanary Principle Analysis (based on Leakcanary 2.7)
What is a memory leak
A memory leak refers to an application that allocates memory space to the system and does not release it after it is used. As a result, the memory unit is occupied and the application cannot use the memory unit any more. In Android, it generally refers to the fact that the object has not been reclaimed after it has exceeded its life cycle. The types of leaks include:
- The Java heap leaks
- Native wild pointer
- The FD handle leaks
Leaks can easily cause application process memory to surge and eventually cause OOM or Too Many Open Files related crashes. These crash points are usually the straw that breaks the camel’s back, rather than the root cause of the crash. It is necessary to dump the memory or open the handle to repair the problem intuitively
A solution to detect memory leaks
Liko 1 byte
When OOM and memory reach the top, HPROF file can be obtained through user insensitive dump. When App exits the background and memory is sufficient, HPROF can be cut and sent back for analysis. Online MAT analyzes HPROF file and generates links and reports. You can report large objects or create too many small objects frequently, which causes memory tightness
2. Well quickly KOOM
The system kernel copy-on-write (COW) mechanism is used to suspend the VM each time before dumping the memory image, and then fork the child process to perform the dump operation. The parent process immediately resumes the VM after the fork succeeds. The whole process takes only a few milliseconds for the parent process. The memory image is analyzed locally by an independent process in a single thread at idle time and deleted immediately after analysis. Github.com/KwaiAppTeam…
3. Leakcanary customization
Leverage the customization to LeakCanary and report the leak trace to the business Server
Leakcanary overview
LeakCanary: It automatically watches destroyed activities and fragments, triggers a heap dump, runs Shark Android and then displays the result.
LeakCanary, pictured as a bird, is actually a literal translation of Canary. In the early days, canaries were often used in mines to detect mine gases because of their sensitivity to harmful gases
LeakCanary comes in five parts
- Application layer facing leakage listening, memory dump
- Memory analysis Service
- Shark, a heap analysis library based on the Kotlin implementation, is similar to the MAT tool
- Leak trace UI display
- Leak database
Leakcanary introduced
The new version of Leakcanary is introduced as simple as a gradle dependency
debugImplementation 'com. Squareup. Leakcanary: leakcanary - android: 2.7'
Copy the code
or
debugImplementation 'com. Squareup. Leakcanary: leakcanary - android - process: 2.7'
Copy the code
Similarities:
- Both use AppWatcherInstaller$MainProcess(because you want to dump the MainProcess’s memory) and use ContentProvider to start the process
Automatic initialization when moving
- Object detection and memory dump are in the main process
- You can customize the initialization timing by configuring the LEAK_CANary_watcher_AUTO_install attribute of XML
The difference between the two:
- Leakcanary – Android Memory analysis service HeapAnalyzerService is running on the main process child thread;
- Leakcanary – Android-Process The memory analysis service HeapAnalyzerService runs in an independent leakCanary process
Leakcanary Startup time
Earlier versions need to handle the initialization of Leakcanary in the onCreate Application. In the new version, to reduce the access cost, the initialization of Leakcanary is stored in the AppWatcherInstaller defined by the library. The principle is that using the ContentProvider onCreate initializes before the Application onCreate (PS: later than the Application attachBaseContext)
AcitivtyThread.java
private void handleBindApplication(AppBindData data) {... Omit codetry {
// If the app is being launched for full backup or restore, bring it up in
// a restricted environment with the base application class.
app = data.info.makeApplication(data.restrictedBackupMode, null); // Application#attachBaseContext(). Omit code// don't bring up providers in restricted mode; they may depend on the
// app's custom Application class
if(! data.restrictedBackupMode) {if(! ArrayUtils.isEmpty(data.providers)) { installContentProviders(app, data.providers);/ / create a ContentProvider}}... Omit codetry {
mInstrumentation.callApplicationOnCreate(app);// Application#onCreate()}... Omit code}Copy the code
Leakcanary initialization
AppWatcherInstaller. OnCreate call Leakcanary initialization
/** * Start to initialize Leakcanary */ when contentProvider is created
override fun onCreate(a): Boolean { val application = context!! .applicationContext as Application AppWatcher.manualInstall(application)/ / initialization
return true
}
Copy the code
AppWatcher. ManualInstall sets a default object detection delay and default object type
@JvmOverloads
fun manualInstall(
application: Application,
retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5).// After adding watchedObjects, enable detection for 5s
watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application) // Check the object
) {
/ /... Omit code
// LeakCanary core component, which implements leak detection and triggers heap dump. The default implementation class is InternalLeakCanary. Kt
LeakCanaryDelegate.loadLeakCanary(application)
// Register the object detected by default
watchersToInstall.forEach {
it.install()
}
}
Copy the code
The default detected objects are
- Activity
- Fragment (View and Fragment itself)
- ViewMoel
- RootView(Window)
- Service
fun appDefaultWatchers( application: Application, reachabilityWatcher: ReachabilityWatcher = objectWatcher ): List<InstallableWatcher> {
return listOf(
ActivityWatcher(application, reachabilityWatcher), // Activity
FragmentAndViewModelWatcher(application, reachabilityWatcher), / / fragments and the viewModel
RootViewWatcher(reachabilityWatcher), // window
ServiceWatcher(reachabilityWatcher) // service)}Copy the code
Each type is added to watchedObjects (a map that holds weak references to objects that need to be checked, defined in objectwatcher.kt) and waits for detection, mainly through component declarations that periodically listen for callbacks and hook points
Actiity detection timing
ActivityLifecycleCallbacks callback through registration, the activity to join in onActivityDestroyed watchedObjects for test
ActivityWatcher.kt
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate(a) {
override fun onActivityDestroyed(activity: Activity) {
reachabilityWatcher.expectWeaklyReachable(
activity, "${activity::class.java.name} received Activity#onDestroy() callback")}}Copy the code
Fragment(AndroidX) Detection timing
By registering FragmentLifecycleCallbacks callback, OnFragmentViewDestroyed and onFragmentDestroyed add View and Fragment to watchedObjects for detection
AndroidXFragmentDestroyWatcher.kt
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")}}Copy the code
ViewModel detects timing
Leakcanary adds a ViewModel to the current Fragment when Fragment onCreate, and this ViewModel follows the life cycle of the host, When onClear is executed, hook all viewModels of the current host and iterate to add those viewModels to watchedObjects
AndroidXFragmentDestroyWatcher.kt
override fun onFragmentCreated( fm: FragmentManager, fragment: Fragment, savedInstanceState: Bundle? ) {
// Add a ViewModel to the current Fragment when the Fragment executes onCreate
ViewModelClearedWatcher.install(fragment, reachabilityWatcher)
}
ViewModelClearedWatcher.kt
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)// Add to storeOwner
}
// Hook all viewModels of the current Fragment
viewModelMap = 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
}
// When the ViewModel is onCleared, all viewModels are added to the detection queue
override fun onCleared(a) { viewModelMap? .values? .forEach { viewModel -> reachabilityWatcher.expectWeaklyReachable( viewModel,"${viewModel::class.java.name} received ViewModel#onCleared() callback")}}Copy the code
Service Detection Opportunity
A Service is similar to an Activity. It also adds a Service object to watchedObjects when onDestroy occurs, but since the Service does not expose a callback to the declaration cycle, So it is also through the hook to get the Service declaration cycle
ServiceWatcher.kt
override fun install(a) {
checkMainThread()
check(uninstallActivityThreadHandlerCallback == null) {
"ServiceWatcher already installed"
}
check(uninstallActivityManager == null) {
"ServiceWatcher already installed"
}
try {
// Hook ActivityThread mCallback from mH
swapActivityThreadHandlerCallback { mCallback ->
uninstallActivityThreadHandlerCallback = {
swapActivityThreadHandlerCallback {
mCallback
}
}
// Proxy object
Handler.Callback { msg ->
// https://github.com/square/leakcanary/issues/2114
// On some Motorola devices (Moto E5 and G6), the msg.obj returns an ActivityClientRecord
// instead of an IBinder. This crashes on a ClassCastException. Adding a type check
// here to prevent the crash.
if(msg.obj ! is IBinder) {return@Callback false
}
// Intercepts the STOP_SERVICE message, which preprocesses the service object to be destroyed
if(msg.what == STOP_SERVICE) { val key = msg.obj as IBinder activityThreadServices[key]? .let { onServicePreDestroy(key, it) } }// Execute the original logicmCallback? .handleMessage(msg) ? :false}}// hook Activity Manage object
swapActivityManager { activityManagerInterface, activityManagerInstance ->
uninstallActivityManager = {
swapActivityManager { _, _ ->
activityManagerInstance
}
}
// Dynamic proxy objects
Proxy.newProxyInstance(
activityManagerInterface.classLoader, arrayOf(activityManagerInterface)
) { _, method, args ->
// Hook service Destroy. The servcie object is not available, so use onServicePreDestroy
if(METHOD_SERVICE_DONE_EXECUTING == method.name) { val token = args!! [0] as IBinder
if (servicesToBeDestroyed.containsKey(token)) {
// Encapsulate the service as a weak reference and trigger the retention check after 5 seconds
onServiceDestroyed(token)
}
}
// Execute the original logic
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"}}}Copy the code
Look again at the onServiceDestroyed method
private fun onServiceDestroyed(token: IBinder) {
// Match the token to the service object obtained during preprocessingservicesToBeDestroyed.remove(token)? .also { serviceWeakReference -> serviceWeakReference.get()? .let { service ->// Add the service object to watchedObjects
reachabilityWatcher.expectWeaklyReachable(
service, "${service::class.java.name} received Service#onDestroy() callback")}}}Copy the code
All default detected objects have been read above, after which the object will be encapsulated as a weak reference and associated with a reclaim queue. The principle is:
- An object containing a weak reference will be added to the collection queue if GC is performed without other references
For example, an Activity A is encapsulated as A weak-reference weakA, and the weak-reference weakA is added to watchedObjects. After 5s, GC is triggered. If A’s reference is added to the recycle queue, then A can be recycled. That removes weakA from watchedObjects. On the other hand, if the reference of A is not added to the recycle queue and the object is referenced by another object, the memory leak is considered and the Heap dump and Analyze processes are triggered
Leakcanary Heap dump and Analyze processes
As mentioned in the previous article, the target object can be added to watchedObjects at a specific time, and heap dump is started after it is judged to be leaked. This process is quite complicated. Let’s take an example of Activity leakage and take a look at a UML diagram
sequenceDiagram autonumber ActivityWatcher ->>+ Activity: registerLifecycler Activity ->> Activity: onDstroy: Activity ->>- ActivityLifecycleCallbacks: ActivityLifecycleCallbacks ->>+ ObjectWatcher: onActivityDestroyed ObjectWatcher ->> ObjectWatcher: KeyedWeakReference ObjectWatcher -->>+ InternalLeakCanary: MoveToRetained (post a 5s delayed runnable) InternalLeakCanary ->> InternalLeakCanary: onObjectRetained InternalLeakCanary ->>+ HeapDumpTrigger: scheduleRetainedObjectCheck HeapDumpTrigger ->> HeapDumpTrigger: scheduleRetainedObjectCheck HeapDumpTrigger ->>+ HeapDumpControl: checkRetainedObjects HeapDumpControl ->>- HeapDumpTrigger: iCanHasHeap HeapDumpTrigger -->>+ ObjectWatcher: get retainedObjectCount ObjectWatcher -->>- HeapDumpTrigger : retainedObjectCount > 0 HeapDumpTrigger ->> HeapDumpTrigger: gc HeapDumpTrigger -->>+ ObjectWatcher: get retainedObjectCount ObjectWatcher -->>- HeapDumpTrigger : retainedObjectCount HeapDumpTrigger ->> HeapDumpTrigger: checkRetainedCount HeapDumpTrigger ->> HeapDumpTrigger: dumpHeap HeapDumpTrigger -->>+ ObjectWatcher: ObjectWatcher ->> ObjectWatcher: clearObjectsWatchedBefore ObjectWatcher -->>- HeapDumpTrigger: HeapDumpTrigger -->>- HeapAnalyzerService: HeapAnalyzerService ->> HeapAnalyzerService: runAnalysis HeapAnalyzerService ->> HeapAnalyzer: analyzeHeap HeapAnalyzer ->> HeapAnalyzer: analyze
Go back to when the Activity object is listening
ActivityWatcher.kt
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate(a) {
override fun onActivityDestroyed(activity: Activity) {
// Add watchedObjects to Activity onDestroy
reachabilityWatcher.expectWeaklyReachable(
activity, "${activity::class.java.name} received Activity#onDestroy() callback")}}Copy the code
- Tips: In the above code, the noOpDelegate uses Kotlin’s delegate mechanism and Java’s dynamic proxy mechanism to implement only the methods of the interface of interest, and the other methods are automatically completed by the delegate to clean up the code
ReachabilityWatcher is actually an ObjectWatcher, and look at expectweak Reachable methods
@Synchronized override fun expectWeaklyReachable( watchedObject: Any, description: String ) {
if(! isEnabled()) {return
}
// Clean up the objects that have been reclaimed
removeWeaklyReachableObjects()
val key = UUID.randomUUID()
.toString()
val watchUptimeMillis = clock.uptimeMillis()
// Encapsulate a weak reference. If there are no other references, the objects in this weak reference will be collected during GC and added to the queue
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"
}
// Add a retention monitor map. If objects are still in the map after GC, they are considered leaked
watchedObjects[key] = reference
// Add a monitor runnable, execute after 5s
checkRetainedExecutor.execute {
moveToRetained(key)
}
}
Copy the code
First take a look at removeWeaklyReachableObjects implementation, this method has called in many places, is to be able to keep clear of in time has been recycled object records
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 {
/** * Weak references contain objects that, if reclaimed, are added to the associated reclaim queue */
ref = queue.poll() as KeyedWeakReference?
if(ref ! =null) {
// The reclaimed object removes the record from watchedObjects
watchedObjects.remove(ref.key)
}
} while(ref ! =null)}Copy the code
After the cleanup is complete, Leakcanary encapsulates the Activity object as a weak reference and associates it with a UUID generated key and a reclaim queue. Add this key and weak reference key-value to watchedObjects, and randomly checkRetainedExecutor posts a runnable to the main thread at a default initialization delay of 5 minutes. Let’s take a look at moveToRetained(key) implementation
@Synchronized private fun moveToRetained(key: String) {
removeWeaklyReachableObjects()
val retainedRef = watchedObjects[key]
if(retainedRef ! =null) {
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
/ / onObjectRetainedListeners actual is initialized in the incoming InternalLeakCanary. Kt object
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}
Copy the code
Let’s go back to the initialization logic handled by InternalLeakCanary at initialization
InternalLeakCanary.kt
override fun invoke(application: Application) {
// Pass in the Application object
_application = application
checkRunningInDebuggableBuild(a)
// Register object retention detection listeners to ObjectWatcher
AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
// heap dump
val heapDumper = AndroidHeapDumper(application, createLeakDirectoryProvider(application))
// Check whether GC conditions are met
val gcTrigger = GcTrigger.Default
val configProvider = { LeakCanary.config }
val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
handlerThread.start()
val backgroundHandler = Handler(handlerThread.looper)
// Determine whether heap dump conditions are met
heapDumpTrigger = HeapDumpTrigger(
application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
configProvider
)
// Apply front-end and background listener logic differentiation processing
application.registerVisibilityListener { applicationVisible ->
this.applicationVisible = applicationVisible
heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
}
registerResumedActivityListener(application)
// Add the LeakCanary icon on the desktop
addDynamicShortcut(application)
// We post so that the log happens after Application.onCreate()
mainHandler.post {
// https://github.com/square/leakcanary/issues/1981
// We post to a background handler because HeapDumpControl.iCanHasHeap() checks a shared pref
// which blocks until loaded and that creates a StrictMode violation.
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()
)
}
}
}
}
}
Copy the code
Back to front onObjectRetained method, you will call to HeapDumpTrigger. The kt of scheduleRetainedObjectCheck ()
HeapDumpTrigger.kt
fun scheduleRetainedObjectCheck(
delayMillis: Long = 0L
) {
val checkCurrentlyScheduledAt = checkScheduledAt
if (checkCurrentlyScheduledAt > 0) { // If the value is greater than 0, it indicates that the system is already monitoring
return
}
// Record the current detection time
checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
backgroundHandler.postDelayed({ / / the child thread
checkScheduledAt = 0
// Test the retained objects
checkRetainedObjects()
}, delayMillis)
}
Copy the code
Frequent repeated checks are avoided by setting the timestamp and a runnable is posted to the child thread
HeapDumpTrigger.kt
private fun checkRetainedObjects(a) {
// Whether dump heap can be triggered
val iCanHasHeap = HeapDumpControl.iCanHasHeap()
val config = configProvider()
if (iCanHasHeap is Nope) {
if (iCanHasHeap is NotifyingNope) { // Send a notification, the user clicks, can force trigger monitoring
/ /... Omit code
}
return
}
// Get the number of remaining objects
var retainedReferenceCount = objectWatcher.retainedObjectCount
if (retainedReferenceCount > 0) {
gcTrigger.runGc() / / triggers the GC
retainedReferenceCount = objectWatcher.retainedObjectCount // Get the number of objects that were not collected again
}
/** * Determine whether to enable the dump heap based on the number of retained heap stores * to minimize the impact, the number of heap stores applied on the front and back ends is different. By default, there are at least 5 heap stores for the front end and at least 1 heap stores for the back end (the time required to dump the back end exceeds the monitoring period) */
if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
val now = SystemClock.uptimeMillis()
val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) { // The heap dump is not repeated within one minuteonRetainInstanceListener.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( // Triggers dump heap
retainedReferenceCount = retainedReferenceCount,
retry = true,
reason = "$retainedReferenceCount retained objects, app is $visibility")}Copy the code
According to the number of watchedObjects checked after GC is triggered, the default value is greater than or equal to 5 in the foreground and 1 in the background. When the condition is met, heap dump is triggered
HeapDumpTrigger.kt
private fun dumpHeap( retainedReferenceCount: Int, retry: Boolean, reason: String ) {
saveResourceIdNamesToMemory()
val heapDumpUptimeMillis = SystemClock.uptimeMillis()
KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
// Dump heap result
when (val heapDumpResult = heapDumper.dumpHeap()) {
is NoHeapDump -> { / / fail
/ /... Omit code
}
is HeapDump -> { / / success
lastDisplayedRetainedObjectCount = 0
lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()
objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
// Start an IntentService to analyze hprof in the child thread
HeapAnalyzerService.runAnalysis(
context = application,
heapDumpFile = heapDumpResult.file,
heapDumpDurationMillis = heapDumpResult.durationMillis,
heapDumpReason = reason
)
}
}
}
Copy the code
The dumped hprof files are handed to the HeapAnalyzerService for analysis, and the HeapAnalyzerService starts Shark to analyze the memory files. Shark is not familiar with it, so I’m not going to analyze it. Each step in the analysis process has a corresponding callback:
OnAnalysisProgressListener.kt
enum class Step {
PARSING_HEAP_DUMP,
EXTRACTING_METADATA,
FINDING_RETAINED_OBJECTS,
FINDING_PATHS_TO_RETAINED_OBJECTS,
FINDING_DOMINATORS,
INSPECTING_OBJECTS,
COMPUTING_NATIVE_RETAINED_SIZE,
COMPUTING_RETAINED_SIZE,
BUILDING_LEAK_TRACES, // Build the leak path
REPORTING_HEAP_ANALYSIS
}
Copy the code
Notably, Leakcanary has opened a callback for the results of memory analysis, allowing users to implement the interface and report the results to their own service servers
HeapAnalyzerService.kt#onHandleIntentInForeground()
/** * Can be customized onHeapAnalyzedListener, A trace * leaktracewrapper. wrap(heapAnalysis.tostring (), 120) formats the trace */
config.onHeapAnalyzedListener.onHeapAnalyzed(fullHeapAnalysis)
Copy the code
The official recommended method is
class LeakUploader : OnHeapAnalyzedListener {
// The default implementation of LeakCanary is to record future leaks and avoid repeated reporting
val defaultListener = DefaultOnHeapAnalyzedListener.create()
override fun onHeapAnalyzed(heapAnalysis: HeapAnalysis) {
TODO("Upload heap analysis to server")
// Delegate to default behavior (notification and saving result)
defaultListener.onHeapAnalyzed(heapAnalysis)
}
}
class DebugExampleApplication : ExampleApplication(a){
override fun onCreate(a) {
super.onCreate()
// Use the custom OnHeapAnalyzedListener to delegate the original logic after processing your own services
LeakCanary.config = LeakCanary.config.copy(
onHeapAnalyzedListener = LeakUploader()
)
}
}
Copy the code
Finally attach Leakcanary official documentation about how to fix the leak, it illustrates how to check the leakage source square. The dead simple. IO/Leakcanary /…
Finally, the final excerpt from LeakCanary explains how to fix memory leaks using weak references, using weak references with caution and addressing the problem at the source of the leak
Memory Leaks cannot be fixed by replacing strong references with weak references. It’s a common solution when attempting to quickly address memory issues, however it never works. The bugs that were causing references to be kept longer than necessary are still there. On top of that, it creates more bugs as some objects will now be garbage collected sooner than they should. It also makes the code much harder to maintain.