保存键值对数据

原文:https://developer.android.com/training/data-storage/shared-preferences

如果您有想要保存的相对较小键值对集合,则应使用 SharedPreferences API。SharedPreferences 对象指向包含键值对的文件,并提供读写这些键值对的简单方法。每个 SharedPreferences 文件均由框架进行管理,可以是私有文件,也可以是共享文件。

注意SharedPreferences API 用于读写键值对,不要将它们与 Preference API 混淆,后者可帮助您构建用于显示应用设置的界面(虽然它们也使用 SharedPreferences 保存用户设置)。如需了解 Preference API,请参阅设置开发者指南

获取SharedPreferences

您可以通过调用以下方法之一创建新的共享偏好设置文件或访问已有共享偏好设置文件:

  • getSharedPreferences() - 如果您需要多个由名称(使用第一个参数指定)标识的共享偏好设置文件,则使用此方法。您可以从您的应用中的任何 Context 调用此方法。

  • getPreferences() - 如果您只需使用 Activity 的一个共享首选项,请从 Activity 中使用此方法。由于这会检索属于该 Activity 的默认共享偏好设置文件,因此您无需提供名称。

  • 如果您使用 SharedPreferences API 保存应用设置,则应改用PreferenceManagergetDefaultSharedPreferences() 方法获取整个应用的默认共享偏好设置文件。如需了解详情,请参阅设置开发者指南

//在私有模式下打开该文件,确保只有您的应用可以访问该文件:
val sharedPreferences1 = getSharedPreferences("file1", Context.MODE_PRIVATE)
sharedPreferences1.edit().putInt("value1",1).apply()
val sharedPreferences2 = getPreferences(Context.MODE_PRIVATE)
sharedPreferences2.edit().putInt("value2",2).apply()
val sharedPreferences3 = PreferenceManager.getDefaultSharedPreferences(this)
sharedPreferences3.edit().putInt("value3",3).apply()

注意:自 API 级别 17 起,MODE_WORLD_READABLEMODE_WORLD_WRITEABLE 模式已被弃用。 从 Android 7.0(API 级别 24)开始,如果您使用这些模式,Android 会抛出 SecurityException。如果您的应用需要与其他应用共享私有文件,可以通过 FLAG_GRANT_READ_URI_PERMISSION 使用 FileProvider。如需了解详情,另请参阅共享文件

写入共享偏好设置

如需写入共享偏好设置文件,请通过对您的 SharedPreferences 调用 edit() 以创建一个 SharedPreferences.Editor

注意:通过对 EncryptedSharedPreferences 对象(而不是 SharedPreferences 对象)调用 edit() 方法,您可以更安全地修改共享偏好设置。如需了解详情,请参阅有关如何更安全地处理数据的指南。

传递您想要使用 putInt()putString() 等方法写入的键和值。然后,调用 apply()commit() 以保存更改。例如:

apply() 会立即更改内存中的 SharedPreferences 对象,但会将更新异步写入磁盘。或者,您也可以使用 commit() 将数据同步写入磁盘。但是,由于 commit() 是同步的,您应避免从主线程调用它,因为它可能会暂停您的界面呈现

从共享偏好设置中读取

如需从共享偏好设置文件中检索值,请调用 getInt()getString() 等方法,为您想要的值提供键;如果键不存在,则可以选择返回默认值。例如:

获取SharedPreferences源码分析

getSharedPreferences(String name, int mode)

以上两个方法本质上都是调用的ContextgetSharedPreferences方法。

getSharedPreferencesPath

getSharedPreferences(File file, int mode)

getSharedPreferencesCacheLocked

SharedPreferencesImpl分析

SharedPreferencesImpl构造函数

startLoadFromDisk

loadFromDisk

获取数据分析

EditorImpl分析

commit()

commitToMemory

enqueueDiskWrite

writeToFile

apply()

applycommit的对比

  • apply没有返回值, commit有返回值能知道修改是否提交成功

  • apply是将修改提交到内存,再异步提交到磁盘文件; commit是同步的提交到磁盘文件;

  • 多并发的提交commit时,需等待正在处理的commit数据更新到磁盘文件后才会继续往下执行,从而降低效率; 而apply只是原子更新到内存,后调用apply函数会直接覆盖前面内存数据,从一定程度上提高很多效率。

获取SP与Editor:

  • getSharedPreferences()是从ContextImpl.sSharedPrefsCache唯一的SPI对象;

  • edit()每次都是创建新的EditorImpl对象.

缺点

虽然 SharedPreferences 使用非常简便,但也是我们诟病比较多的存储方法。它的性能问题比较多,我可以轻松地说出它的“七宗罪”。

  • 跨进程不安全。由于没有使用跨进程的锁,就算使用MODE_MULTI_PROCESS,SharedPreferences 在跨进程频繁读写有可能导致数据全部丢失。根据线上统计,SP 大约会有万分之一的损坏率。

  • 加载缓慢。SharedPreferences 文件的加载使用了异步线程,而且加载线程并没有设置线程优先级,如果这个时候主线程读取数据就需要等待文件加载线程的结束。这就导致出现主线程等待低优先级线程锁的问题,比如一个 100KB 的 SP 文件读取等待时间大约需要 50~100ms,我建议提前用异步线程预加载启动过程用到的 SP 文件。

  • 全量写入。无论是调用 commit() 还是 apply(),即使我们只改动其中的一个条目,都会把整个内容全部写到文件。而且即使我们多次写入同一个文件,SP 也没有将多次修改合并为一次,这也是性能差的重要原因之一。

  • 卡顿。由于提供了异步落盘的 apply 机制,在崩溃或者其他一些异常情况可能会导致数据丢失。所以当应用收到系统广播,或者被调用 onPause 等一些时机,系统会强制把所有的 SharedPreferences 对象数据落地到磁盘。如果没有落地完成,这时候主线程会被一直阻塞。这样非常容易造成卡顿,甚至是 ANR,从线上数据来看 SP 卡顿占比一般会超过 5%。

参考

最后更新于