Android应用存储全局常用对象选SharedPreferences还是伴生静态对象更好?
问题
我知道这是一个比较常见的问题,但我想了解存储对象时,是使用SharedPreferences(JSON序列化方式)更好,还是存为Activity的伴生对象(companion object)静态变量更好?这个对象几乎会在所有Activity和Fragment中使用。
例如我有一个User对象。
第一种方案实现如下:
val mUser = User(x,x,x...) val mUserJson = Gson().toJson(mUser) mSharedPreferences .edit() .putString(USER_DATA, mUserJson) .apply()
第二种方案是在首Activity中声明该对象,再在其他Activity中初始化:
class FirstActivity : AppCompatActivity() { companion object { lateinit var mUser: User } }
其他Activity中赋值逻辑:
FirstActivity.mUser = User(x,x,x...)
我不清楚第二种方案是否存在效率等问题,而第一种方案需要在每个用到User对象的Activity中初始化SharedPreferences,请问这个开销是否过大?另外如果要修改User对象的属性,我需要重新将对象转成JSON写入偏好设置,流程如下:
val mUserPref = mSharedPreferences.getString(USER_DATA, "") .run { Gson().fromJson(this, User::class.java) } mUserPref.variable = new_value mSharedPreferences .edit() .putString(USER_DATA, Gson().toJson(mUserPref)) .apply()
请问哪一种方案更好,原因是什么?谢谢。
回答
两种方案各有适用场景,但单独使用任何一种都存在明显缺陷,实际开发中通常会结合两者的优势做封装。
先分别说下两种方案的问题:
伴生对象静态存储方案的问题
- 数据丢失风险极高:静态变量的生命周期等同于APP进程生命周期,一旦APP切后台被系统回收、或者用户手动杀进程重启,变量会被清空。如果你的代码里用了
lateinit,访问未初始化的变量会直接抛出崩溃。 - 场景兼容性差:如果有外部跳转入口(比如点击推送、桌面快捷方式直接进入非首页的Activity),首Activity没有被启动的情况下,伴生对象的变量根本没完成初始化,100%会触发崩溃。
- 数据一致性无保障:多进程场景下静态变量是进程独占的,不同进程读取到的数值完全不同;而且全局可修改的
var没有读写管控,很容易出现脏数据,出问题后很难排查。 - 额外的内存泄漏风险:如果User对象持有Context、View等短生命周期的引用,静态变量会长期持有这些引用,导致内存泄漏。
这个方案唯一的优势是读写效率极高,都是内存操作,但稳定性缺陷完全盖过了效率优势,绝对不建议单独使用。
SharedPreferences序列化存储方案的问题
- 你担心的每次初始化SharedPreferences的开销可以忽略,SharedPreferences本身是单例实现,首次加载后会在内存中缓存全量数据,后续获取实例的开销极低。
- 但频繁读写的场景下,每次读都要做JSON反序列化、每次写都要做JSON序列化+文件写入,会产生不必要的性能开销,频繁调用
apply还有概率触发ANR(Android系统的部分生命周期回调会等待所有SP的异步写入任务完成,待写入任务过多会卡住主进程)。 - 没有类型安全校验,一旦JSON格式损坏,读取时会直接抛出解析异常。
最优实现方案
把内存缓存和持久化结合,做一层统一的读写管控即可兼顾效率和稳定性:
- 用独立的单例类持有User的内存缓存,不要和Activity绑定,所有读操作直接走内存,性能拉满。
- APP启动时(在Application的
onCreate中)一次性从SharedPreferences读取数据反序列化到内存,不需要每次使用都读SP。 - 所有写操作同时更新内存缓存和持久化层,既保证数据一致性,又避免进程被杀后数据丢失。
- 所有读写逻辑都封装在单例类内部,避免外部随意修改产生脏数据。
参考实现代码:
object UserManager { private var cachedUser: User? = null private lateinit var sp: SharedPreferences private const val KEY_USER_DATA = "user_data" // 只需在Application初始化时调用一次 fun init(context: Context) { sp = context.getSharedPreferences("app_config", Context.MODE_PRIVATE) // 启动时一次性加载持久化数据到内存 cachedUser = runCatching { Gson().fromJson(sp.getString(KEY_USER_DATA, ""), User::class.java) }.getOrNull() } // 对外暴露读方法 fun getUser(): User? = cachedUser // 对外暴露全量更新方法 fun updateUser(newUser: User) { cachedUser = newUser // 异步写入持久化层 sp.edit() .putString(KEY_USER_DATA, Gson().toJson(newUser)) .apply() } // 对外暴露单字段更新方法,按需扩展 fun updateVariable(newValue: String) { cachedUser?.let { user -> user.variable = newValue updateUser(user) } } }
如果你的项目有频繁修改用户属性、或者多进程读写的需求,也可以把SharedPreferences替换为MMKV,性能和稳定性会更好。
内容的提问来源于stack exchange,提问作者ladytoky0
相关产品推荐
相关产品推荐

