Android Jetpack 之 DataStore

1. 概述

Google 推出了 JetPack 的新成员 DataStore,DataStore 是一种新的数据存储方案。DataStore 以异步、一致的事务方式存储数据,克服了 SharedPreferences 的一些缺点。

Jetpack DataStore 是经过改进的新版数据存储解决方案,旨在取代 SharedPreferences。DataStore 基于 Kotlin 协程和流程构建而成,提供两种不同的实现:

  • Proto DataStore,它允许您存储类型化的对象(由协议缓冲区提供支持)
  • Preferences DataStore,用于存储键值对

以异步、一致的事务方式存储数据,克服了 SharedPreferences 的大部分缺点。

 Google 的目的很明确,就是打算用来取代 SharedPreferences,相信很快 SharedPreferences 就会变成 Deprecated 了。DataStore 提供了两种不同的实现:Preferences DataStore 和 Proto DataStore。DataStore 和 SharedPreferences 的数据存储方案对比如下所示:(链接,链接可能访问不了,多访问几次)

Feature

SharedPreferences

PreferencesDataStore

ProtoDataStore

Async API

✅ (only for reading changed values, via listener)

✅ (via Flow)

✅ (via Flow)

Synchronous API

✅ (but not safe to call on UI thread)

Safe to call on UI thread

❌*

✅ (work is moved to Dispatchers.IO under the hood)

✅ (work is moved to Dispatchers.IO under the hood)

Can signal errors

Safe from runtime exceptions

❌**

Has a transactional API with strong consistency guarantees

Handles data migration

✅ (from SharedPreferences)

✅ (from SharedPreferences)

Type safety

✅ with Protocol Buffers

2. DataStore 的导入

目前 DataStore 还处于 alpha 版本,要使用 DataStore 需要添加的依赖如下:

dependencies {
  // Preferences DataStore
  implementation "androidx.datastore:datastore-preferences:1.0.0-alpha02"

  // Proto DataStore
  implementation "androidx.datastore:datastore-core:1.0.0-alpha02"
}

3. DataStore 的使用

DataStore 分为 Preferences DataStore 和 Proto DataStore。

3.1 Preferences DataStore

Preferences DataStore 是由类 DataStore 和 Preferences 实现,用于存储简单的键值对到磁盘。

3.1.1 Preferences DataStore 的创建

    private val DATASTORE_PREFERENCE_NAME = "DataStorePreference"//定义 DataStore 的名字
    mDataStorePre = this.createDataStore(
           name = DATASTORE_PREFERENCE_NAME
    )

 createDataStore 是 Context 的一个扩展方法:

fun Context.createDataStore(
    name: String,
    corruptionHandler: ReplaceFileCorruptionHandler? = null,
    migrations: List> = listOf(),
    scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
): DataStore =
    PreferenceDataStoreFactory.create(
        produceFile = {
            File(this.filesDir, "datastore/$name.preferences_pb")
        },
        corruptionHandler = corruptionHandler,
        migrations = migrations,
        scope = scope
    )

3.1.2 数据的写入和读取

    private suspend fun savePreInfo(value: String) {
        var preKey = preferencesKey(PREFERENCE_KEY_NAME)
        mDataStorePre.edit { mutablePreferences ->
            mutablePreferences[preKey] = value
        }
    }

    private suspend fun readPreInfo(): String {
        var preKey = preferencesKey(PREFERENCE_KEY_NAME)
        var value = mDataStorePre.data.map { preferences ->
            preferences[preKey] ?: ""
        }
        return value.first()
    }

Preferences DataStore 以键值对的形式存储在本地,首先应该定义一个 Key:

 var preKey = preferencesKey(PREFERENCE_KEY_NAME)

key 的类型是 Preferences.Key,但是只支持 Int , String, Boolean , Float , Long 类型:

inline fun  preferencesKey(name: String): Preferences.Key {
    return when (T::class) {
        Int::class -> {
            Preferences.Key(name)
        }
        String::class -> {
            Preferences.Key(name)
        }
        Boolean::class -> {
            Preferences.Key(name)
        }
        Float::class -> {
            Preferences.Key(name)
        }
        Long::class -> {
            Preferences.Key(name)
        }
        Set::class -> {
            throw IllegalArgumentException("Use `preferencesSetKey` to create keys for Sets.")
        }
        else -> {
            throw IllegalArgumentException("Type not supported: ${T::class.java}")
        }
    }
}

Preferences DataStore 中是通过 DataStore.edit() 写入数据,edit 方法是个 suspend 函数,必须在协程中进行调用;通过 DataStore.data 去读取数据,返回的是一个 Flow

具体的使用例子如下:

            R.id.btn_pre_save -> {
                var textPre = edit_pre.text.trim().toString()
                lifecycleScope.launch {
                    savePreInfo(textPre)
                }
            }
            R.id.btn_pre_read -> {
                lifecycleScope.launch {
                    var value = readPreInfo()
                    Toast.makeText(this@MainActivity, value, Toast.LENGTH_SHORT).show()
                }

            }

3.1.3 从 SharedPreference 迁移数据

Google 推出 DataStore 的目的是为了取代 SharedPreference,对于老项目,就需要从 SharedPreference 中进行数据的迁移,从 SharedPreference 迁移到 DataStore。

3.1.1 中 createDataStore 方法的参数中有 migrations 参数:

migrations: List> = listOf()

只需要在 createDataStore 方法中按照如下格式就可以自动完成数据的迁移:

        mDataStorePre = this.createDataStore(
            name = DATASTORE_PREFERENCE_NAME,
            migrations = listOf(SharedPreferencesMigration(this, SP_PREFERENCE_NAME))
        )

3.2 Proto DataStore 

Proto DataStore 是通过 protocol buffers 将对象序列化存储在磁盘。Protocol buffers 是什么,在这之前我听都没听过,怎么办,学呗。

Android Jetpack 之 DataStore_第1张图片

Protocol buffers 的介绍可以参考:

https://developers.google.cn/protocol-buffers

在这里,只需要知道:

Protocol Buffers (ProtocolBuffer/ protobuf )是Google公司开发的一种数据描述语言,类似于XML能够将结构化数据序列化,可用于数据存储、通信协议等方面。同XML相比,Protocol buffers在序列化结构化数据方面有许多优点:

  • 更简单

  • 数据描述文件只需原来的1/10至1/3

  • 解析速度是原来的20倍至100倍

  • 减少了二义性

  • 生成了更容易在编程中使用的数据访问类

3.2.1 Proto DataStore 的创建

在创建 Proto DataStore 的时候,在 AndroidStudio 中,必须先做如下配置:

在 project 的 build.gradle 中添加依赖:

classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.8'

在 app 的 build.gradle 中,修改的比较多,所以整个文件都贴出来:

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'com.google.protobuf'

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.2"

    defaultConfig {
        applicationId "cn.zzw.datastore"
        minSdkVersion 21
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    buildFeatures {
        dataBinding true
    }
    kotlinOptions {
        jvmTarget = "1.8"
    }
    sourceSets {
        main {
            proto {
                srcDir 'src/main/proto'
                include '**/*.proto'
            }
        }
    }
}

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.3.2'
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
    // Preferences DataStore
    implementation "androidx.datastore:datastore-preferences:1.0.0-alpha02"
    // Proto DataStore
    implementation "androidx.datastore:datastore-core:1.0.0-alpha02"
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
    implementation "com.google.protobuf:protobuf-javalite:3.10.0"
}

protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:3.10.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'
                }
            }
        }
    }
}

接着,在目录 app/src/main/proto 创建文件 user_prefs.proto:

syntax = "proto3";

option java_package = "cn.zzw.datastore";
option java_multiple_files = true;

message UserPreferences {
      int32 id = 1;
      string name = 2;
      int32 age = 3;
      string phone = 4;
}

记得要执行 rebuild project 。

接着创建 UserPreferencesSerializer:

package cn.zzw.datastore

import androidx.datastore.Serializer
import java.io.InputStream
import java.io.OutputStream

object UserPreferencesSerializer : Serializer {
    override fun readFrom(input: InputStream): UserPreferences {
        return UserPreferences.parseFrom(input)
    }

    override fun writeTo(t: UserPreferences, output: OutputStream) = t.writeTo(output)

}

最后创建 Proto DataStore :

        mDataStorePro =
            this.createDataStore(
                fileName = "user_pros.pb",
                serializer = UserPreferencesSerializer
            )

3.2.2 数据的写入和读取

    private suspend fun saveProInfo(value: String) {
        mDataStorePro.updateData { preferences ->
            preferences.toBuilder().setId(110).setName(value).setAge(39).setPhone("119120").build()
        }
    }

    private suspend fun readProInfo(): String {+
        val userPreferencesFlow: Flow = mDataStorePro.data
        return userPreferencesFlow.first().toString()
    }

调用如下:

            R.id.btn_pro_save -> {
                var textPre = edit_pro.text.trim().toString()
                lifecycleScope.launch {
                    saveProInfo(textPre)
                }
            }
            R.id.btn_pro_read -> {
                lifecycleScope.launch {
                    var value = readProInfo()
                    Toast.makeText(this@MainActivity, value, Toast.LENGTH_SHORT).show()
                }
            }

4.总结

目前 DataStore 还处于 alpha 版本,等到正式版本出来后,还是要考虑下用它来替换 SharedPreference。DataStore 和 SharedPreference 一样适合用来存储小且简单的数据,如果是较多的数据,还是推荐用 Room。此篇只是记录了 DataStore 的基础用法,等后续正式版本出来后,再来研究下它的源码,看它是如何实现。

参考:

https://developer.android.google.cn/topic/libraries/architecture/datastore

https://codelabs.developers.google.com/codelabs/android-preferences-datastore#5

https://scalereal.com/android/2020/09/03/hello-datastore-bye-sharedpreferences-android.html

 

你可能感兴趣的:(Android,DataStore,Jetpack,数据持久化)