2.3 使用Lifecycle解决实际项目中常见的问题

了解Lifecycle的基本使用之后,接着来看如何使用Lifecycle解决实际项目中常见的问题。

2.3.1 Dialog内存泄漏问题分析

Dialog是Android开发中最常用的组件之一,相信每位读者都使用过。现在一起实现一个Dialog,用于网络请求的简单提示。Dialog的代码很简单,直接设置一个布局即可,具体如下:

class TipDialog(context: Context) : Dialog(context) {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.item_tip_dialog)
    }
}

接下来模拟如下场景:

  • 进入页面时开始网络请求,显示Dialog。
  • 请求结束时(两秒后),退出当前页面。

下面实现上述需求,Activity的代码如下:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)
        TipDialog(this).show()
        Handler().postDelayed({
            finish()
        }, 2000)
    }
}

运行程序,两秒后,软件出现异常崩溃,错误日志如下:

00:58:23.618 28162-28162/com.example.jetpackdemo E/WindowManager: android.view.WindowLeaked: Activity com.example.jetpackdemo.ui.activity.MainActivity has leaked window DecorView@df7a4a4[MainActivity] that was originally added here
        at android.view.ViewRootImpl.<init>(ViewRootImpl.java:818)
        at android.view.ViewRootImpl.<init>(ViewRootImpl.java:798)
        at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:399)
        at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:110)
        at android.app.Dialog.show(Dialog.java:353)
        at com.example.jetpackdemo.ui.activity.MainActivity.onCreate(MainActivity.kt:23)
        at android.app.Activity.performCreate(Activity.java:8146)
        at android.app.Activity.performCreate(Activity.java:8130)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1310)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3668)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3866)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:140)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:100)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2296)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:254)
        at android.app.ActivityThread.main(ActivityThread.java:8234)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:612)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1006)

相信很多开发者都遇到过这个错误,这是因为在Activity关闭的时候,Dialog没有关闭,进而导致内存泄漏。了解了出现异常的原因,解决起来就很容易了,在销毁Activity的时候,关闭Dialog即可,示例代码如下:

class MainActivity : AppCompatActivity() {
    var dialog: TipDialog? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)
        dialog = TipDialog(this)
        dialog?.show()
        Handler().postDelayed({
            finish()
        }, 2000)
    }
    override fun onDestroy() {
        super.onDestroy()
        dialog?.dismiss()
    }
}

上面的方法虽然可以解决内存泄漏问题,但若弹窗类型很多,则需要在onDestroy中编写许多额外的处理逻辑,且容易忘记,不过,如果使用Lifecycle组件,就可以完美地解决这个问题。

2.3.2 使用Lifecycle打造一个完美的Dialog

由于Dialog中的参数Context必须是Activity的上下文,因此开发者完全可以在Dialog中使用Lifecycle组件来感知生命周期,在2.2.1节中已经介绍,只要是在androidx.fragment.app.Fragment、ComponentActivity及其子类Activity中,就可以直接使用Lifecycle组件。所以这成了一个取舍问题,如果xxxActivity继承的是Activity,将无法直接使用Lifecycle,那就只能自定义LifecycleOwner或者在Activity中注册了,但是这样做完全没有必要,这里默认xxxActivity继承的是ComponentActivity。

修改TipDialog的代码,使得TipDialog可以感应生命周期变化,示例如下:

class TipDialog(context: Context) : Dialog(context), LifecycleObserver {
    init {
        if (context is ComponentActivity) {
            (context as ComponentActivity).lifecycle.addObserver(this)
        }
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.item_tip_dialog)
    }
    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    private fun onDestroy() {
        if (isShowing) {
            dismiss()
        }
    }
}

上面的代码在2.2.1节中已经详细介绍了,此处就不再赘述。当Dialog所依附的Activity被销毁时,Dialog也可以自动关闭,再也不用担心Dialog的内存泄漏问题了。如此一来,就使用Lifecycle实现了一个完美的Dialog。

注意

在实际项目开发中,Dialog使用不当会出现除了内存泄漏之外的其他问题,这需要开发者自行处理,这里解决的只是此种情境下产生的内存泄漏问题。