模拟系统内存不足时应用崩溃问题

问题

线上一直会报一个 Crash,通过日志发现用户的操作路径:

  1. 将一个展示结果的 Activity 退到了后台
  2. 然后当回到这个 Activity 时,发生了 NPE 错误导致 Crash

一开始,我预测是 Activity 被系统回收了,再次进入时数据未恢复造成了 Crash。于是我在开发者工具中将「保留Activity活动」设置为不保留。这样一旦应用被切到后台,系统立刻就会销毁这个 Activity ,但是这样之后,当用户再次进入时每次都相当于重启了 App,一直不能复现这个问题。

模拟复现

后来,经由同事提醒,我安装了一个可以一直向系统怼内存的 App。通过它我模拟了系统在内存不足情况下的表现。

fill_ram

  1. 首先将应用切到后台
  2. 然后打开怼内存的应用,一直向系统怼内存,直到怼不动为止。一般到内存占用 97% 左右,这个应用就怼不动了。
  3. 然后再切回应用,果然问题重现了

查看日志发现,是这个 Activity 引用了一个全局的对象,在系统内存不足时,Activity 被回收了,此时 Activity 上显示的 View 的状态都被系统保存,在 Activity 重新进入时被恢复;但是 Activity 中其他未被 View 引用的局部变量数据是不会被系统自动保存和恢复的,需要自己手动在 onSavaInstanceStateonRestoreInstanceState 中进行保存和恢复,否则在 Activity 被恢复之后,如果使用这些局部变量就会出现 NPE,导致崩溃。

局部变量的恢复问题解决了,但是还有一个全局对象的问题,全局对象作为单例存在整个应用中,而且它包含的数据对象较多,就不能通过简单的 saveInstanceState 保存。我们现在的方案是在这个全局对象构造时就会将它序列化写入到 SharedPreferences 中,但遇到有用到的 Activity 时,会在 Activity onCreate 之前从 SharedPreferences 中恢复。

阅读官方文档

如果一个后台 Activity 是由于系统的限制(比如配置发生改变,内存不足)被系统销毁了,尽管这个 Activity 实例已经不存在了,但是系统会记住这个 Activity,如果用户试图再次回到这个 Activity,
系统就会重新创建一个 Activity,并会使用一组在销毁 Activity 时保存的数据去恢复 UI 界面。这一组用来恢复的数据以 Bundle 的形式存储。默认情况下,系统会使用 Bundle 去储存 Activity 布局中的每个 View 对象的数据。因此如果你的 Activity 被销毁然后重建,布局的状态会恢复到之前的状态,不需要单独写代码。

但是如果想存储一些其他成员变量数据,需要自己单独写代码。

  • 储存简单的、轻量的数据可以使用 onSavaInstanceState
  • onSavaInstanceState 中的 Bundle 不适合储存大量数据,因为他会在 main Thread 进行序列化操作,并且消耗系统进程的内存。大量数据应该使用持久化存储做。

总结:

  • onSavaInstanceState 和 onRestoreInstanceState 可以在 Activity 被系统回收之后, 在重新创建时,恢复在 onSavaInstanceState 中储存的数据。

  • 已经设置在 View 上的数据,系统会自动进行保存和恢复。

  • 尽量不用全局数据,用了就要考虑到内存被回收之后,数据恢复的问题。

赏杯咖啡 🍵 Donate