프로토콜 버퍼는 구조화된 데이터를 직렬화하기 위한
Google의 언어 중립적, 플랫폼 중립적, 확장 가능한 메커니즘입니다.
데이터 구조화 방법을 한 번 정의한 다음 특수 생성 소스 코드를 사용하여 다양한 데이터 스트림과 다양한 언어를 사용하여 구조화된 데이터를 쉽게 쓰고 읽을 수 있습니다.
.proto
파일에 포로토콜 버퍼 메세지 타입을 정의하여 우리가 직렬화할 정보가 어떻게 구조화 될지를 기술합니다.
각 프로토콜 버퍼 메시지는 하나의 작은 정보 레코드로서 일련의 이름-값
쌍을 포함합니다.
message Person {
required string name = 1;
required int32 id = 2;
optional string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME];
}
repeated PhoneNumber phone = 4;
}
각 메시지 타입은 하나 이상의 유일한 숫자 변수를 갖으며 각 필드는 이름과 값을 갖습니다. 값의 유형으로는 숫자, 불리언, 로우 바이트, 위의 예제와 같이 다른 프로토콜 버퍼 메시지 타입이 될 수 있으며 우리는 우리의 대이터를 계층적으로 구조화 할 수 있습니다.선택적 필드나 필수 필드(required fields), 반복되는 필드들을 기술할 수 있습니다.
Proto Datastore를 사용하고 스키마용 코드 생성을 위해 Protobuf
를 가져오려면 다음과 같은 작업으로 build.gradle 파일을 조금 변경해야 합니다.
plugins {
...
id "com.google.protobuf" version "0.8.12"
}
dependencies {
implementation "androidx.datastore:datastore-core:1.0.0-alpha04"
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'
}
}
}
}
}
이를 만드는 작업은 두 단계로 이루어진다.
Searializer<T>
를 구현하는 클래스를 정의합니다. 여기서 T는 .proto
파일에 정의된 유형입니다. serializer 클래스는 데이터 유형을 읽고 쓰는 방법을 Datastore에 notify합니다.
datasStore
로 만든 delegate하여 DataStore<T>
의 인스턴스를 만듭니다. 여기서 T
는 .proto
파일에 정의된 유형입니다.
user_prefs.proto
파일을 새로 만듭니다. protobuf에서 각 구조는 message
키워드를 사용하여 정의됩니다. 구조의 각 요소는 유형과 이름에 따라 메시지 내에 정의되면 1부터 차례로 순서가 할당됩니다. 우선은 showCompleted
라는 부울 값이 있는 UserPreferences
메시지를 정의해보겠습니다.
syntax = "proto3";
option java_package = "com.example.application";
option java_multiple_files = true;
message Settings {
int32 example_counter = 1;
}
Settings
클래스는 proto 파일에 정의된message
로부터 컴파일 시간에 생성됩니다. 프로젝트를 다시 빌드해야 합니다.
proto 파일에 정의한 데이터 유형을 읽고 쓰는 방법을 Datastore에 알리려면 serializer를 구현해야 합니다. 또한 serializer는 디스크에 데이터가 없는 경우 반환될 기본값을 정의합니다. data
패키지에 SettingsSerializer
라는 새 파일을 만듭니다.
object SettingsSerializer : Serializer<Settings> {
override val defaultValue: Settings = Settings.getDefaultInstance()
override suspend fun readFrom(input: InputStream): Settings {
try {
return Settings.parseFrom(input)
} catch (exception: InvalidProtocolBufferException) {
throw CorruptionException("Cannot read proto.", exception)
}
}
override suspend fun writeTo(
t: Settings,
output: OutputStream) = t.writeTo(output)
}
UserPreferencesSerializer
val Context.settingsDataStore: DataStore<Settings> by dataStore(
fileName = "settings.pb",
serializer = SettingsSerializer
)
DataStore.data
를 사용하여 저장된 객체에서 적절한 속성의 Flow
를 expose
val exampleCounterFlow: Flow<Int> = context.settingsDataStore.data
.map { settings ->
// The exampleCounter property is generated from the proto schema.
settings.exampleCounter
}
트랜잭션 방식으로 업데이트하는 updataData()
함수를 제공합니다. 이 함수는 데이터의 현재 상태를 데이터 유형의 인스턴스를 제공하고 읽기-쓰기-수정 작업을 통해 트랜잭셕 방식으로 데이터를 업데이트합니다
suspend fun incrementCounter() {
context.settingsDataStore.updateData { currentSettings ->
currentSettings.toBuilder()
.setExampleCounter(currentSettings.exampleCounter + 1)
.build()
}
}
SharedPreferences는 UI 스레드에서 호출하기에 안전한 동기 API가 있고, 오류 신호를 보내는 메커니즘이 없고, 트랜잭션 API가 없다는 등 일련의 단점이 있습니다.
SharedPreferences를 대체하는 Datastore는 API의 거의 모든 단점을 해결합니다.
Datastore는 Kotlin 코루틴과 Flow를 사용하는 완전 비동기 API가 있으며, 데이터 이전을 처리하고, 데이터 일관성을 보장하고, 데이터 손상을 처리합니다.
더 자세한건 이 링크를 보길..
https://yalematta.dev/blog/proto-datastore.html
https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=sthwin&logNo=221020384012
https://developer.android.com/topic/libraries/architecture/datastore#proto-schema
https://developer.android.com/codelabs/android-proto-datastore#9