github编辑

保存键值对数据

原文:https://developer.android.com/training/data-storage/shared-preferencesarrow-up-right

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

circle-info

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

获取SharedPreferences

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

//在私有模式下打开该文件,确保只有您的应用可以访问该文件:
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()
circle-info

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

写入共享偏好设置

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

circle-info

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

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

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

从共享偏好设置中读取

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

获取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%。

参考

最后更新于