Welcome to jetpack DataStore, an improved new data storage solution designed to replace the original SharedPreferences. Jetpack DataStore is developed based on Kotlin collaboration and Flow, and provides two different implementations: proto DataStore and preferences DataStore. Proto DataStore can store objects with types (implemented with protocol buffers); Preferences DataStore, which can store key value pairs. In DataStore, data is stored in an asynchronous, consistent and transactional manner, which overcomes most of the shortcomings of shared preferences.
-
protocol buffers
https://developers.google.cn/protocol-buffers
SharedPreferences vs. DataStore
-
SharedPreferences has a synchronization API that looks like it can be called thread safe in the UI, but it actually performs disk I/O operations. In addition, the apply() method blocks the UI thread at fsync(). Anywhere in your application, whenever a Service or Activity starts or stops, a call waiting for fsync() will be triggered. The fsync() call process scheduled by apply() blocks the UI thread, which is often the source of ANR** SharedPreferences throws runtime exceptions when parsing errors.
-
ANRhttps://developer.android.google.cn/topic/performance/vitals/anr
In both implementations, unless otherwise specified, the DataStore stores preferences in a file, and all data operations are performed in dispatchers Execute on Io.
Although both Preferences DataStore and Proto DataStore can store data, their implementation methods are different:
-
Preference DataStore, like SharedPreferences, cannot define a schema or guarantee access to key values of the correct type.
-
Proto DataStore allows you to define a schema using Protocol buffers. Using Protobufs, you can preserve strongly typed data. They are faster, smaller, and less ambiguous than XML or other similar data formats. Although Proto DataStore requires you to learn a new serialization mechanism, considering the advantages of strongly typed schema brought by Proto DataStore, we think the cost is worth it.
-
Protocol buffershttps://developers.google.cn/protocol-buffers
Comparison between Room and DataStore
If you have the need to locally update data, reference integrity, or support large and complex data sets, you should consider using Room Not DataStore. DataStore is an ideal choice for small, simple datasets. It does not support local updates and referential integrity.
Using DataStore
First add the DataStore dependency. If you are using Proto DataStore, make sure you have also added proto dependencies:
-
proto dependency
https://github.com/google/protobuf-gradle-plugin
def dataStoreVersion = "1.0.0-alpha05" // Confirm the latest version number on the Android Developer website // https://developer.android.google.cn/jetpack/androidx/releases/datastore // Preferences DataStore implementation "androidx.datastore:datastore-preferences:$dataStoreVersion" // Proto DataStore implementation "androidx.datastore:datastore-core:$dataStoreVersion"
When you use Proto DataStore, you need to use the proto file to define your own schema in the app/src/main/proto / directory. For more information on defining proto schema s, see the protobuf language guide.
- protobuf Language Guide
https://developers.google.cn/protocol-buffers/docs/proto3
syntax = "proto3"; option java_package = "<your package name here>"; option java_multiple_files = true; message Settings { int my_counter = 1; }
Create DataStore
You can use context Createdatastore() extension method creates DataStore:
// Create Preferences DataStore val dataStore: DataStore<Preferences> = context.createDataStore( name = "settings" )
If you are using Proto DataStore, you also need to implement the Serializer interface to tell DataStore how to read and write your data types.
object SettingsSerializer : Serializer<Settings> { override fun readFrom(input: InputStream): Settings { try { return Settings.parseFrom(input) } catch (exception: InvalidProtocolBufferException) { throw CorruptionException("Cannot read proto.", exception) } } override fun writeTo(t: Settings, output: OutputStream) = t.writeTo(output) } // Create Proto DataStore val settingsDataStore: DataStore<Settings> = context.createDataStore( fileName = "settings.pb", serializer = SettingsSerializer )
Read data from DataStore
Whether it is the Preferences object or the object defined in the proto schema, the DataStore will expose the stored data in the form of Flow. DataStore can ensure that it is in dispatchers Retrieve data on Io, so your UI thread will not be blocked.
-
Dispatchers.IO
https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-i-o.html
Using Preferences DataStore:
val MY_COUNTER = preferencesKey<Int>("my_counter") val myCounterFlow: Flow<Int> = dataStore.data .map { currentPreferences -> // Unlike Proto DataStore, type safety is not guaranteed here. currentPreferences[MY_COUNTER] ?: 0 }
Using Proto DataStore:
val myCounterFlow: Flow<Int> = settingsDataStore.data .map { settings -> // The myCounter attribute is generated by your proto schema! settings.myCounter }
Write data to DataStore
In order to write data, DataStore provides a DataStore updateData() suspends the function, which will provide you with the status of the currently stored data as a parameter, both for the Preferences object or the object instance you defined in the proto schema. The updatedata () function uses atomic read, write, modify operations and updates the data in a transactional manner. This process is completed when the data is stored on disk.
Preferences DataStore also provides a datastore Edit () function to facilitate data update. In this function, you will receive a MutablePreferences object for editing instead of the preferences object. This function, like updateData(), applies the changes to the disk after the conversion code block is completed, and this process will be completed when the data is stored on the disk.
Using Preferences DataStore:
suspend fun incrementCounter() { dataStore.edit { settings -> // It can safely increase our counters without losing data due to resource competition. val currentCounterValue = settings[MY_COUNTER] ?: 0 settings[MY_COUNTER] = currentCounterValue + 1 } }
Using Proto DataStore:
suspend fun incrementCounter() { settingsDataStore.updateData { currentSettings -> // It can safely increase our counters without losing data due to resource competition. currentSettings.toBuilder() .setMyCounter(currentSettings.myCounter + 1) .build() } }
**Migrating from SharedPreferences to DataStore**
To migrate from SharedPreferences to DataStore, you need to pass the SharedPreferences migration object to the DataStore constructor. DataStore can automatically complete the migration from SharedPreferences to DataStore. The migration runs before any data access occurs in the DataStore, which means that in the DataStore Data returns any value and DataStore Before updatedata() can update data, your migration must have been successful.
If you want to migrate to the Preferences DataStore, you can use the default implementation of SharedPreferences migration. Just pass in the name used in the SharedPreferences construct.
Using Preferences DataStore:
val dataStore: DataStore<Preferences> = context.createDataStore( name = "settings", migrations = listOf(SharedPreferencesMigration(context, "settings_preferences")) )
When migrating to Proto DataStore, you must implement a mapping function to define how to migrate the key value pairs used by SharedPreferences to the DataStore schema you defined.
Using Proto DataStore:
val settingsDataStore: DataStore<Settings> = context.createDataStore( produceFile = { File(context.filesDir, "settings.preferences_pb") }, serializer = SettingsSerializer, migrations = listOf( SharedPreferencesMigration( context, "settings_preferences" ) { sharedPrefs: SharedPreferencesView, currentData: UserPreferences -> // Here, map sharedPrefs to your type. } ) )
summary
SharedPreferences has many defects: it seems that the synchronization API that can be safely called in the UI thread is not safe, there is no error prompt mechanism, there is a lack of transaction API, and so on. DataStore is an alternative to Shared Preferences, which solves most of the problems of Shared Preferences. DataStore contains a fully asynchronous API implemented using Kotlin collaboration and Flow, which can handle data migration, ensure data consistency, and handle data corruption.
Advanced notes of Android advanced development system and the latest interview review notes PDF, please scan the csdn official QR code below for free
end of document
Your favorite collection is my greatest encouragement!
Welcome to follow me, share Android dry goods and exchange Android technology.
If you have any opinions on the article or any technical problems, please leave a message in the comment area for discussion!