【问题标题】:Android AlertDialog DecorView Memory LeakAndroid AlertDialog DecorView 内存泄漏
【发布时间】:2023-07-11 16:51:01
【问题描述】:

通过Executor类发起网络通信时,我使用了showProgress函数。通信结束后收到结果时,使用 hideProgess 函数。代码如下,我在BaseActivity中使用。

    private val networkIO = Executors.newFixedThreadPool(3)
    private var progressBar: AlertDialog? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        progressBar = AlertDialog.Builder(this)
            .setView(R.layout.dialog_loading)
            .setCancelable(false)
            .create()
            .apply { window?.setBackgroundDrawableResource(android.R.color.transparent) }

        doOnCreate()
    }

    override fun onDestroy() {
        progressBar = null
        super.onDestroy()
    }

    fun showProgress() {
        runOnUiThread {
            try {
                if (progressBar?.isShowing == false) {
                    progressBar?.show()
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }

    fun hideProgress() {
        runOnUiThread { progressBar?.dismiss() }
    }

在 MainActivity 中执行下一次网络通信后移动到另一个 Activity 时发生内存泄漏。

private fun fetchCard() {
    networkIO.execute {
        showProgress()
        networkManager.getCardInfo { _, code, message ->
            Timber.d("## $code")
            Timber.d("## $message")
            hideProgress()
        }
    }
}

LeakCanary 提供的泄漏原因

2021-03-08 05:11:42.552 8678-8678/com.tagless D/LeakCanary: ​
    ┬───
    │ GC Root: System class
    │
    ├─ android.app.ActivityThread class
    │    Leaking: NO (MainActivity↓ is not leaking and a class is never leaking)
    │    ↓ static ActivityThread.sCurrentActivityThread
    ├─ android.app.ActivityThread instance
    │    Leaking: NO (MainActivity↓ is not leaking)
    │    mInitialApplication instance of com.tagless.TaglessApp
    │    mSystemContext instance of android.app.ContextImpl
    │    mSystemUiContext instance of android.app.ContextImpl
    │    ↓ ActivityThread.mActivities
    ├─ android.util.ArrayMap instance
    │    Leaking: NO (MainActivity↓ is not leaking)
    │    ↓ ArrayMap.mArray
    ├─ java.lang.Object[] array
    │    Leaking: NO (MainActivity↓ is not leaking)
    │    ↓ Object[].[3]
    ├─ android.app.ActivityThread$ActivityClientRecord instance
    │    Leaking: NO (MainActivity↓ is not leaking)
    │    activity instance of com.tagless.ui.main.MainActivity with mDestroyed = false
    │    ↓ ActivityThread$ActivityClientRecord.activity
    ├─ com.tagless.ui.main.MainActivity instance
    │    Leaking: NO (Activity#mDestroyed is false)
    │    mApplication instance of com.tagless.TaglessApp
    │    mBase instance of androidx.appcompat.view.ContextThemeWrapper
    │    ↓ BaseActivity.progressBar
    │                   ~~~~~~~~~~~
    ├─ androidx.appcompat.app.AlertDialog instance
    │    Leaking: UNKNOWN
    │    Retaining 106.7 kB in 1647 objects
    │    mContext instance of android.view.ContextThemeWrapper, wrapping activity com.tagless.ui.main.MainActivity with
    │    mDestroyed = false
    │    Dialog#mDecor is null
    │    ↓ Dialog.mWindow
    │             ~~~~~~~
    ├─ com.android.internal.policy.PhoneWindow instance
    │    Leaking: UNKNOWN
    │    Retaining 23.3 kB in 289 objects
    │    mContext instance of android.view.ContextThemeWrapper, wrapping activity com.tagless.ui.main.MainActivity with
    │    mDestroyed = false
    │    Window#mDestroyed is false
    │    ↓ PhoneWindow.mDecor
    │                  ~~~~~~
    ╰→ com.android.internal.policy.DecorView instance
    ​     Leaking: YES (ObjectWatcher was watching this because com.android.internal.policy.DecorView received
    ​     View#onDetachedFromWindow() callback)
    ​     Retaining 3.4 kB in 59 objects
    ​     key = 5f2f819a-9640-429f-afff-4489f7b039d2
    ​     watchDurationMillis = 5703
    ​     retainedDurationMillis = 702
    ​     View not part of a window view hierarchy
    ​     View.mAttachInfo is null (view detached)
    ​     View.mWindowAttachCount = 1
    ​     mContext instance of android.view.ContextThemeWrapper, wrapping activity com.tagless.ui.main.MainActivity with
    ​     mDestroyed = false
    
    METADATA
    
    Build.VERSION.SDK_INT: 29
    Build.MANUFACTURER: samsung
    LeakCanary version: 2.6
    App process name: com.tagless
    Stats: LruCache[maxSize=3000,hits=5668,misses=70891,hitRate=7%]
    RandomAccess[bytes=3887192,reads=70891,travel=27244024610,range=20775300,size=25990100]
    Heap dump reason: user request
    Analysis duration: 3864 ms

提前谢谢任何人请帮忙!

【问题讨论】:

  • 你试过用 .dispose() 或 .cancel() 代替 .dismiss() 吗?
  • @javdromero 我尝试了取消功能,但它是相同的,并且 dispose 不是对话框功能。

标签: android memory-leaks leakcanary


【解决方案1】:

添加progressBar = null 应该可以解决您的问题。 progressBar被Activity引用,不能被GC回收。

    fun showProgress() {
        runOnUiThread {
            try {
                if (progressBar == null) {
                    progressBar = AlertDialog.Builder(this)
                        .setView(R.layout.dialog_loading)
                        .setCancelable(false)
                        .create()
                        .apply { window?.setBackgroundDrawableResource(android.R.color.transparent) }
                }
                
                if (progressBar?.isShowing == false) {
                    progressBar?.show()
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }

    fun hideProgress() {
        runOnUiThread { 
            progressBar?.dismiss()
            progressBar = null
        }
    }

【讨论】: