DataStore 是一種數(shù)據(jù)存儲(chǔ)解決方案,使用協(xié)議緩沖區(qū)存儲(chǔ)鍵值對(duì)或類(lèi)型化對(duì)象骑歹。DataStore 使用 Kotlin 協(xié)程和 Flow 以異步预烙、一致的事務(wù)方式存儲(chǔ)數(shù)據(jù)。
如果您當(dāng)前在使用SharedPreferences 存儲(chǔ)數(shù)據(jù)道媚,請(qǐng)考慮遷移到 DataStore扁掸。
Preferences DataStore 和 Proto DataStore
- Preferences DataStore 使用鍵存儲(chǔ)和訪(fǎng)問(wèn)數(shù)據(jù)。此實(shí)現(xiàn)不需要預(yù)定義的架構(gòu)最域,也不確保類(lèi)型安全谴分。
- Proto DataStore 將數(shù)據(jù)作為自定義數(shù)據(jù)類(lèi)型的實(shí)例進(jìn)行存儲(chǔ)。此實(shí)現(xiàn)要求您使用協(xié)議緩沖區(qū)來(lái)定義架構(gòu)羡宙,但可以確保類(lèi)型安全狸剃。
基本使用
引入
implementation("androidx.datastore:datastore:1.0.0")
implementation("androidx.datastore:datastore-preferences:1.0.0")
Preferences DataStore 的使用
(1)創(chuàng)建datasource實(shí)例
val dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
datasouce會(huì)將數(shù)據(jù)保存在內(nèi)部存儲(chǔ)的以下目錄
[圖片上傳失敗...(image-caad2-1638167023752)]
其中,settings為上邊name中設(shè)置的值狗热,后綴名是preferences_pb文件
注意,在實(shí)際的開(kāi)發(fā)中虑省,建議將上述的dataStore設(shè)置為單例模式匿刮。
(2)寫(xiě)數(shù)據(jù)
datastore 使用edit方法以異步的方式保存數(shù)據(jù)。
val EXAMPLE_COUNTER = intPreferencesKey("example_counter")
dataStore.edit { settings ->
val currentCounterValue = settings[EXAMPLE_COUNTER] ?: 0
settings[EXAMPLE_COUNTER] = currentCounterValue + 1
}
其中EXAMPLE_COUNTER 為key值探颈,并且必須是一個(gè)Preferences.Key熟丸,datastore目前支持以下幾種類(lèi)型的key:
intPreferencesKey
doublePreferencesKey
stringPreferencesKey
booleanPreferencesKey
floatPreferencesKey
longPreferencesKey
(3)讀數(shù)據(jù)
datastore以flow的方式觀(guān)察數(shù)據(jù)的變化。
val exampleCounterFlow: Flow<Int> = dataStore.data
.map { preferences ->
preferences[EXAMPLE_COUNTER] ?: 0
}
GlobalScope.launch {
exampleCounterFlow.collectLatest {
Log.i(TAG, "read value:$it")
}
}
Proto DataStore 的使用
Proto DataStore 用于保存實(shí)例對(duì)象伪节,使用之前需要先了解Proto協(xié)議及在A(yíng)ndroid下的基本使用方式光羞,這里不再贅述。
Proto DataStore 的使用和 Preferences DataStore基本類(lèi)似怀大,在寫(xiě)入數(shù)據(jù)時(shí)使用updateData提交數(shù)據(jù)
dataStore.updateData { settings ->
settings.toBuilder()
.setExampleCounter(settings.exampleCounter + 1)
.setId(currentSettings.id + 1)
.build()
}
讀取方式和Preferences DataStore一樣這里也不再贅述纱兑。
使用方式就先介紹到這里,下面來(lái)分析下DataStore的源碼
源碼分析
我們以Preferences DataStore 的讀寫(xiě)為例化借,Proto DataStore的過(guò)程類(lèi)似Preferences DataStore潜慎。
先從datastore的實(shí)例化入手
val dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
dataStore初始化以委托的方式調(diào)用 preferencesDataStore 函數(shù) 返回一個(gè) DataStore<Preferences>實(shí)例
我們來(lái)看preferencesDataStore的實(shí)現(xiàn)
public fun preferencesDataStore(
name: String,
corruptionHandler: ReplaceFileCorruptionHandler<Preferences>? = null,
produceMigrations: (Context) -> List<DataMigration<Preferences>> = { listOf() },
scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
): ReadOnlyProperty<Context, DataStore<Preferences>> {
return PreferenceDataStoreSingletonDelegate(name, corruptionHandler, produceMigrations, scope)
}
preferencesDataStore 函數(shù)中,
name 上邊解釋過(guò)蓖康,就是保存的文件的名字
corruptionHandler 中包含了讀寫(xiě)失敗時(shí)的錯(cuò)誤铐炫。
produceMigrations 用于數(shù)據(jù)遷移,當(dāng)我們需要從sp中將數(shù)據(jù)遷移到datastore時(shí)需要此參數(shù)蒜焊。
scope 指定了線(xiàn)程調(diào)度器倒信,默認(rèn)是在IO線(xiàn)程中。
preferencesDataStore 函數(shù)返回了一個(gè)PreferenceDataStoreSingletonDelegate的實(shí)例泳梆,我們來(lái)看PreferenceDataStoreSingletonDelegate的具體實(shí)現(xiàn)
internal class PreferenceDataStoreSingletonDelegate internal constructor(
private val name: String,
private val corruptionHandler: ReplaceFileCorruptionHandler<Preferences>?,
private val produceMigrations: (Context) -> List<DataMigration<Preferences>>,
private val scope: CoroutineScope
) : ReadOnlyProperty<Context, DataStore<Preferences>> {
private val lock = Any()
@GuardedBy("lock")
@Volatile
private var INSTANCE: DataStore<Preferences>? = null
/**
* Gets the instance of the DataStore.
*
* @param thisRef must be an instance of [Context]
* @param property not used
*/
override fun getValue(thisRef: Context, property: KProperty<*>): DataStore<Preferences> {
return INSTANCE ?: synchronized(lock) {
if (INSTANCE == null) {
val applicationContext = thisRef.applicationContext
INSTANCE = PreferenceDataStoreFactory.create(
corruptionHandler = corruptionHandler,
migrations = produceMigrations(applicationContext),
scope = scope
) {
applicationContext.preferencesDataStoreFile(name)
}
}
INSTANCE!!
}
}
}
代碼很容易理解鳖悠,PreferenceDataStoreSingletonDelegate 實(shí)現(xiàn)了 ReadOnlyProperty 接口榜掌,在ReadOnlyProperty接口中 有一個(gè)重載的getValue方法
public fun interface ReadOnlyProperty<in T, out V> {
public operator fun getValue(thisRef: T, property: KProperty<*>): V
}
PreferenceDataStoreSingletonDelegate 中g(shù)etValue方法返回了一個(gè)DataStore<Preferences>的單例對(duì)象。
INSTANCE通過(guò) PreferenceDataStoreFactory.create 函數(shù)創(chuàng)建竞穷,我們先來(lái)看這句
applicationContext.preferencesDataStoreFile(name)
preferencesDataStoreFile 是context的一個(gè)擴(kuò)展唐责,需要我們前邊傳入的name參數(shù),猜測(cè)這里是設(shè)置保存文件路徑瘾带。
public fun Context.preferencesDataStoreFile(name: String): File =
this.dataStoreFile("$name.preferences_pb")
public fun Context.dataStoreFile(fileName: String): File =
File(applicationContext.filesDir, "datastore/$fileName")
preferencesDataStoreFile 進(jìn)一步調(diào)用了dataStoreFile函數(shù)鼠哥,dataStoreFile函數(shù)中設(shè)置了保存文件的具體路徑,
到這里我們知道了看政,DataSource是將數(shù)據(jù)保存在了
data/data/包名/files/datastore/xxx.preferences_pb
文件中朴恳。我們回到PreferenceDataStoreSingletonDelegate類(lèi)中,繼續(xù)看 INSTANCE的創(chuàng)建過(guò)程允蚣。
PreferenceDataStoreFactory.kt
public fun create(
corruptionHandler: ReplaceFileCorruptionHandler<Preferences>? = null,
migrations: List<DataMigration<Preferences>> = listOf(),
scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()),
produceFile: () -> File
): DataStore<Preferences> {
val delegate = DataStoreFactory.create(
serializer = PreferencesSerializer,
corruptionHandler = corruptionHandler,
migrations = migrations,
scope = scope
) {
val file = produceFile()
check(file.extension == PreferencesSerializer.fileExtension) {
"File extension for file: $file does not match required extension for" +
" Preferences file: ${PreferencesSerializer.fileExtension}"
}
file
}
return PreferenceDataStore(delegate)
}
}
PreferenceDataStoreFactory的create方法沒(méi)有太多邏輯于颖,先是繼續(xù)調(diào)用DataStoreFactory.create方法,返回一個(gè)DataStore<T>的代理嚷兔,同時(shí)森渐,檢查創(chuàng)建的文件名稱(chēng)合法性。最后返回一個(gè)PreferenceDataStore實(shí)例冒晰。
我們先看DataStoreFactory.create方法
public fun <T> create(
serializer: Serializer<T>,
corruptionHandler: ReplaceFileCorruptionHandler<T>? = null,
migrations: List<DataMigration<T>> = listOf(),
scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()),
produceFile: () -> File
): DataStore<T> =
SingleProcessDataStore(
produceFile = produceFile,
serializer = serializer,
corruptionHandler = corruptionHandler ?: NoOpCorruptionHandler(),
initTasksList = listOf(DataMigrationInitializer.getInitializer(migrations)),
scope = scope
)
}
我們先看DataStoreFactory 從名稱(chēng)上看像是一個(gè)工廠(chǎng)類(lèi)同衣,其實(shí)他只是個(gè)單例對(duì)象,create方法直接返回了一個(gè)
SingleProcessDataStore的實(shí)例壶运。
SingleProcessDataStore是最后真正的datastore實(shí)例化的類(lèi)DataStore的讀寫(xiě)關(guān)鍵邏輯也是在這里實(shí)現(xiàn)耐齐。
到這里,datastore的實(shí)例化基本分析完畢蒋情,下面來(lái)看數(shù)據(jù)的讀寫(xiě)過(guò)程埠况。
寫(xiě)
datastore通過(guò)edit方式實(shí)現(xiàn)數(shù)據(jù)的更新
dataStore.edit { settings ->
val currentCounterValue = settings[EXAMPLE_COUNTER] ?: 0
settings[EXAMPLE_COUNTER] = currentCounterValue + 1
}
在edit函數(shù)的實(shí)現(xiàn)中,直接調(diào)用了datastore的updateData函數(shù)棵癣,
public suspend fun DataStore<Preferences>.edit(
transform: suspend (MutablePreferences) -> Unit
): Preferences {
return this.updateData {
// It's safe to return MutablePreferences since we freeze it in
// PreferencesDataStore.updateData()
it.toMutablePreferences().apply { transform(this) }
}
}
在上面datastore的實(shí)例化時(shí)我們知道辕翰,datastore對(duì)象實(shí)際上是一個(gè)SingleProcessDataStore的實(shí)例化對(duì)象,那我們直接看SingleProcessDataStore 中的updateData方法:
override suspend fun updateData(transform: suspend (t: T) -> T): T {
val ack = CompletableDeferred<T>()
val currentDownStreamFlowState = downstreamFlow.value
val updateMsg =
Message.Update(transform, ack, currentDownStreamFlowState, coroutineContext)
actor.offer(updateMsg)
return ack.await()
}
updateData 函數(shù)利用了協(xié)程中的處理并發(fā)時(shí)的 Actors解決方案浙巫,如果你對(duì)Actors不了解可以看下這里https://www.kotlincn.net/docs/reference/coroutines/shared-mutable-state-and-concurrency.html 金蜀,總之在這里只要記住這是為了處理寫(xiě)同步的操作即可。
actor.offer(updateMsg) 這句將任務(wù)提交到actor中的畴,updateMsg 中攜帶了數(shù)據(jù)及狀態(tài)信息渊抄,我們來(lái)看actor中實(shí)現(xiàn)
private val actor = SimpleActor<Message<T>>(
scope = scope,
onComplete = {
it?.let {
downstreamFlow.value = Final(it)
}
// We expect it to always be non-null but we will leave the alternative as a no-op
// just in case.
synchronized(activeFilesLock) {
activeFiles.remove(file.absolutePath)
}
},
onUndeliveredElement = { msg, ex ->
if (msg is Message.Update) {
// TODO(rohitsat): should we instead use scope.ensureActive() to get the original
// cancellation cause? Should we instead have something like
// UndeliveredElementException?
msg.ack.completeExceptionally(
ex ?: CancellationException(
"DataStore scope was cancelled before updateData could complete"
)
)
}
}
) { msg ->
when (msg) {
is Message.Read -> {
handleRead(msg)
}
is Message.Update -> {
handleUpdate(msg)
}
}
}
actor 根據(jù)消息類(lèi)型,進(jìn)行讀寫(xiě)操作丧裁,由于上一步我們傳遞的是Message.Update類(lèi)型护桦,因此會(huì)調(diào)用handleUpdate(msg)函數(shù),我們繼續(xù)進(jìn)入handleUpdate(msg)函數(shù)中
private suspend fun handleUpdate(update: Message.Update<T>) {
// All branches of this *must* complete ack either successfully or exceptionally.
// We must *not* throw an exception, just propagate it to the ack.
update.ack.completeWith(
runCatching {
when (val currentState = downstreamFlow.value) {
is Data -> {
// We are already initialized, we just need to perform the update
transformAndWrite(update.transform, update.callerContext)
}
is ReadException, is UnInitialized -> {
if (currentState === update.lastState) {
// we need to try to read again
readAndInitOrPropagateAndThrowFailure()
// We've successfully read, now we need to perform the update
transformAndWrite(update.transform, update.callerContext)
} else {
// Someone else beat us to read but also failed. We just need to
// signal the writer that is waiting on ack.
// This cast is safe because we can't be in the UnInitialized
// state if the state has changed.
throw (currentState as ReadException).readException
}
}
is Final -> throw currentState.finalException // won't happen
}
}
)
}
我們先看主線(xiàn)邏輯煎娇,在handleUpdate中 根據(jù)狀態(tài)類(lèi)型進(jìn)一步跳轉(zhuǎn)二庵,如果我們上來(lái)就執(zhí)行寫(xiě)操作贪染,downstreamFlow.value的初始狀態(tài)是UnInitialized ,那么會(huì)執(zhí)行這里的邏輯
if (currentState === update.lastState) {
// we need to try to read again
readAndInitOrPropagateAndThrowFailure()
// We've successfully read, now we need to perform the update
transformAndWrite(update.transform, update.callerContext)
}
我們看在進(jìn)一步看transformAndWrite 方法
private suspend fun transformAndWrite(
transform: suspend (t: T) -> T,
callerContext: CoroutineContext
): T {
// value is not null or an exception because we must have the value set by now so this cast
// is safe.
val curDataAndHash = downstreamFlow.value as Data<T>
curDataAndHash.checkHashCode()
val curData = curDataAndHash.value
val newData = withContext(callerContext) { transform(curData) }
// Check that curData has not changed...
curDataAndHash.checkHashCode()
return if (curData == newData) {
curData
} else {
writeData(newData)
downstreamFlow.value = Data(newData, newData.hashCode())
newData
}
}
關(guān)鍵邏輯在這里
return if (curData == newData) {
curData
} else {
writeData(newData)
downstreamFlow.value = Data(newData, newData.hashCode())
newData
}
如果要更新的值和當(dāng)前值相等催享,就不再繼續(xù)執(zhí)行杭隙,否則,執(zhí)行writeData 然后更新downstreamFlow.value的狀態(tài)為Data因妙,繼續(xù)看writeData 函數(shù)
internal suspend fun writeData(newData: T) {
file.createParentDirectories()
val scratchFile = File(file.absolutePath + SCRATCH_SUFFIX)
try {
FileOutputStream(scratchFile).use { stream ->
serializer.writeTo(newData, UncloseableOutputStream(stream))
stream.fd.sync()
// TODO(b/151635324): fsync the directory, otherwise a badly timed crash could
// result in reverting to a previous state.
}
if (!scratchFile.renameTo(file)) {
throw IOException(
"Unable to rename $scratchFile." +
"This likely means that there are multiple instances of DataStore " +
"for this file. Ensure that you are only creating a single instance of " +
"datastore for this file."
)
}
} catch (ex: IOException) {
if (scratchFile.exists()) {
scratchFile.delete() // Swallow failure to delete
}
throw ex
}
}
這里真正執(zhí)行了數(shù)據(jù)的保存操作痰憎,而且是先將數(shù)據(jù)寫(xiě)到了一個(gè).tmp的臨時(shí)文件中,然后調(diào)用
scratchFile.renameTo(file)
將scratchFile文件重命名為file文件攀涵。到這里數(shù)據(jù)就保存成功了铣耘。
這里有兩個(gè)地方需要注意下
stream.fd.sync()
(1)這句代碼是通過(guò)文件描述符刷新數(shù)據(jù),執(zhí)行這句之后以故,內(nèi)存中的數(shù)據(jù)會(huì)立即同步到文件中蜗细,這是linux的機(jī)制,知道即可怒详。
scratchFile.renameTo(file)
(2)renameTo方法是將文件重命名炉媒,測(cè)試時(shí)發(fā)現(xiàn)在A(yíng)ndroid平臺(tái)下不論目標(biāo)文件是否存在,均會(huì)執(zhí)行成功昆烁,除非scratchFile 不存在橱野,這可能和Java中的不一致,具體還需要看下renameTo的源碼善玫。
讀
我們?cè)賮?lái)看下數(shù)據(jù)的讀取過(guò)程。
在上面的示例中我們知道 讀的過(guò)程就是觀(guān)察dataStore.data流的過(guò)程密强,我們繼續(xù)查看SingleProcessDataStore相關(guān)代碼
override val data: Flow<T> = flow {
val currentDownStreamFlowState = downstreamFlow.value
if (currentDownStreamFlowState !is Data) {
// We need to send a read request because we don't have data yet.
actor.offer(Message.Read(currentDownStreamFlowState))
}
emitAll(
downstreamFlow.dropWhile {
if (currentDownStreamFlowState is Data<T> ||
currentDownStreamFlowState is Final<T>
) {
false
} else {
it === currentDownStreamFlowState
}
}.map {
when (it) {
is ReadException<T> -> throw it.readException
is Final<T> -> throw it.finalException
is Data<T> -> it.value
is UnInitialized -> error(
"This is a bug in DataStore. Please file a bug at: " +
"https://issuetracker.google.com/issues/new?" +
"component=907884&template=1466542"
)
}
}
)
}
如果讀之前沒(méi)有寫(xiě)操作或者第一次會(huì)先執(zhí)行
if (currentDownStreamFlowState !is Data) {
// We need to send a read request because we don't have data yet.
actor.offer(Message.Read(currentDownStreamFlowState))
}
downstreamFlow.value的初始值為*UnInitialized
但是如果之前有過(guò)寫(xiě)操作茅郎,就可以直接從緩存中讀取最新值,因?yàn)樵趯?xiě)完時(shí)downstreamFlow.value中保存了最新值
private suspend fun transformAndWrite(
transform: suspend (t: T) -> T,
callerContext: CoroutineContext
): T {
...
return if (curData == newData) {
curData
} else {
writeData(newData)
downstreamFlow.value = Data(newData, newData.hashCode())
...
}
}
我們來(lái)看下actor中執(zhí)行讀的相關(guān)操作或渤,直接查看handlRead函數(shù)
private suspend fun handleRead(read: Message.Read<T>) {
when (val currentState = downstreamFlow.value) {
...
UnInitialized -> {
readAndInitOrPropagateFailure()
}
...
}
}
在進(jìn)入readAndInitOrPropagateFailure函數(shù)中
private suspend fun readAndInitOrPropagateFailure() {
try {
readAndInit()
} catch (throwable: Throwable) {
downstreamFlow.value = ReadException(throwable)
}
}
在readAndInit繼續(xù)調(diào)用 readDataOrHandleCorruption執(zhí)行讀操作
var initData = readDataOrHandleCorruption()
private suspend fun readDataOrHandleCorruption(): T {
try {
return readData()
} catch (ex: CorruptionException) {
...
}
}
最后在readData中執(zhí)行了真正的讀操作系冗。
private suspend fun readData(): T {
try {
FileInputStream(file).use { stream ->
return serializer.readFrom(stream)
}
} catch (ex: FileNotFoundException) {
if (file.exists()) {
throw ex
}
return serializer.defaultValue
}
}
在獲取到數(shù)據(jù)后downstreamFlow.value變?yōu)镈ata
private suspend fun readAndInit() {
...
downstreamFlow.value = Data(initData, initData.hashCode())
}
最后通過(guò)emitAll發(fā)射出去
emitAll(
downstreamFlow.dropWhile {
...
}.map {
when (it) {
...
is Data<T> -> it.value
...
}
}
)
以上就是 DataStore讀寫(xiě)的主要流程,涉及到的其他細(xì)節(jié)由于篇幅原因這里就不展開(kāi)了薪鹦,感興趣的小伙伴們可以自己閱讀掌敬。