为什么 AccessibilityManager.sInstance 会导致内存泄漏?

Is there a reason for why AccessibilityManager.sInstance would cause a memory leak?

我有一个包含片段的 activity。 运行 Leak Canary,我看到 activity 有内存泄漏。

我已经注释掉了从 activity 和片段到 activity 仅显示片段并且片段具有空 xml 布局的所有代码。我在任一文件或 xml.

中都没有可访问性
* AccessibilityManager.!(this[=11=])! (anonymous subclass of android.view.accessibility.IAccessibilityManagerClient$Stub)
* ↳ AccessibilityManager.!(mTouchExplorationStateChangeListeners)!
* ↳ CopyOnWriteArrayList.!(elements)!
* ↳ array Object[].!([2])!
* ↳ AccessibilityManagerCompat$TouchExplorationStateChangeListenerWrapper.!(mListener)!
* ↳ BaseTransientBottomBar$SnackbarBaseLayout.!(this[=11=])! (anonymous implementation of android.support.v4.view.accessibility.AccessibilityManagerCompat$TouchExplorationStateChangeListener)
* ↳ Snackbar$SnackbarLayout.mContext
* ↳ ContextThemeWrapper.mBase
* ↳ MessagesActivity

我遇到过类似的问题。我持有对 Snackbar 的引用。在我删除该引用后,内存泄漏就消失了。

更新

例如替换

val snackbar = Snackbar.make(rootLayout, "Hello Snackbar", Snackbar.LENGTH_INDEFINITE)
snackbar.show()

Snackbar.make(rootLayout, "Hello Snackbar", Snackbar.LENGTH_INDEFINITE).show()

我不知道为什么它解决了我的问题。我无法在其他项目中重现此内存泄漏。根据堆栈跟踪,系统似乎没有调用 BaseTransientBottomBar.onDetachedFromWindow(),因此 touchExplorationStateChangeListener 没有从 accessibilityManager 中删除。同样,我不知道为什么会这样。下面是 BaseTransientBottomBar.onDetachedFromWindow().

的代码
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (this.onAttachStateChangeListener != null) {
            this.onAttachStateChangeListener.onViewDetachedFromWindow(this);
        }

        AccessibilityManagerCompat.removeTouchExplorationStateChangeListener(this.accessibilityManager, this.touchExplorationStateChangeListener);
    } 

好的,我真的想通了。这是 Snackbar 中的内存泄漏,这里是如何重现它的:https://github.com/GC-Xi/SnackbarBug

复制方式

  1. 创建一个 Snackbar 并在 Activity
  2. 中引用它
  3. 不要调用 Snackbar.show()
  4. 打开和关闭 Activity
  5. 请注意 Activity 未被垃圾回收,因为 snackbar 引用了它

原因

SnackbarBaseLayout 在构造函数中调用 addTouchExplorationStateChangeListener(),在 onDetachedFromWindow() 中调用 removeTouchExplorationStateChangeListener()addTouchExplorationStateChangeListener() 可能应该从 onAttachedToWindow() 调用,因为 SnackbarBaseLayout 没有附加到 window 除非调用 Snackbar.show()

解决方案 1

更新到 AndroidX 并改用 com.google.android.material.snackbar.Snackbar。 https://github.com/GC-Xi/SnackbarBug/tree/solution1

解决方案 2

除非您准备好显示 Snackbar,否则不要创建它。 https://github.com/GC-Xi/SnackbarBug/tree/solution2