main

square/leakcanary

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

RootViewWatcher.kt

TLDR

The RootViewWatcher class is a part of the LeakCanary library and is used to track root views and expect them to become weakly reachable after they are removed from the window manager.

Classes

RootViewWatcher

The RootViewWatcher class implements the InstallableWatcher interface and is responsible for tracking root views. It has the following properties and methods:

  • reachabilityWatcher: This property holds an instance of the ReachabilityWatcher interface.
  • listener: This property holds an instance of the OnRootViewAddedListener interface.
  • install(): This method installs the RootViewWatcher instance by adding the listener to the onRootViewsChangedListeners list in the Curtains class.
  • uninstall(): This method uninstalls the RootViewWatcher instance by removing the listener from the onRootViewsChangedListeners list in the Curtains class.
/*
 * Copyright (C) 2015 Square, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package leakcanary

import android.app.Activity
import android.app.Dialog
import android.view.View
import android.view.View.OnAttachStateChangeListener
import com.squareup.leakcanary.objectwatcher.core.R
import curtains.Curtains
import curtains.OnRootViewAddedListener
import curtains.WindowType.PHONE_WINDOW
import curtains.WindowType.POPUP_WINDOW
import curtains.WindowType.TOAST
import curtains.WindowType.TOOLTIP
import curtains.WindowType.UNKNOWN
import curtains.phoneWindow
import curtains.windowType
import curtains.wrappedCallback
import leakcanary.internal.friendly.mainHandler

/**
 * Expects root views to become weakly reachable soon after they are removed from the window
 * manager.
 */
class RootViewWatcher(
  private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {

  private val listener = OnRootViewAddedListener { rootView ->
    val trackDetached = when(rootView.windowType) {
      PHONE_WINDOW -> {
        when (rootView.phoneWindow?.callback?.wrappedCallback) {
          // Activities are already tracked by ActivityWatcher
          is Activity -> false
          is Dialog -> {
            // Use app context resources to avoid NotFoundException
            // https://github.com/square/leakcanary/issues/2137
            val resources = rootView.context.applicationContext.resources
            resources.getBoolean(R.bool.leak_canary_watcher_watch_dismissed_dialogs)
          }
          // Probably a DreamService
          else -> true
        }
      }
      // Android widgets keep detached popup window instances around.
      POPUP_WINDOW -> false
      TOOLTIP, TOAST, UNKNOWN -> true
    }
    if (trackDetached) {
      rootView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {

        val watchDetachedView = Runnable {
          reachabilityWatcher.expectWeaklyReachable(
            rootView, "${rootView::class.java.name} received View#onDetachedFromWindow() callback"
          )
        }

        override fun onViewAttachedToWindow(v: View) {
          mainHandler.removeCallbacks(watchDetachedView)
        }

        override fun onViewDetachedFromWindow(v: View) {
          mainHandler.post(watchDetachedView)
        }
      })
    }
  }

  override fun install() {
    Curtains.onRootViewsChangedListeners += listener
  }

  override fun uninstall() {
    Curtains.onRootViewsChangedListeners -= listener
  }
}