Google Jetpack 新出的DataStore用来代替SharePreferences的使用,DataStore有两种实现方式,一种是Preferences DataStore,一种是Proto DataStore,下面文章内容主要介绍第二种Proto DataStore的使用。
第一种Jetpack Preferences DataStore使用详解地址
Github的项目地址
一:添加依赖
在app的build.grale里添加如下依赖
implementation('androidx.datastore:datastore:1.0.0-alpha04') {
exclude group: 'org.jetbrains.kotlinx', module: 'kotlinx-coroutines-core'
exclude group: 'org.jetbrains.kotlinx', module: 'kotlinx-coroutines-core-jvm'
}
implementation('androidx.datastore:datastore-core:1.0.0-alpha04') {
exclude group: 'org.jetbrains.kotlinx', module: 'kotlinx-coroutines-core-jvm'
}
因为会报因为会报Duplicate class kotlinx.coroutines.AbstractCoroutine found in modules jetified-kotlinx-coroutines-core-jvm-1.3.9.jar
所以上面的 需要
exclude group: 'org.jetbrains.kotlinx', module: 'kotlinx-coroutines-core'
exclude group: 'org.jetbrains.kotlinx', module: 'kotlinx-coroutines-core-jvm'
二:下载插件,并添加proto需要的配置
要支持.proto结尾的文件,需要先下载插件,另外gradle需要是5.6以上,必须是java8。
配置插件参考地址1
配置插件参考地址2
1:在project的build.gradle里添加 classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.14',并添加上仓库mavenCentral()
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.14'
}
}
2:在app的build.gradle里添加 apply plugin: 'com.google.protobuf'
apply plugin: 'com.google.protobuf'
3: 在app的build.gradle里添加 proto的文件位置
// 设置 proto 文件位置
android{
sourceSets {
main {
proto {
// proto 文件默认路径是 src/main/proto
// 可以通过 srcDir 修改 proto 文件的位置
srcDir 'src/main/proto'
}
}
}
}
4: 使用3.8.0之后的配置方式,在app的build.gradle里添加如下代码
/**
* 注意配置 protoc 命令,分为不同的版本,版本不同配置的方式不同,
* 网上大部分都是 3.0.x ~ 3.7.x 的配置方式,关于这种配置方法,可以查看 [protobuf-gradle-plugin](https://github.com/google/protobuf-gradle-plugin) 文档,这里不在演示了,也不建议使用
* 在此项目中使用的是 3.8 以后 的配置的配置,
*/
protobuf {
// 设置 protoc 的版本
protoc {
// //从仓库下载 protoc 这里的版本号需要与依赖 com.google.protobuf:protobuf-javalite:xxx 版本相同
artifact = 'com.google.protobuf:protoc:3.10.0'
}
generateProtoTasks {
all().each { task ->
task.builtins {
java {
option "lite"
}
}
}
}
// 默认生成目录 $buildDir/generated/source/proto 通过 generatedFilesBaseDir 改变生成位置
// generatedFilesBaseDir = "$projectDir/src/main"
}
dependencies {
// You need to depend on the lite runtime library, not protobuf-java
implementation 'com.google.protobuf:protobuf-javalite:3.10.0'
}
三:定义一个协议 并在app/src/main/proto/ 文件夹下创建一个.proto的协议文件
在app/src/main/ 下新建一个proto文件夹。并且创建一个persons.proto的文件,文件内容如下:
syntax = "proto3";
option java_package = "com.example.jepcaktestapp.datastore.proto";
option java_outer_classname = "PersonProtos";
message Person {
// 格式:字段类型 + 字段名称 + 字段编号
int32 count = 1;
}
下面对文件的内容简单做个解释,后序会对proto3语法做个简单介绍:
- syntax 声明proto的版本,比如 这里syntax = "proto3";
- option java_package 定义生成的类的包名
- option java_outer_classname 定义生成的类的类名
- message 声明的是内部类, message里面的格式是 字段类型+字段名称+字段编号
- 类型以Java的对应关系:string->String, int32->int , int64->long, bool->Boolean,float->float,double->double
四:编译
执行Rebuild Project,每次 proto 文件内容变更的时候,都要重新点击 Build -> Rebuild Project 即可生成对应的 Java 文件
该Java 文件(上面proto文件我们定义的PersonProtos),该文件的
默认生成目录 app/build/generated/source/proto/包名/类名
可以通过 generatedFilesBaseDir 改变此文件的生成位置
五:使用
1:创建
// 指定名字
private val PROTO_NAME = "proto_datastore.pb"
var dataStore: DataStore<PersonProtos.Person> = context.createDataStore(
fileName = PROTO_NAME,
serializer = PersonSerializer
)
object PersonSerializer :Serializer<PersonProtos.Person>{
override val defaultValue: PersonProtos.Person
get() = PersonProtos.Person.getDefaultInstance()
override fun readFrom(input: InputStream): PersonProtos.Person {
try {
return PersonProtos.Person.parseFrom(input)
}catch (e:IOException){
throw CorruptionException("Cannot read proto.", e)
}
}
override fun writeTo(t: PersonProtos.Person, output: OutputStream) = t.writeTo(output)
}
2:存储
// 拿count属性来说
suspend fun putCount(count:Int) {
dataStore.updateData { it.toBuilder().setCount(count).build() }
}
3:获取
// 拿count属性来说
suspend fun getCount():Int = dataStore.data.map { it.count }.first()
4:关键代码
class ProtoDataStoreImpl(val context: Application){
// 指定名字
private val PROTO_NAME = "proto_datastore.pb"
var dataStore: DataStore<PersonProtos.Person> = context.createDataStore(
fileName = PROTO_NAME,
serializer = PersonSerializer
)
private val shardPrefsMigration = SharedPreferencesMigration<PersonProtos.Person>(context, SpUtils.SHARE_PREFERENCES_NAME) {
sharedPreferencesView, person ->
// 获取 SharedPreferences 的数据
val count = sharedPreferencesView.getInt(SpKeys.KEY_COUNT,0)
// 存储到dataStore中
person.toBuilder().setCount(count).setName(name).setFlag(flag).setPrice(price).setTime(time).build()
}
suspend fun putCount(count:Int) {
dataStore.updateData { it.toBuilder().setCount(count).build() }
}
suspend fun getCount():Int = dataStore.data.map { it.count }.first()
fun spToDataStore(){
dataStore = context.createDataStore(
fileName = PROTO_NAME,
serializer = PersonSerializer,
migrations = listOf(shardPrefsMigration)
)
}
}
object StoreFactory {
fun provideProtoDataStore(context: Application):ProtoDataStoreImpl{
return ProtoDataStoreImpl(context)
}
}
object PersonSerializer :Serializer<PersonProtos.Person>{
override val defaultValue: PersonProtos.Person
get() = PersonProtos.Person.getDefaultInstance()
override fun readFrom(input: InputStream): PersonProtos.Person {
try {
return PersonProtos.Person.parseFrom(input)
}catch (e:IOException){
throw CorruptionException("Cannot read proto.", e)
}
}
override fun writeTo(t: PersonProtos.Person, output: OutputStream) = t.writeTo(output)
}
class MainActivity : AppCompatActivity() ,View.OnClickListener ,CoroutineScope by MainScope(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tv_proto_data_store.setOnClickListener(this)
}
override fun onClick(v: View?) {
when(v){
tv_preferences_data_store->{
launch (Dispatchers.Main){
// 设置
StoreFactory.providePreferencesDataStore(application).putInt(PreferencesKeys.KEY_COUNT,1)
// 获取
val count = StoreFactory.providePreferencesDataStore(application).getInt(PreferencesKeys.KEY_COUNT)
}
}
}
}
override fun onDestroy() {
cancel()
super.onDestroy()
}
}
5:proto datastore生成的位置
我们可以使用adb shell
run-as 包名
ls
查看到files文件夹
proto datastore的文件就存放在files的datastore文件夹里
文件名字就是我们定义的proto_datastore.pb
六:SP转Proto DataStore
主要是获取Sp的值,在一一对应设置到Proto里,关键代码如下:
private val shardPrefsMigration = SharedPreferencesMigration<PersonProtos.Person>(context, SpUtils.SHARE_PREFERENCES_NAME) {
sharedPreferencesView, person ->
// 获取 SharedPreferences 的数据
val count = sharedPreferencesView.getInt(SpKeys.KEY_COUNT,0)
person.toBuilder().setCount(count).build()
}
var dataStore: DataStore<PersonProtos.Person> = context.createDataStore(
fileName = PROTO_NAME,
serializer = PersonSerializer,
migrations = listOf(shardPrefsMigration)
)
七:Pro DataStore跟 Preferences DataStore 有何区别?
- Preference DataStore 主要是为了解决 SharedPreferences 所带来的性能问题
- Proto DataStore 比 Preference DataStore 更加灵活,支持更多的类型
- Preference DataStore 支持 Int 、 Long 、 Boolean 、 Float 、 String,Double,Set
- protocol buffers 支持的类型,Proto DataStore 都支持
- Preference DataStore 以 XML 的形式存储 key-value 数据,可读性很好
- Proto DataStore 使用了二进制编码压缩,体积更小,速度比 XML 更快
八:proto3语法简介
例子,定义一个文件如下:
syntax = "proto3";
option java_package = "com.example.jepcaktestapp.datastore.proto";
option java_outer_classname = "PersonProtos";
message Teacher{
string grade = 1;
}
message Person {
// 格式:字段类型 + 字段名称 + 字段编号
string name = 1;
int32 count = 2;
bool flag = 3;
float price = 4;
int64 time = 5;
double money = 6;
Teacher teacher = 7;
enum Weekday{
SUN = 0;
MON = 1;
TUE = 2;
WED = 3;
THU = 4;
FRI = 5;
SAT = 6;
}
Weekday weekday = 8;
repeated string course = 9;
}
- syntax :指定 protobuf 的版本,如果没有指定默认使用 proto2
- option :表示一个可选字段
- java_package 是 指定生成 java 类所在的包名
- java_outer_classname : 指定生成 java 类的名字
- 在一个 proto 文件中,可以定义多个 message 如果messageA里有messageB,表示B是A的内部类
- message里内容格式 字段类型 + 字段名称 + 字段编号,比如 string name = 1; string是类型,name是名称,等号右边1表示编号
字段类型
proto type | java type | 默认值 |
---|---|---|
double | double | 0 |
float | float | 0 |
int32 | int | 0 |
int64 | long | 0 |
bool | boolean | false |
string | String | "" |
repeated | List | 空集合 |
也可以用其他的消息类型,作为类型,例子如下:
message Person {
// 格式:字段类型 + 字段名称 + 字段编号
Teacher teacher =1;
}
message Teacher{
......
}
一个proto文件里面可以有多个message,message之间也可以嵌套。例如:
message Person {
// 格式:字段类型 + 字段名称 + 字段编号
message Teacher{
string name=1;
}
Teacher teacher = 1;
}
这些message会被编译成静态内部类。
message下也可以用枚举,而且枚举的第一个值必须是0。(是为了兼容proto2的语法)
message Person {
enum Weekday{
SUN = 0;
MON = 1;
TUE = 2;
WED = 3;
THU = 4;
FRI = 5;
SAT = 6;
}
Weekday weekday = 1;
}