main

square/leakcanary

Last updated at: 29/12/2023 09:39

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
    }
  }
}