前言
DataStore是google官方提供的,用于替換SharedPreferences來對簡單資料進行存儲的解決方案。對于SharedPreferences存在的缺陷
進行修補,并且可以和Kotlin協程與Flow結合。
DataStore與SharedPreferences 功能對比
DataStore 功能
DataStore提供對基本類型和對象類型進行分開存儲,分别使用 PreferencesDataStore,ProtoDataStore.通過名字很容區分 PreferencesDataStore用來存儲基本類型資料,String、int、long、double、float、boolean、set< String >。
Proto DataStore 使用協定緩沖區來定義架構。使用協定緩沖區可持久保留強類型資料。與 XML 和其他類似的資料格式相比,協定緩沖區速度更快、規格更小、使用更簡單,并且更清楚明了。雖然使用 Proto DataStore 需要學習新的序列化機制,但 Proto DataStore 有着強大的類型優勢,值得學習。
并且提供了友善的遷移接口,對于基礎類型存儲可以通過配置自動實作遷移。
PreferencesDataStore 配置依賴
dependencies {
//preferencesDatastore功能依賴
implementation "androidx.datastore:datastore-preferences:1.0.0"
//protoDatastore功能依賴
implementation "androidx.datastore:datastore-core:1.0.0"
implementation "com.google.protobuf:protobuf-javalite:3.18.0"
implementation "androidx.datastore:datastore:1.0.0"
}
PreferencesDataStore 初始化
//user_preferences 生成的檔案名稱 user_preferences.preferences_pb
private const val USER_PREFERENCES_NAME = "user_preferences"
private val Context.dataStore by preferencesDataStore(USER_PREFERENCES_NAME)
PreferencesDataStore 讀取寫入:需要放在協程中進行讀寫
private suspend fun spStoreSave() {
dataStore.edit { preferences ->
preferences[stringPreferencesKey("one1")] = "中華人民共和國"
preferences[intPreferencesKey("one2")] = 1
preferences[doublePreferencesKey("one3")] = 1.00
preferences[floatPreferencesKey("one4")] = 1f
preferences[longPreferencesKey("one5")] = 1L
preferences[booleanPreferencesKey("one6")] = true
preferences[stringSetPreferencesKey("one7")] = setOf("1", "2", "3", "4", "5", "6")
}
}
讀取
private suspend fun getSpStore() {
val flow = dataStore.data.catch { exception ->
// dataStore.data throws an IOException when an error is encountered when reading data
if (exception is IOException) {
Log.e(TAG, "Error reading preferences.", exception)
emit(emptyPreferences())
} else {
throw exception
}
}.map { preferences ->
println(preferences[stringPreferencesKey("one1")])
println(preferences[intPreferencesKey("one2")])
println(preferences[doublePreferencesKey("one3")])
println(preferences[floatPreferencesKey("one4")])
println(preferences[longPreferencesKey("one5")])
println(preferences[booleanPreferencesKey("one6")])
preferences[stringSetPreferencesKey("one7")]
}
flow.collect()
}
生成的檔案内部結構 user_preferences.preferences_pb
PreferencesDataStore 與 SharedPreferences對比:大小、内部結構
SharedPreferences寫入相同的資料後進行對比
private fun saveUserInfo() {
val userInfo = getSharedPreferences(SETTING, MODE_PRIVATE)
val editor = userInfo.edit() //擷取Editor
editor.apply {
putString("one1", "中華人民共和國")
putInt("one2", 1)
putFloat("one3",1.00f)
putFloat("one4",1f)
putLong("one5",1L)
putBoolean("one6",true)
putStringSet("one7", setOf("1", "2", "3", "4", "5", "6"))
}
editor.commit() //送出修改
}
檔案内部結構對比。左邊為SharedPreferences存儲,右邊為PreferencesDataStore 存儲。
相同寫入相同内容,SharedPreferences比PreferencesDataStore存儲大小多了3倍。
SharedPreferences遷移PreferencesDataStore
需要指定要遷入的 PreferencesDataStore 檔案名稱和要遷出的檔案SharedPreferences檔案名稱,如下:
//SharedPreferences 舊存儲結構SharedPreferences
private const val SETTING = "setting"
//要遷入的新存儲結構 PreferencesDataStore
private const val USER_PREFERENCES_NAME = "user_preferences"
private val Context.dataStore by preferencesDataStore(
name = USER_PREFERENCES_NAME,
produceMigrations = { context ->
// Since we're migrating from SharedPreferences, add a migration based on the
// SharedPreferences name
listOf(SharedPreferencesMigration(context, SETTING))
}
)
重新編譯運作項目後檢查 裝置路徑:data/data/packageName/files/datastore/ xx.preferences_pb檔案,如果檔案中有SharedPreferences内容,表示自動遷移完成了。
Proto DataStore
Proto DataStore 是用來存取可序列化對象,SharedPreferences 不能直接對對象進行存取,開發中 一般做法會将對象轉換成jsonString進行存儲,需要取出時再由取出來的jsonStrinng轉換成需要的Bean檔案。如果使用Proto DataStore存取那麼可以直接取出定義的Bean對象。
1.添加protoBuffer 模闆代碼生成插件 和 Proto DataStore 依賴項
在項目app子產品 build.gradle 檔案中添加自動生成模闆代碼的插件和必要的依賴項
plugins {
id 'com.android.application'
id 'kotlin-android'
id "com.google.protobuf" version "0.8.17"
}
//protobuf閉包 與 android閉包同一級
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:3.14.0"
}
// Generates the java Protobuf-lite code for the Protobufs in this project. See
// https://github.com/google/protobuf-gradle-plugin#customizing-protobuf-compilation
// for more information.
generateProtoTasks {
all().each { task ->
task.builtins {
java {
option 'lite'
}
}
}
}
}
dependencies {
//protocol cache
implementation "androidx.datastore:datastore-core:1.0.0"
implementation "com.google.protobuf:protobuf-javalite:3.18.0"
implementation("androidx.datastore:datastore:1.0.0")
}
2.定義protobuf 對象并實作序列化(需要額外學習成本)
需要在**/app/src/main/proto**在建立一個字尾為.proto的 protoBuffer檔案,比如這裡建立一個名為
person.proto的檔案。 之後需要手動進行編譯,可以通過檢查app/build/generrated/source/proto/debug/java/包名/下
syntax = "proto3";
// 預設生成路徑 ${project.buildDir}/generated/source/proto
option java_package = "com.codelab.android.datastore";
option java_multiple_files = true;
message Person {
//學号
int32 id = 1;
//姓名
string name = 2;
//年齡
int32 age = 3;
//班級
string class = 4;
}
建立一個序列化器,都是一樣的寫法,照着模闆寫就行
//建立序列化器
object PersonPreferencesSerializer : Serializer<Person> {
override val defaultValue: Person = Person.getDefaultInstance()
override suspend fun readFrom(input: InputStream): Person {
try {
return Person.parseFrom(input)
} catch (e: InvalidProtocolBufferException) {
throw CorruptionException("Cannot read proto.", e)
}
}
override suspend fun writeTo(t: Person, output: OutputStream) {
t.writeTo(output)
}
}
3.存取 protobuf 對象,并輸出
//存
lifecycleScope.launch {
persion.updateData { preferenceChanges ->
preferenceChanges.toBuilder()
.setId(10000)
.setAge(1)
.setName("小明")
.setClass_("一年級")
.build()
}
}
//取
lifecycleScope.launch {
persion.data.catch { exception ->
// dataStore.data throws an IOException when an error is encountered when reading data
if (exception is IOException) {
Log.e(TAG, "Error reading sort order preferences.", exception)
emit(Person.getDefaultInstance())
} else {
throw exception
}
}.map {
with(it) {
println("class =$class_ name =$name age =$age id =$id")
}
}.collect()
}
proto DataStore 遷移
SharedPreferences 檔案遷移到ProtoDataStore,需要手動取出來SharedPreferences記憶體儲值,通過事務Build設定給Proto對象。需要注意的一點是每一個定義的proto 對象為一個單獨的檔案。如果SharedPreferences 記憶體儲值需要轉存入Proto對象中。需要在如下代碼Listof(SharedPreferencesMigration(),SharedPreferencesMigration(), …)定義多個接收SharedPreferences值的對象。
private val Context.userPreferencesStore: DataStore<UserPreferences> by dataStore(fileName = 對象.pb檔案名稱,
serializer = UserPreferencesSerializer,
produceMigrations = { context ->
listOf(SharedPreferencesMigration(
context, Sp檔案名稱
) { sharedPreferences: SharedPreferencesView, currentData: UserPreferences ->
if (currentData.sortOrder == SortOrder.UNSPECIFIED) {
currentData.toBuilder().setSortOrder(
SortOrder.valueOf(
sharedPreferences.getString(
SORT_ORDER_KEY, SortOrder.NONE.name
)!!
)
).build()
} else {
currentData
}
})
})