Android应对内存压力时保存UI状态的策略

当一个应用程序启动时,Android系统用一个执行线程(主线程)为该应用程序启动一个新进程。在Android中,应用程序本身并不直接控制其进程生命周期。相反,系统会根据应用程序对用户的重要性和总内存的可用性来确定应用程序的生存期。在可用内存非常低的危急情况下,系统需要通过杀死最不必要的进程来回收内存,以保持系统的正常运行。本文将讨论Android系统如何决定在不影响用户的情况下杀死哪个应用程序来释放内存,以及在应用程序被系统杀死时保存UI状态的策略。

内存压力与进程优先级

内存压力是系统内存不足的状态。系统需要通过终止不重要的进程或请求进程释放非关键的缓存资源来释放内存。在决定要杀死哪些进程时,Android系统会根据运行在其中的组件和这些组件的状态来权衡它们对用户的相对重要性。

以下是进程的重要性层次结构(不包括Linux附带的本机或系统进程):

前景处理:用户当前正在与之交互的进程。如果一个过程满足以下条件之一,则认为它处于前景中:

在屏幕顶部有一个正在运行的活动(已调用该活动的onResume()回调)。
具有正在运行的广播接收器(正在执行onReceive()方法)。
包含当前正在其某个回调(onCreate()、onStart()或onDestroy()中执行代码的服务。
这些进程具有最高优先级,并且是在内存压力下最后被终止的进程。

可见过程:以某种方式用户可以感知到的过程,例如在后台播放音乐的应用程序。在以下条件下,过程被视为可见:

1、具有一个正在运行的活动,该活动在屏幕上对用户可见,但在前台不可见(已调用该活动的onPause()回调)。例如,前台活动向用户显示一个对话框。
2、前台服务正在运行
3、包含系统正在为用户知道的特定功能使用的服务,例如实时墙纸或输入法服务。这些进程也被认为是重要的,不会被终止,除非这样做是为了保持所有前台进程的运行。
服务流程:包含用于执行诸如后台网络数据上载或下载、存储压缩…已运行很长时间的服务的进程的重要性可能会降级,以避免过度使用资源。虽然这些进程对用户来说不是直接可见的,但它们通常仍在做一些基本的事情,因此系统会尽量使它们保持活动状态—没有足够的内存来保留所有前景和可见的进程。

缓存进程:用户以前使用过但当前不需要的进程。它们被保存在内存中,以便在应用程序之间切换时提高效率。当其他地方需要内存等资源时,系统可以根据需要随意终止这些进程。过程越旧,越有可能先被杀死。

在决定如何对流程进行分类时,系统将根据流程中当前活动的所有组件中最重要的级别进行决策。如果您不希望在用户离开应用程序时终止长时间运行的操作,请确保您的进程具有高优先级。你可以访问官方指南流程和应用程序生命周期了解有关流程和应用程序生命周期的更多信息。

当应用程序在内存压力下被终止时保留UI状态

如果用户暂时离开你的应用程序,稍后再回来,他们希望用户界面保持不变。但是,当用户不在时,系统可能会破坏应用程序的进程。为了确保流畅的用户体验,你的应用程序应该保持用户界面状态,因为它以前从未被杀死过。

当活动由于内存限制而被销毁时,系统会记住它的存在,并在该活动被杀死之前保存一组数据。这些保存的数据称为实例状态并且将在稍后重新创建活动时可用。

onSaveInstanceState()

当活动开始停止时,系统将为活动调用onSaveInstanceState()方法以保存必要的信息。数据将存储在Bundle对象中,该对象是一组键值对。此方法的默认实现已经保存了活动的视图层次结构的状态,例如EditText中的文本或RecyclerView的当前滚动位置。因此,在重写onSaveInstanceState()时,应该始终调用超级类实现。

// Code from: https://developer.android.com/guide/components/activities/activity-lifecycle#save-simple,-lightweight-ui-state-using-onsaveinstancestate
override fun onSaveInstanceState(outState: Bundle?) {
    // Save the user's current game state
    outState?.run {
        putInt(STATE_SCORE, currentScore)
        putInt(STATE_LEVEL, currentLevel)
    }

    // Always call the superclass so it can save the view hierarchy state
    super.onSaveInstanceState(outState)
}

companion object {
    val STATE_SCORE = "playerScore"
    val STATE_LEVEL = "playerLevel"
}

onSavedInstanceState()需要在主线程上序列化,并消耗系统进程内存,因此不适合存储大量数据。如果需要保留更复杂的数据结构或更持久的数据结构,请考虑在活动处于前台时将数据保存在本地数据库或SharedPreferences中。

onRestoreInstanceState()

当您的活动在先前被销毁后重新创建时,您可以同时使用onCreate()和onRestoreInstanceState()回调方法来接收保存的实例状态。

对于onCreate()方法,在尝试读取状态束之前,必须检查它是否为null。如果为null,则系统将创建活动的新实例,而不是恢复先前已销毁的实例。

// Code from: https://developer.android.com/guide/components/activities/activity-lifecycle#save-simple,-lightweight-ui-state-using-onsaveinstancestate
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState) // Always call the superclass first

    // Check whether we're recreating a previously destroyed instance
    if (savedInstanceState != null) {
        with(savedInstanceState) {
            // Restore value of members from saved state
            currentScore = getInt(STATE_SCORE)
            currentLevel = getInt(STATE_LEVEL)
        }
    } else {
        // Probably initialize members with default values for a new instance
    }
    // ...
}

另一种恢复数据的方法是使用onRestoreInstanceState(),系统在onStart()方法之后调用它。只有在有要还原的已保存状态时,系统才会调用此方法,因此不需要进行空检查。

// Code from: https://developer.android.com/guide/components/activities/activity-lifecycle#save-simple,-lightweight-ui-state-using-onsaveinstancestate
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
    // Always call the superclass so it can restore the view hierarchy
    super.onRestoreInstanceState(savedInstanceState)

    // Restore state members from saved instance
    savedInstanceState?.run {
        currentScore = getInt(STATE_SCORE)
        currentLevel = getInt(STATE_LEVEL)
    }
}

在活动包含ViewModel的情况下,可以使用 保存状态句柄通过它的构造函数。SavedStateHandle对象是一个键值映射,允许您写入和检索数据。这些值在进程被系统终止后继续存在,并在同一对象中保持可用。

不要保留活动

“不要保留活动”是“开发人员选项”下的一个设置,它会在用户离开时立即销毁每个活动。此设置旨在模拟Android由于内存压力而在后台杀死一个活动的情况。所以这是一个非常方便的工具来调试你的应用程序。

举个例子:

这里我们有两个活动,第一个活动要求用户输入他们的名字,第二个活动执行一个简单的计数器。用户名将存储在对象类中,因为我们需要来自应用程序的其他部分。

// WelcomeActivity.kt
// static object to store user data
object UserInfo {
    var name: String = "Unknown"
}

class WelcomeActivity : AppCompatActivity() {

    private lateinit var binding: ActivityWelcomeBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityWelcomeBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)

        binding.confirmButton.setOnClickListener {
            UserInfo.name = binding.usernameInput.text.toString()
            val intent = Intent(this, MainActivity::class.java)
            startActivity(intent)
        }
    }
}
// MainActivity.kt
class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    private var count = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)
        setWelcomeText()
        setCounterText()
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        outState.putInt(STATE_COUNTER, count)
    }

    private fun setWelcomeText() {
        binding.welcomeText.text = "Hello ${UserInfo.name}"
    }

    private fun setCounterText() {
        binding.counter.text = count.toString()
    }
}

如您所见,我们目前没有任何机制来保存UI状态。如果在启用“不保留活动”设置时按“主页”按钮,活动将被销毁。再次打开应用程序时,计数器将重置为0。
所以我们知道设置的作用,让我们实现onSaveInstanceState()方法来保持状态并使我们的应用程序满足用户的期望。

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    private var count = 0

    companion object {
        private const val STATE_COUNTER = "state_counter"
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)

        if(savedInstanceState != null) {
            count = savedInstanceState.getInt(STATE_COUNTER)
        }

        setWelcomeText()
        setCounterText()
    }

   override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        outState.putInt(STATE_COUNTER, count)
    }
}

现在回到应用程序后,计数器已经成功恢复,一切正常,对吧?嗯,有一个陷阱。在内存压力下,系统不会直接终止活动来释放内存。相反,它会杀死运行活动的进程,不仅破坏活动,而且还会破坏进程中运行的所有其他内容。您可能已经看到了我们示例中的问题。我们将用户名存储在一个静态对象中,只要进程还活着,它就一直是活动的。请记住,“不要保留活动”只会破坏单个活动,而不是过程本身。但在现实生活中,整个过程都会被杀死,所以所有静态对象也都消失了。
现在,我们需要做的就是存储用户名,然后就可以开始了。

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    private var count = 0

    companion object {
        private const val STATE_COUNTER = "state_counter"
        private const val STATE_USER_NAME = "state_user_name"
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)

        if(savedInstanceState != null) {
            count = savedInstanceState.getInt(STATE_COUNTER)
            UserInfo.name = savedInstanceState.getString(STATE_USER_NAME, "")
        }

        setWelcomeText()
        setCounterText()
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        outState.putInt(STATE_COUNTER, count)
        outState.putString(STATE_USER_NAME, UserInfo.name)
    }
}

结论

保存和恢复活动的UI状态是用户体验的关键部分。有很多事情需要考虑,以确保你的应用程序运行得尽可能顺利。记住要在特殊的约束条件下测试你的应用程序,可能会出现一些你没有预料到的随机错误。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值