乐趣区

关于android:Android-中意料之外的应用崩溃以及它们的解决方案

如果问前端、后端甚至游戏开发人员之间存在什么共同点,那就是咱们都厌恶利用产品呈现 Bug,尤其是当这些谬误导致利用解体时。而在利用公布后,监督应用程序中这些一直减少的解体是一种极其不欢快的体验。

不论应用程序的业务逻辑如何,都可能会因为运行的零碎或平台问题而导致呈现某些奇怪的解体景象。在 Android 中,从后盾状态复原应用程序时可能会产生解体 —— 此类解体是意外产生的,而且仅通过查看解体日志,咱们很难了解解体的具体起因以及解决问题,而本文探讨了此类问题及其解决办法。

问题

在监督产品的解体日志时,我留神到一些问题一劳永逸。该利用在失常测试条件下仿佛运行良好,并且解体不可复现,直到应用程序从后台任务中进入前台。

每个 Android 应用程序都在其本人的过程中运行,并且操作系统已为该过程调配了一些内存。当用户与其余应用程序交互时将应用程序置于后盾时,如果应用程序没有足够的可用内存,则操作系统会终止你的应用程序过程。而这一状况通常产生在前台运行另一个须要更大手机内存 (RAM) 的应用程序时。

当应用程序过程被终止的时候,所有的单例对象和长期数据都同时失落了,而当初如果你返回你的应用程序,零碎会创立一个新的过程,而你的应用程序会从你退出时候的 Activity 栈顶执行 Resume 函数复原该 Activity。

因为此时你的所有的单例对象都失落了,因而当这个 Activity 尝试拜访雷同的对象时,就会遇到空指针异样而解体退出。

这是个问题。在咱们持续探讨解决方案之前,让咱们复现一下这种状况。

复现解体

  1. 在模拟器或通过 USB 电缆(译者注:Android 11 也可应用 Wi-Fi 连贯设施调试)连贯的理论设施上应用 ADB 运行指令(如 Android Studio)运行的任何应用程序。
  2. 导航到任意一个页面,而后按下“主页”按钮。
  3. 关上终端,键入以下命令,咱们就能够获取应用程序的过程 ID(PID)。
adb shell pidof com.darktheme.example

该命令的语法为 adb shell pidof APP_BUNDLE_ID

请记下你在终端窗口上看到的 PID(这可用于验证现有的应用程序过程是否已被终止,并在咱们复原应用程序时启动了新的过程)。

  1. 键入以下终端命令以终止你的应用程序过程
adb shell am kill com.darktheme.example

当初,从后台任务中关上你的应用程序,并查看该应用程序是否解体。如果是,请不要放心,咱们将在下一部分中探讨如何解决此问题。如果没有,你能够松一口气了,因为这是你应得的。

须要留神的是,从后盾关上利用后,请从新获取利用所属过程的 PID。如果你在第 3 步中记下的 PID 与新的 PID 相等,则该过程并没有被终止。

倡议的解决方案

有两种办法能够解决此问题。依据你所处的状况,你能够决定用哪一个办法来推动问题的解决:

解决方案 1:

一种简便的解决方案是,当用户从后盾复原应用程序时,让应用程序查看咱们现有的应用程序过程是否被完结并从新创立。如果是,则能够导航回启动界面,使其看起来像是一个应用程序的初始化界面。

你能够将以下代码放在 BaseActivity 中:

    override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)
        if (savedInstanceState != null) {
            // 获取以后 PID
            val currentPID = android.os.Process.myPid().toString()

            // 比拟以后 PID 与 保留的 PID 是否统一
            if (currentPID != savedInstanceState.getString(PID)) {
                // 如果以后 PID 与 保留的 PID 不雷同,意味着新的过程被创立,从 SplashActivity 重启利用
                val intent = Intent(applicationContext, SplashActivity::class.java)
                intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
                startActivity(intent)
                finish()}
        }
    }

    override fun onSaveInstanceState(bundle: Bundle) {super.onSaveInstanceState(bundle)
        bundle.putString(PID, android.os.Process.myPid().toString())
    }
  • 通过覆写 onSaveInstanceState() 性能,你能够将你的 PID 打包保留下来。
  • onCreate() 办法中,你须要比拟以后 PID 和打包保留的 PID。
  • 如果以后过程是是从新创立的流程,则重定向导航到 Splash Activity。

当用户从后盾导航回被完结了的应用程序时候,该应用程序将从 SplashActivity 重新启动,就像是一次新的启动。

这将避免应用程序拜访在过程重建过程中可能已失落的数据,从而避免应用程序解体。

尽管此解决方案能够避免解体,然而这种办法其实就是重新启动应用程序,而不是从中断的地位复原应用程序。如果你在公布利用后遇到此问题,并且急迫地心愿疾速解决这个问题,则此解决方案应该能帮你大忙。

然而,如果你刚从头开始开发,则解决方案 2 将是你的现实抉择,因为它能够做到从中断的地位复原应用程序。

解决方案 2:

当初,你必定曾经留神到能够利用“包”对象保留和拜访数据。与后面的示例中的操作相似,将每个 Activity / Fragment 中所有必要的信息保留下来。

因为咱们拜访是被保留在“包”中的数据,这会防止应用程序解体,并且应用程序能从中断处复原。所有其余 Activity / Fragment 也会被从新创立。

对于 Fragment 中的 RecyclerView,做法应该是:

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)
        val recyclerView = view.findViewById(R.id.recyclerView)
        recyclerView.layoutManager = LinearLayoutManager(context)
        users = savedInstanceState?.getParcelableArrayList("userList") ?: viewModel.getUsers()
        rv.adapter = DataAdapter(users, this)
    }

    override fun onSaveInstanceState(outState: Bundle) {super.onSaveInstanceState(outState)
        outState.putParcelableArrayList("userList", users as ArrayList)
    }
  • 通过覆写 onSaveInstanceState() 性能,咱们能够将所需信息保留在 Bundle 对象中。
  • 咱们会让应用程序查看 onViewCreated() 函数中捆绑包中的数据是否可用,如果不可用,则会通过拜访 ViewModel 的办法获取数据。

论断

在 Android 平台上,因为过程被终止而导致的利用解体是很常见的。而如果咱们应用较新的 Android 版本,咱们能够留神到,出于节俭电源的目标,大量的后盾应用程序被强制完结运行了。

解决方案 1 能够疾速解决你现有的利用解体问题。

然而,如果你正在从头开始开发应用程序,我倡议应用解决方案 2,因为它能够确保零碎会从先前敞开的地位复原该应用程序,因而带来更好的用户体验。

钻研此类解体的根本原因可能会挺艰难的,因而我心愿本文可能以任何可能的形式对你有所帮忙。请通知我你们对文中探讨的解决方案有何认识。

关注我,每天分享常识干货,你要的,我都有~~~

退出移动版