โปรเจ็กต์: /architecture/_project.yaml หนังสือ: /architecture/_book.yaml คำหลัก: datastore, architecture, api:JetpackDataStore คำอธิบาย: สำรวจคู่มือสถาปัตยกรรมแอปนี้ในไลบรารีเลเยอร์ข้อมูลเพื่อดูข้อมูลเกี่ยวกับ Preferences DataStore และ Proto DataStore, การตั้งค่า และอื่นๆ hide_page_heading: true
DataStore ส่วนหนึ่งของ Android Jetpack
Jetpack DataStore เป็นโซลูชันพื้นที่เก็บข้อมูลที่ช่วยให้คุณจัดเก็บคู่คีย์-ค่าหรือออบเจ็กต์ที่มีการพิมพ์ด้วยบัฟเฟอร์โปรโตคอลได้ DataStore ใช้โครูทีนและ Flow ของ Kotlin เพื่อจัดเก็บข้อมูลแบบไม่พร้อมกัน อย่างสม่ำเสมอ และแบบทรานแซ็กชัน
หากใช้ SharedPreferences
เพื่อจัดเก็บข้อมูล ให้พิจารณาย้ายข้อมูลไปยัง DataStore แทน
DataStore ของค่ากำหนดและ Proto DataStore
DataStore มีการใช้งาน 2 แบบ ได้แก่ Preferences DataStore และ Proto DataStore
- DataStore ของค่ากําหนดจะจัดเก็บและเข้าถึงข้อมูลโดยใช้คีย์ การ ติดตั้งใช้งานนี้ไม่จำเป็นต้องมีสคีมาที่กำหนดไว้ล่วงหน้า และไม่มี การรับประกันความปลอดภัยของประเภท
- Proto DataStore จัดเก็บข้อมูลเป็นอินสแตนซ์ของประเภทข้อมูลที่กำหนดเอง การใช้งานนี้กำหนดให้คุณต้องกำหนดสคีมาโดยใช้บัฟเฟอร์โปรโตคอล แต่จะให้ความปลอดภัยของประเภท
ใช้ DataStore อย่างถูกต้อง
โปรดคำนึงถึงกฎต่อไปนี้เสมอเพื่อให้ใช้ DataStore ได้อย่างถูกต้อง
อย่าสร้างอินสแตนซ์ของ
DataStore
มากกว่า 1 รายการสำหรับไฟล์ที่กำหนดใน กระบวนการเดียวกัน การทำเช่นนี้อาจทำให้ฟังก์ชันการทำงานทั้งหมดของ DataStore หยุดทำงาน หากมี DataStore หลายรายการที่ใช้งานอยู่สำหรับไฟล์หนึ่งๆ ในกระบวนการเดียวกัน DataStore จะแสดงข้อผิดพลาดIllegalStateException
เมื่ออ่านหรืออัปเดตข้อมูลประเภททั่วไปของ
DataStore<T>
ต้องเปลี่ยนแปลงไม่ได้ การเปลี่ยนแปลงประเภท ที่ใช้ใน DataStore จะทำให้ความสอดคล้องที่ DataStore มอบให้ไม่ถูกต้อง และอาจทำให้เกิดข้อบกพร่องที่ร้ายแรงและจับได้ยาก เราขอแนะนำให้คุณใช้ บัฟเฟอร์โปรโตคอล ซึ่งจะช่วยให้มั่นใจได้ถึงความคงที่ API ที่ชัดเจน และการ ซีเรียลไลซ์ที่มีประสิทธิภาพอย่าใช้
SingleProcessDataStore
และMultiProcessDataStore
ร่วมกันสำหรับไฟล์เดียวกัน หากต้องการเข้าถึงDataStore
จากกระบวนการมากกว่า 1 รายการ คุณต้องใช้MultiProcessDataStore
ตั้งค่า
หากต้องการใช้ Jetpack DataStore ในแอป ให้เพิ่มข้อมูลต่อไปนี้ลงในไฟล์ Gradle โดยขึ้นอยู่กับการติดตั้งใช้งานที่คุณต้องการใช้
Preferences DataStore
Groovy
// Preferences DataStore (SharedPreferences like APIs) dependencies { implementation "androidx.datastore:datastore-preferences:1.1.7" // optional - RxJava2 support implementation "androidx.datastore:datastore-preferences-rxjava2:1.1.7" // optional - RxJava3 support implementation "androidx.datastore:datastore-preferences-rxjava3:1.1.7" } // Alternatively - use the following artifact without an Android dependency. dependencies { implementation "androidx.datastore:datastore-preferences-core:1.1.7" }
Kotlin
// Preferences DataStore (SharedPreferences like APIs) dependencies { implementation("androidx.datastore:datastore-preferences:1.1.7") // optional - RxJava2 support implementation("androidx.datastore:datastore-preferences-rxjava2:1.1.7") // optional - RxJava3 support implementation("androidx.datastore:datastore-preferences-rxjava3:1.1.7") } // Alternatively - use the following artifact without an Android dependency. dependencies { implementation("androidx.datastore:datastore-preferences-core:1.1.7") }
Proto DataStore
Groovy
// Typed DataStore (Typed API surface, such as Proto) dependencies { implementation "androidx.datastore:datastore:1.1.7" // optional - RxJava2 support implementation "androidx.datastore:datastore-rxjava2:1.1.7" // optional - RxJava3 support implementation "androidx.datastore:datastore-rxjava3:1.1.7" } // Alternatively - use the following artifact without an Android dependency. dependencies { implementation "androidx.datastore:datastore-core:1.1.7" }
Kotlin
// Typed DataStore (Typed API surface, such as Proto) dependencies { implementation("androidx.datastore:datastore:1.1.7") // optional - RxJava2 support implementation("androidx.datastore:datastore-rxjava2:1.1.7") // optional - RxJava3 support implementation("androidx.datastore:datastore-rxjava3:1.1.7") } // Alternatively - use the following artifact without an Android dependency. dependencies { implementation("androidx.datastore:datastore-core:1.1.7") }
จัดเก็บคู่คีย์-ค่าด้วย Preferences DataStore
การใช้งาน DataStore ของค่ากําหนดจะใช้คลาส DataStore
และ
Preferences
เพื่อบันทึกคู่คีย์-ค่าลงในดิสก์
สร้าง Preferences DataStore
ใช้ตัวแทนพร็อพเพอร์ตี้ที่สร้างโดย preferencesDataStore
เพื่อสร้างอินสแตนซ์ของ DataStore<Preferences>
เรียกใช้ครั้งเดียวที่ระดับบนสุดของไฟล์ Kotlin และเข้าถึงผ่านพร็อพเพอร์ตี้นี้ตลอดทั้งแอปพลิเคชัน ซึ่งจะช่วยให้คุณเก็บ DataStore
ไว้เป็น Singleton ได้ง่ายขึ้น
หรือใช้ RxPreferenceDataStoreBuilder
หากคุณใช้ RxJava
พารามิเตอร์ name
ที่จำเป็นคือชื่อของ Preferences DataStore
Kotlin
// At the top level of your kotlin file:
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
Java
RxDataStore<Preferences> dataStore =
new RxPreferenceDataStoreBuilder(context, "settings").build();
อ่านจาก DataStore ของค่ากำหนด
เนื่องจาก Preferences DataStore ไม่ได้ใช้สคีมาที่กำหนดไว้ล่วงหน้า คุณจึงต้องใช้ฟังก์ชันประเภทคีย์ที่เกี่ยวข้องเพื่อกำหนดคีย์สำหรับแต่ละค่าที่ต้องการจัดเก็บในอินสแตนซ์ DataStore<Preferences>
เช่น หากต้องการกำหนดคีย์
สำหรับค่า int ให้ใช้ intPreferencesKey()
จากนั้นใช้พร็อพเพอร์ตี้
DataStore.data
เพื่อแสดงค่าที่จัดเก็บที่เหมาะสมโดยใช้
Flow
Kotlin
val EXAMPLE_COUNTER = intPreferencesKey("example_counter")
val exampleCounterFlow: Flow<Int> =
context.dataStore.data.map { preferences ->
// No type safety.
preferences[EXAMPLE_COUNTER] ?: 0
}
Java
Preferences.Key<Integer> EXAMPLE_COUNTER =
PreferencesKeys.int("example_counter");
Flowable<Integer> exampleCounterFlow =
dataStore.data().map(prefs -> prefs.get(EXAMPLE_COUNTER));
เขียนไปยัง Preferences DataStore
DataStore ของค่ากําหนดมีฟังก์ชัน edit()
ที่อัปเดตข้อมูลใน DataStore
แบบทรานแซ็กชัน พารามิเตอร์ transform
ของฟังก์ชันยอมรับ
บล็อกโค้ดที่คุณอัปเดตค่าได้ตามต้องการ ระบบจะถือว่าโค้ดทั้งหมดใน
บล็อกการแปลงเป็นธุรกรรมเดียว
Kotlin
suspend fun incrementCounter() {
context.dataStore.edit { settings ->
val currentCounterValue = settings[EXAMPLE_COUNTER] ?: 0
settings[EXAMPLE_COUNTER] = currentCounterValue + 1
}
}
Java
Single<Preferences> updateResult = dataStore.updateDataAsync(prefsIn -> {
MutablePreferences mutablePreferences = prefsIn.toMutablePreferences();
Integer currentInt = prefsIn.get(INTEGER_KEY);
mutablePreferences.set(INTEGER_KEY, currentInt != null ? currentInt + 1 : 1);
return Single.just(mutablePreferences);
});
// The update is completed once updateResult is completed.
จัดเก็บออบเจ็กต์ที่พิมพ์ด้วย Proto DataStore
การใช้งาน Proto DataStore จะใช้ DataStore และบัฟเฟอร์โปรโตคอลเพื่อ บันทึกออบเจ็กต์ที่มีการพิมพ์ลงในดิสก์
กำหนดสคีมา
Proto DataStore ต้องมีสคีมาที่กำหนดไว้ล่วงหน้าในไฟล์ Proto ในไดเรกทอรี
app/src/main/proto/
สคีมานี้กำหนดประเภทของออบเจ็กต์
ที่คุณคงไว้ใน Proto DataStore ดูข้อมูลเพิ่มเติมเกี่ยวกับการกำหนดสคีมาโปรโต
ได้ที่คู่มือภาษา Protobuf
syntax = "proto3";
option java_package = "com.example.application.proto";
option java_multiple_files = true;
message Settings {
int32 example_counter = 1;
}
สร้าง Proto DataStore
การสร้าง Proto DataStore เพื่อจัดเก็บออบเจ็กต์ที่มีการพิมพ์ มี 2 ขั้นตอนดังนี้
- กำหนดคลาสที่ใช้
Serializer<T>
โดยที่T
คือประเภทที่กำหนด ในไฟล์ Proto คลาส Serializer นี้จะบอก DataStore วิธีอ่านและเขียน ประเภทข้อมูลของคุณ ตรวจสอบว่าคุณได้ระบุค่าเริ่มต้นสำหรับ Serializer เพื่อ ใช้ในกรณีที่ยังไม่มีการสร้างไฟล์ - ใช้ตัวแทนพร็อพเพอร์ตี้ที่สร้างโดย
dataStore
เพื่อสร้างอินสแตนซ์ ของDataStore<T>
โดยที่T
คือประเภทที่กําหนดไว้ในไฟล์ Proto เรียกใช้ฟังก์ชันนี้ ครั้งเดียวที่ระดับบนสุดของไฟล์ Kotlin และเข้าถึงผ่านพร็อพเพอร์ตี้ นี้ตลอดทั้งแอป พารามิเตอร์filename
จะบอก DataStore ว่าจะใช้ไฟล์ใดในการจัดเก็บข้อมูล และพารามิเตอร์serializer
จะ ระบุชื่อของคลาส Serializer ที่กำหนดไว้ในขั้นตอนที่ 1
Kotlin
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)
}
val Context.settingsDataStore: DataStore<Settings> by dataStore(
fileName = "settings.pb",
serializer = SettingsSerializer
)
Java
private static class SettingsSerializer implements Serializer<Settings> {
@Override
public Settings getDefaultValue() {
return Settings.getDefaultInstance();
}
@Override
public Settings readFrom(@NotNull InputStream input) {
try {
return Settings.parseFrom(input);
} catch (InvalidProtocolBufferException exception) {
throw CorruptionException("Cannot read proto.", exception);
}
}
@Override
public void writeTo(Settings t, @NotNull OutputStream output) {
t.writeTo(output);
}
}
RxDataStore<Byte> dataStore =
new RxDataStoreBuilder<Byte>(
context,
/* fileName= */ "settings.pb",
new SettingsSerializer()
).build();
อ่านจาก Proto DataStore
ใช้ DataStore.data
เพื่อแสดง Flow
ของพร็อพเพอร์ตี้ที่เหมาะสมจาก
ออบเจ็กต์ที่จัดเก็บ
Kotlin
val exampleCounterFlow: Flow<Int> = context.settingsDataStore.data
.map { settings ->
// The exampleCounter property is generated from the proto schema.
settings.exampleCounter
}
Java
Flowable<Integer> exampleCounterFlow =
dataStore.data().map(settings -> settings.getExampleCounter());
เขียนไปยัง Proto DataStore
Proto DataStore มีฟังก์ชัน
updateData()
ที่อัปเดตออบเจ็กต์ที่จัดเก็บแบบทรานแซ็กชัน updateData()
จะแสดงสถานะปัจจุบันของข้อมูลเป็นอินสแตนซ์ของประเภทข้อมูลและอัปเดตข้อมูลแบบธุรกรรมในการดำเนินการอ่าน-เขียน-แก้ไขแบบอะตอม
Kotlin
suspend fun incrementCounter() {
context.settingsDataStore.updateData { currentSettings ->
currentSettings.toBuilder()
.setExampleCounter(currentSettings.exampleCounter + 1)
.build()
}
}
Java
Single<Settings> updateResult =
dataStore.updateDataAsync(currentSettings ->
Single.just(
currentSettings.toBuilder()
.setExampleCounter(currentSettings.getExampleCounter() + 1)
.build()));
ใช้ DataStore ในโค้ดแบบซิงโครนัส
ประโยชน์หลักอย่างหนึ่งของ DataStore คือ API แบบอะซิงโครนัส แต่การเปลี่ยนโค้ดโดยรอบให้เป็นแบบอะซิงโครนัสอาจทำไม่ได้เสมอไป กรณีนี้อาจเกิดขึ้นหากคุณทำงานกับโค้ดเบสที่มีอยู่ซึ่งใช้ I/O ของดิสก์แบบซิงโครนัส หรือหากคุณมี Dependency ที่ไม่ได้ให้ API แบบอะซิงโครนัส
โคโรทีน Kotlin มีตัวสร้างโคโรทีน runBlocking()
เพื่อช่วย
เชื่อมช่องว่างระหว่างโค้ดแบบซิงโครนัสและแบบอะซิงโครนัส คุณใช้
runBlocking()
เพื่ออ่านข้อมูลจาก DataStore แบบซิงโครนัสได้ RxJava มีเมธอดที่บล็อกใน Flowable
โค้ดต่อไปนี้จะบล็อกเธรดที่เรียกใช้
จนกว่า DataStore จะแสดงข้อมูล
Kotlin
val exampleData = runBlocking { context.dataStore.data.first() }
Java
Settings settings = dataStore.data().blockingFirst();
การดำเนินการ I/O แบบซิงโครนัสในเทรด UI อาจทำให้เกิด ANR หรือ UI ที่ไม่ตอบสนอง คุณสามารถลดปัญหาเหล่านี้ได้โดยการโหลดข้อมูลจาก DataStore ล่วงหน้าแบบไม่พร้อมกัน ดังนี้
Kotlin
override fun onCreate(savedInstanceState: Bundle?) {
lifecycleScope.launch {
context.dataStore.data.first()
// You should also handle IOExceptions here.
}
}
Java
dataStore.data().first().subscribe();
ด้วยวิธีนี้ DataStore จะอ่านข้อมูลแบบไม่พร้อมกันและแคชข้อมูลไว้ในหน่วยความจำ การอ่านแบบซิงโครนัสในภายหลังโดยใช้ runBlocking()
อาจเร็วกว่าหรืออาจหลีกเลี่ยงการดำเนินการ I/O ของดิสก์
ทั้งหมดได้หากการอ่านครั้งแรกเสร็จสมบูรณ์แล้ว
ใช้ DataStore ในโค้ดแบบหลายกระบวนการ
คุณสามารถกําหนดค่า DataStore เพื่อเข้าถึงข้อมูลเดียวกันในกระบวนการต่างๆ โดยมีพร็อพเพอร์ตี้ความสอดคล้องของข้อมูลเดียวกันกับที่อยู่ภายในกระบวนการเดียว โดยเฉพาะอย่างยิ่ง DataStore มีบริการต่อไปนี้
- การอ่านจะแสดงเฉพาะข้อมูลที่บันทึกลงในดิสก์เท่านั้น
- ความสอดคล้องแบบ Read-After-Write
- ระบบจะจัดลำดับการเขียน
- การอ่านจะไม่ถูกบล็อกโดยการเขียน
พิจารณาแอปพลิเคชันตัวอย่างที่มีบริการและกิจกรรม
บริการนี้ทำงานในกระบวนการแยกต่างหากและอัปเดต DataStore เป็นระยะ
<service android:name=".MyService" android:process=":my_process_id" />
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { scope.launch { while(isActive) { dataStore.updateData { Settings(lastUpdate = System.currentTimeMillis()) } delay(1000) } } }
ในขณะที่แอปจะรวบรวมการเปลี่ยนแปลงเหล่านั้นและอัปเดต UI
val settings: Settings by dataStore.data.collectAsState() Text( text = "Last updated: $${settings.timestamp}", )
หากต้องการใช้ DataStore ในกระบวนการต่างๆ คุณต้องสร้างออบเจ็กต์ DataStore โดยใช้ MultiProcessDataStoreFactory
val dataStore: DataStore<Settings> = MultiProcessDataStoreFactory.create(
serializer = SettingsSerializer(),
produceFile = {
File("${context.cacheDir.path}/myapp.preferences_pb")
}
)
serializer
จะบอก DataStore ถึงวิธีอ่านและเขียนประเภทข้อมูลของคุณ ตรวจสอบว่าคุณ
ระบุมูลค่าเริ่มต้นสำหรับ Serializer ที่จะใช้ในกรณีที่ยังไม่มีการสร้างไฟล์
ตัวอย่างการใช้งานโดยใช้
kotlinx.serialization มีดังนี้
@Serializable
data class Settings(
val lastUpdate: Long
)
@Singleton
class SettingsSerializer @Inject constructor() : Serializer<Settings> {
override val defaultValue = Settings(lastUpdate = 0)
override suspend fun readFrom(input: InputStream): Settings =
try {
Json.decodeFromString(
Settings.serializer(), input.readBytes().decodeToString()
)
} catch (serialization: SerializationException) {
throw CorruptionException("Unable to read Settings", serialization)
}
override suspend fun writeTo(t: Settings, output: OutputStream) {
output.write(
Json.encodeToString(Settings.serializer(), t)
.encodeToByteArray()
)
}
}
คุณสามารถใช้การแทรกทรัพยากร Dependency ของ Hilt เพื่อให้อินสแตนซ์ DataStore ไม่ซ้ำกันต่อกระบวนการ
@Provides
@Singleton
fun provideDataStore(@ApplicationContext context: Context): DataStore<Settings> =
MultiProcessDataStoreFactory.create(...)
จัดการไฟล์ที่เสียหาย
ในบางกรณีที่เกิดขึ้นไม่บ่อยนัก ไฟล์ที่คงอยู่บนดิสก์ของ DataStore อาจเสียหาย โดยค่าเริ่มต้น DataStore จะไม่กู้คืนจากการเสียหายโดยอัตโนมัติ
และการพยายามอ่านจาก DataStore จะทำให้ระบบแสดงCorruptionException
DataStore มี API ตัวแฮนเดิลความเสียหายที่จะช่วยให้คุณกู้คืนได้อย่างราบรื่น ในสถานการณ์ดังกล่าว และหลีกเลี่ยงการทิ้งข้อยกเว้น เมื่อกำหนดค่าแล้ว ตัวแฮนเดิลการเสียหายจะแทนที่ไฟล์ที่เสียหายด้วยไฟล์ใหม่ที่มี ค่าเริ่มต้นที่กำหนดไว้ล่วงหน้า
หากต้องการตั้งค่าแฮนเดิลนี้ ให้ระบุ corruptionHandler
เมื่อสร้างอินสแตนซ์ DataStore ใน by dataStore()
หรือในเมธอด DataStoreFactory
ของ Factory
val dataStore: DataStore<Settings> = DataStoreFactory.create(
serializer = SettingsSerializer(),
produceFile = {
File("${context.cacheDir.path}/myapp.preferences_pb")
},
corruptionHandler = ReplaceFileCorruptionHandler { Settings(lastUpdate = 0) }
)
แสดงความคิดเห็น
แชร์ความคิดเห็นและไอเดียกับเราผ่านแหล่งข้อมูลต่อไปนี้
- เครื่องมือติดตามปัญหา:
- รายงานปัญหาเพื่อให้เราแก้ไขข้อบกพร่องได้
แหล่งข้อมูลเพิ่มเติม
ดูข้อมูลเพิ่มเติมเกี่ยวกับ Jetpack DataStore ได้จากแหล่งข้อมูลเพิ่มเติมต่อไปนี้
ตัวอย่าง
บล็อก
Codelabs
แนะนำสำหรับคุณ
- หมายเหตุ: ข้อความลิงก์จะแสดงเมื่อ JavaScript ปิดอยู่
- โหลดและแสดงข้อมูลแบบแบ่งหน้า
- ภาพรวม LiveData
- เลย์เอาต์และการเชื่อมโยงนิพจน์