ViewLocationHolderLeakFix.kt
TLDR
The ViewLocationHolderLeakFix
file provides a fix for a memory leak issue related to the ViewGroup.ViewLocationHolder.sPool
static pool. It includes methods to apply the fix and clear the static pool.
Methods
applyFix
This method applies the fix for the memory leak issue. It checks if the version of the Android SDK is 28 (Android 9.0 Pie) and registers a listener to handle the removal of root views, such as child windows like dialogs. If the current thread is the main thread, it directly calls the uncheckedClearStaticPool
method, otherwise it uses the mainHandler
to post a task to the main thread to call the uncheckedClearStaticPool
method. Additionally, it registers an activity lifecycle callback to handle the destruction of AndroidX fragment views.
clearStaticPool
This method clears the ViewGroup.ViewLocationHolder.sPool
static pool. It first checks if the current thread is the main thread and that the Android SDK version is 28 (Android 9.0 Pie). It then calls the uncheckedClearStaticPool
method.
uncheckedClearStaticPool
This private method attempts to clear the ViewGroup.ViewLocationHolder.sPool
static pool. It creates a FrameLayout
as a ViewGroup
and adds 32 View
objects to it. It then adds the children of the FrameLayout
to an ArrayList
using the addChildrenForAccessibility
method. If an exception is thrown during this process, the method logs an error and sets the failedClearing
flag to true, preventing future attempts to clear the static pool.
package leakcanary
import android.annotation.SuppressLint
import android.app.Activity
import android.app.Application
import android.os.Build.VERSION
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import curtains.Curtains
import curtains.OnRootViewRemovedListener
import leakcanary.internal.friendly.checkMainThread
import leakcanary.internal.friendly.isMainThread
import leakcanary.internal.friendly.mainHandler
import leakcanary.internal.friendly.noOpDelegate
import leakcanary.internal.onAndroidXFragmentViewDestroyed
import shark.SharkLog
/**
* @see [AndroidLeakFixes.VIEW_LOCATION_HOLDER].
*/
@SuppressLint("NewApi")
object ViewLocationHolderLeakFix {
private var groupAndOutChildren: Pair<ViewGroup, ArrayList<View>>? = null
private var failedClearing = false
internal fun applyFix(application: Application) {
if (VERSION.SDK_INT != 28) {
return
}
// Takes care of child windows (e.g. dialogs)
Curtains.onRootViewsChangedListeners += OnRootViewRemovedListener {
if (isMainThread) {
uncheckedClearStaticPool(application)
} else {
mainHandler.post {
uncheckedClearStaticPool(application)
}
}
}
application.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks
by noOpDelegate() {
override fun onActivityCreated(
activity: Activity,
savedInstanceState: Bundle?
) {
activity.onAndroidXFragmentViewDestroyed {
uncheckedClearStaticPool(application)
}
}
})
}
/**
* Clears the ViewGroup.ViewLocationHolder.sPool static pool.
*/
fun clearStaticPool(application: Application) {
checkMainThread()
if (VERSION.SDK_INT != 28) {
return
}
uncheckedClearStaticPool(application)
}
private fun uncheckedClearStaticPool(application: Application) {
if (failedClearing) {
return
}
try {
if (groupAndOutChildren == null) {
val viewGroup = FrameLayout(application)
// ViewLocationHolder.MAX_POOL_SIZE = 32
for (i in 0 until 32) {
val childView = View(application)
viewGroup.addView(childView)
}
groupAndOutChildren = viewGroup to ArrayList()
}
val (group, outChildren) = groupAndOutChildren!!
group.addChildrenForAccessibility(outChildren)
} catch (ignored: Throwable) {
SharkLog.d(ignored) {
"Failed to clear ViewLocationHolder leak, will not try again."
}
failedClearing = true
}
}
}