Article directory
0. Introduction
Since the launch of components, Google JetPack
has always recommended that we use DataStore
components to replace the components we knew on the first day of learning android SharedPreferences
. The reason is very simple, because SharedPreferences
most of the problems that existed back then DataStore
came to solve these problems.
1. Disadvantages of SP
As SP
for what problems exist, we can directly check DataStore
the comments on the source code:
- Synchronous API encourages StrictMode violations
- apply() and commit() have no mechanism of signalling errors
- apply() will block the UI thread on fsync()
- Not durable – it can returns state that is not yet persisted
- No consistency or transactional semantics
- Throws runtime exception on parsing errors
- Exposes mutable references to its internal state
To translate word for word in our broken English:
- Synchronous APIs encourage StrictMode violations
- The apply() and commit() methods have no error signaling mechanism
- The apply() method will block the UI thread when redrawing the interface
- Non-durable - it can return state, but not persist state
- no consistency or transaction semantics
- When there is an error in parsing, a runtime exception is thrown directly
- In its internal state, expose its mutable reference
Literal translations of questions written by foreigners are generally difficult to understand, unless the questions are very simple and clear. So let me say a few words, and roughly talk about the problems I think SP
exist :
- Cross-process is not supported, and using
MODE_MULTI_PROCESS
the pattern is useless. And in cross-process, frequent reading and writing may lead to data corruption or loss;- Reading files in lazy loading mode
SP
may cause getXXX() to block. So it is recommended to initialize asynchronously in advanceSP
;sp
All the data in the file is stored in memory, so it is not suitableSP
for children with large amounts of dataedit()
method creates a newEditorImpl
object every time. Suggest onceedit()
, many timesputXXX
;- Whether it is
commit()
orapply()
, it is written in full for any modification. In this case, the frequently modified configuration items are stored in separateSP
files;commit()
Synchronous save, with a return value;apply()
asynchronous save, no return value.onPause()
onReceive()
The asynchronous write operation is used in the method to complete the execution, which may cause freeze or ANR.
Of course, this is not to SP
say that it is useless. As the saying goes, existence is reasonable. When we do not involve cross-processes and the amount of stored data is relatively small, it SP
is still a pretty good choice.
2. Basic usage of DataStore
The first thing that needs to be declared is that DataStore
there are two versions, one is similar to SP
reading and writing based on ordinary files; the other is based on Google protobuf
patterns, here protobuf
is Google
a self-developed data structure, which is usually used more At least, I have written similar ones in my blog before, so here I will only introduce the first file-based logic:
The following introduces the basic usage DataStore
of :
First you need to import:
implementation("androidx.datastore:datastore:1.0.0")
First of all, we need to be clear, since ours DataStore
is compatible with the current use SP
, then it should support SP
the storage type, and we also know that SP
the supported data types are Int
, Long
, Float
, Boolean
, String
and StringSet
; at this time DataStore
, not only the above six data structures are supported, but also One additional Double
type is supported.
Create an DataStore
object:
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "dataStore_data")
First we need to read an Int
object of type:
val keyName = intPreferencesKey("example_counter")
val keyNameFlow: Flow<Int> = context.dataStore.data
.map {
preferences -> preferences[keyName] ?: 0
}
Then we write an object of Int
type :
val keyName = intPreferencesKey("example_counter")
suspend fun incrementCounter() {
context.dataStore.edit {
settings ->
val currentCounterValue = settings[keyName] ?: 0
settings[keyName] = currentCounterValue + 1
}
}
At first glance, it looks very confusing, what is written. That's right, I also thought the same way on the first day I DataStore
learned SP
. It's more than ten thousand times more fragrant than this thing. I don't know so many new things, and it feels awkward to write. Writing a simple way to store so many code.
First of all, when a new knowledge point comes out, everyone must resist it in their hearts, because it takes time and energy to understand and practice. But everyone is the same, so we still need to embrace changes.
First of all, let's follow the simple method. We all know that SP
it is a XML
file- based Key-Value
structure, so DataStore
as its compatible class, it must also be compatible with this Key-Value
structure. So is DataStore
it type? But it is not, it is a type, the specific type is: , can be divided into the following types:Key
String
Preferences.Key
androidx.datastore.preferences.core.Preferences.Key
- intPreferencesKey -> Preferences.Key<Int> Save
Int
type data- doublePreferencesKey -> Preferences.Key<Double> Save
Double
type data- stringPreferencesKey -> Preferences.Key<String> Save
String
type data- booleanPreferencesKey ->Preferences.Key<Boolean> Save
Boolean
type data- floatPreferencesKey -> Preferences.Key<Float> save
Float
type data- longPreferencesKey -> Preferences.Key<Long> save
Long
type data- stringSetPreferencesKey -> Preferences.Key<Set<String>> Save
Set<String>
type data
With Key
that, we need to see DataStore
how to store and read data.
a. How to write DataStore
SP
Yes Editor
, DataStore
there are edit
methods for the same reason:
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) }
}
}
First of all, it is a suspend
function that can only be run in the body of the coroutine. Whenever suspend
a function runs in a suspended mode, it will not block the main thread.
Since it is suspend
a function, we can write data in a summation way 同步
:异步
同步方式
:
private suspend fun saveSyncIntData(key : String, value:Int) {
globalDataStore.edit {
mutablePreferences -> mutablePreferences[intPreferencesKey(key)] = value }
}
异步方式
:
This is very simple, you can play it as you like, just add one to the synchronization method runBlocking
:
private fun saveIntData(key: String, value:Int) = runBlocking {
saveSyncIntData(key,value) }
b. How to read DataStore
According to the above convention, there must be two methods of 同步读取
and . 异步读取
First of all, it needs to be clear that DataStore
what is data
returned is Flow
a type, which Flow
is a stream interface, similar to inRxJava
, there are many operators that can transform data, and if time permits, you can write an article about it.Observable
Flow
First we get 同步的读
:
private fun readSyncIntData(key: String, defaultValue: Int) : Flow<Int> = dataStore.data.catch {
if(it is IOException) {
it.printStackTrace()
emit(emptyPreferences())
} else {
throw it
}
}.map {
it[intPreferencesKey(key)] ?: defaultValue }
Interpret the code, dataStore.data
the return type is Flow
the type, Flow
check catch
whether there is an exception, then map
convert it, then get Flow<Int>
, and finally return.
After writing 同步的读
, then 异步的读
:
private fun readIntData(key: String, defaultValue : Int) : Int {
var resultValue = defaultValue
runBlocking {
dataStore.data.first {
resultValue = it[intPreferencesKey(key)] ?: resultValue
true
}
}
return resultValue
}
The asynchronous read directly returns the specific type of data, and first
the operator here means to take the first one.
Basically, we DataStore
have a simple understanding of the operation, and the important thing is to practice it by ourselves, which is neither difficult nor easy.
3. Migrate from SP to DataStore
There are roughly two steps:
- Need one
SharedPreferencesMigration
, this migration class is not difficult, just need the file name you pass inContext
andSP
:
val Context.dataStore : DataStore<Preferences> by preferencesDataStore(name = "dataStore_setting",
produceMigrations = {
context ->
listOf( SharedPreferencesMigration(context, "sp_name"))
})
- When
dataStore
the generation is complete, a read or write operation needs to be performed,SharedPreferences
the data will be migrated todataStore
, andSharedPreferences
the file will also be deleted.
-
SP
folder used
-
Migrated to
DataStore
the folder
You can seeSP
that the file has been deleted, and thendataStore
the file directory is/data/data/package_name/files/xxx.preferences_pb
4. Encapsulation class of DataStore
For the convenience of operation, the logic encapsulated here DataStore
will be more convenient to read and write. The code of the method part is:
It is also very simple when we use it, the direct code is:
DataStoreUtils.putData("int_value",100)
DataStoreUtils.putData("long_value",100L)
DataStoreUtils.putData("float_value",100.0f)
DataStoreUtils.putData("double_value",100.00)
DataStoreUtils.putData("boolean_value",true)
DataStoreUtils.putData("string_value","hello world")
val intValue = DataStoreUtils.getData("int_value", 0)
val longValue = DataStoreUtils.getData("long_value", 0L)
val floatValue = DataStoreUtils.getData("float_value", 0.0f)
val doubleValue = DataStoreUtils.getData("double_value", 0.00)
val booleanValue = DataStoreUtils.getData("boolean_value", false)
val stringValue = DataStoreUtils.getData("string_value", "hello")
Of course, this is just an asynchronous way of reading/storing, of course we also have a synchronous way of getting:
lifecycle.coroutineScope.launch {
// 读取
DataStoreUtils.getSyncData("int_value",0).collect(object : FlowCollector<Int> {
override suspend fun emit(value: Int) {
Log.d("TAG","get sync data : $value")
}
})
// 写入
DataStoreUtils.putSyncData("int_value", 1)
}
Of course, you can see the specific source code here .
5. Personal conclusions
Generally speaking, DataStore
if it is highly encapsulated, there is basically no difference in the way of use SP
. It solves SP
the existing criticisms, but for now, its performance is still unknown. This may require subsequent online inspections. Of course, there should be no big problems with the things produced by Gu Daddy. Of course, there is still a very high threshold for learning . DataStore
Among Kotlin
them 协程
, there is still a rather steep learning gradient for related knowledge points such as .高阶函数
Flow