DataStore بخشی از Android Jetpack.
Jetpack DataStore راهحلی برای ذخیره داده است که به شما امکان میدهد جفتهای کلید-مقدار یا اشیای تایپشده را با بافرهای پروتکل ذخیره کنید. DataStore از Kotlin coroutines و Flow برای ذخیره دادهها بهصورت ناهمزمان، یکپارچه، و تراکنشی استفاده میکند.
اگر از SharedPreferences برای ذخیره دادهها استفاده میکنید، به DataStore انتقال دهید.
DataStore API
میانای DataStore میانای برنامهسازی کاربردی زیر را ارائه میدهد:
جریانی که میتواند برای خواندن دادهها از DataStore استفاده شود
val data: Flow<T>تابعی برای بهروزرسانی دادهها در DataStore
suspend updateData(transform: suspend (t) -> T)
پیکربندیهای DataStore
اگر میخواهید دادهها را بااستفاده از کلیدها ذخیره و به آنها دسترسی پیدا کنید، از پیادهسازی Preferences
DataStore استفاده کنید که به طرحواره ازپیش تعریفشده نیاز ندارد و ایمنی نوع را ارائه نمیدهد. این ویژگی یک API شبیه به SharedPreferences دارد اما
معایب مربوط به اولویتهای مشترک را ندارد.
DataStore به شما امکان میدهد کلاسهای سفارشی را ماندگار کنید. برای انجام این کار، باید طرحوارهای برای دادهها تعریف کنید و Serializer را برای تبدیل آن به قالب ماندگار ارائه دهید. میتوانید انتخاب کنید که از «بافرهای پروتکل»، JSON، یا هر استراتژی سریالسازی دیگری استفاده کنید.
راهاندازی
برای استفاده از Jetpack DataStore در برنامهتان، موارد زیر را به فایل Gradle اضافه کنید بسته به اینکه میخواهید از کدام پیادهسازی استفاده کنید:
فروشگاه داده تنظیمات
خطوط زیر را به بخش وابستگیهای فایل gradle خود اضافه کنید:
گرووی
dependencies { // Preferences DataStore (SharedPreferences like APIs) implementation "androidx.datastore:datastore-preferences:1.2.1" // Alternatively - without an Android dependency. implementation "androidx.datastore:datastore-preferences-core:1.2.1" }
کاتلین
dependencies { // Preferences DataStore (SharedPreferences like APIs) implementation("androidx.datastore:datastore-preferences:1.2.1") // Alternatively - without an Android dependency. implementation("androidx.datastore:datastore-preferences-core:1.2.1") }
برای افزودن پشتیبانی اختیاری از RxJava، وابستگیهای زیر را اضافه کنید:
گرووی
dependencies { // optional - RxJava2 support implementation "androidx.datastore:datastore-preferences-rxjava2:1.2.1" // optional - RxJava3 support implementation "androidx.datastore:datastore-preferences-rxjava3:1.2.1" }
کاتلین
dependencies { // optional - RxJava2 support implementation("androidx.datastore:datastore-preferences-rxjava2:1.2.1") // optional - RxJava3 support implementation("androidx.datastore:datastore-preferences-rxjava3:1.2.1") }
فروشگاه داده
خطوط زیر را به بخش وابستگیهای فایل gradle خود اضافه کنید:
گرووی
dependencies { // Typed DataStore for custom data objects (for example, using Proto or JSON). implementation "androidx.datastore:datastore:1.2.1" // Alternatively - without an Android dependency. implementation "androidx.datastore:datastore-core:1.2.1" }
کاتلین
dependencies { // Typed DataStore for custom data objects (for example, using Proto or JSON). implementation("androidx.datastore:datastore:1.2.1") // Alternatively - without an Android dependency. implementation("androidx.datastore:datastore-core:1.2.1") }
وابستگیهای اختیاری زیر را برای پشتیبانی از RxJava اضافه کنید:
گرووی
dependencies { // optional - RxJava2 support implementation "androidx.datastore:datastore-rxjava2:1.2.1" // optional - RxJava3 support implementation "androidx.datastore:datastore-rxjava3:1.2.1" }
کاتلین
dependencies { // optional - RxJava2 support implementation("androidx.datastore:datastore-rxjava2:1.2.1") // optional - RxJava3 support implementation("androidx.datastore:datastore-rxjava3:1.2.1") }
برای سریالسازی محتوا، وابستگیهایی را برای Protocol Buffers یا سریالسازی JSON اضافه کنید.
سریالسازی JSON
برای استفاده از سریالسازی JSON، موارد زیر را به فایل Gradle خود اضافه کنید:
گرووی
plugins { id("org.jetbrains.kotlin.plugin.serialization") version "2.2.20" } dependencies { implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0" }
کاتلین
plugins { id("org.jetbrains.kotlin.plugin.serialization") version "2.2.20" } dependencies { implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0") }
سریالسازی پروتوباف
برای استفاده از سریالسازی Protobuf، موارد زیر را به فایل Gradle خود اضافه کنید:
گرووی
plugins { id("com.google.protobuf") version "0.9.5" } dependencies { implementation "com.google.protobuf:protobuf-kotlin-lite:4.32.1" } protobuf { protoc { artifact = "com.google.protobuf:protoc:4.32.1" } generateProtoTasks { all().forEach { task -> task.builtins { create("java") { option("lite") } create("kotlin") } } } }
کاتلین
plugins { id("com.google.protobuf") version "0.9.5" } dependencies { implementation("com.google.protobuf:protobuf-kotlin-lite:4.32.1") } protobuf { protoc { artifact = "com.google.protobuf:protoc:4.32.1" } generateProtoTasks { all().forEach { task -> task.builtins { create("java") { option("lite") } create("kotlin") } } } }
استفاده صحیح از DataStore
برای استفاده صحیح از DataStore، همیشه قوانین زیر را بهخاطر داشته باشید:
هرگز بیشاز یک نمونه از
DataStoreبرای فایل معینی در فرایند یکسان ایجاد نکنید. انجام این کار میتواند باعث ازکار افتادن همه عملکردهای DataStore شود. اگر چندین DataStore برای یک فایل معین در یک فرایند فعال باشند، DataStore هنگام خواندن یا بهروزرسانی دادههاIllegalStateExceptionرا پرتاب میکند.نوع عمومی
DataStore<T>باید تغییرناپذیر باشد. تغییر دادن نوعی که در DataStore استفاده میشود، سازگاریای را که DataStore ارائه میدهد نامعتبر میکند و اشکالات بالقوه جدی و دشوار برای شناسایی ایجاد میکند. توصیه میکنیم از بافرهای پروتکل استفاده کنید که به تضمین تغییرناپذیری، API واضح، و سریالسازی کارآمد کمک میکند.کاربردهای
SingleProcessDataStoreوMultiProcessDataStoreرا برای یک فایل ترکیب نکنید. اگر قصد دارید از بیشاز یک فرایند بهDataStoreدسترسی پیدا کنید، باید ازMultiProcessDataStoreاستفاده کنید.
تعریف دادهها
Preferences DataStore
کلیدی را تعریف کنید که برای ماندگار کردن دادهها در دیسک استفاده خواهد شد.
val EXAMPLE_COUNTER = intPreferencesKey("example_counter")
JSON DataStore
برای مخزن داده JSON، یک گزارمان @Serialization به دادههایی که میخواهید
ماندگار شوند اضافه کنید.
@Serializable data class Settings(val exampleCounter: Int)
کلاسی را تعریف کنید که Serializer<T> را پیادهسازی میکند، که در آن T نوع کلاسی است که
گزارمان را قبلاً به آن اضافه کردهاید. مطمئن شوید که مقدار پیشفرضی را برای سریالساز اضافه کنید تا درصورتیکه هنوز فایلی ایجاد نشده باشد از آن استفاده شود.
object SettingsSerializer : Serializer<Settings> { override val defaultValue: Settings = Settings(exampleCounter = 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()) } }
Proto DataStore
پیادهسازی Proto DataStore از DataStore و بافرهای پروتکل برای ماندگار کردن اشیای نوعدار در دیسک استفاده میکند.
Proto DataStore به طرحوارهای ازپیش تعریفشده در فایل proto در
دایرکتوری app/src/main/proto/ نیاز دارد. این طرحواره نوع اشیایی را که در Proto DataStore ذخیره میکنید تعریف میکند. برای کسب اطلاعات بیشتر درباره تعریف کردن طرحواره proto، به راهنمای زبان protobuf مراجعه کنید.
فایلی بهنام settings.proto را به پوشه src/main/proto اضافه کنید:
syntax = "proto3"; option java_package = "com.example.datastoresampleapp"; option java_multiple_files = true; message Settings { int32 counter = 1; bool foo = 2; }
کلاسی را تعریف کنید که Serializer<T> را پیادهسازی میکند، که در آن T نوع تعریفشده
در فایل proto است. این کلاس سریالساز تعریف میکند که DataStore چگونه نوع داده شما را میخواند و مینویسد. مطمئن شوید که مقدار پیشفرضی برای
سریالساز اضافه کنید تا درصورتیکه هنوز فایلی ایجاد نشده است استفاده شود.
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) { return t.writeTo(output) } }
ایجاد DataStore
باید نامی برای فایلی که برای ماندگار کردن دادهها استفاده میشود مشخص کنید.
Preferences DataStore
پیادهسازی Preferences DataStore از کلاسهای DataStore و
Preferences برای ماندگار کردن جفتهای کلید-مقدار در دیسک استفاده میکند. برای ایجاد نمونهای از DataStore<Preferences>، از نماینده دارایی ایجادشده توسط preferencesDataStore استفاده کنید. آن را یکبار در سطح بالای فایل Kotlin خود فراخوانی کنید. ازطریق این دارایی در بقیه
برنامهتان به DataStore دسترسی داشته باشید. این کار باعث میشود که DataStore را راحتتر بهعنوان تکنمونه نگه دارید.
پارامتر اجباری name نام Preferences DataStore است.
// At the top level of your kotlin file: val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
JSON DataStore
برای ایجاد نمونهای از
DataStore<T>، که در آن T کلاس داده سریالشدنی است، از نماینده دارایی ایجادشده توسط dataStore استفاده کنید. آن را یکبار
در سطح بالای فایل Kotlin خود فراخوانی کنید و ازطریق این نماینده
دارایی در سراسر بقیه برنامه به آن دسترسی پیدا کنید. پارامتر fileName به
DataStore میگوید از کدام فایل برای ذخیره دادهها استفاده کند و پارامتر serializer
نام کلاس سریالساز تعریفشده قبلی را به DataStore میگوید.
val Context.dataStore: DataStore<Settings> by dataStore( fileName = "settings.json", serializer = SettingsSerializer, scope = CoroutineScope(Dispatchers.IO + SupervisorJob()), )
Proto DataStore
از نماینده دارایی ایجادشده توسط dataStore برای ایجاد نمونهای از
DataStore<T> استفاده کنید، که در آن T نوع تعریفشده در فایل پروتکل است. آن را
یکبار در سطح بالای فایل Kotlin خود فراخوانی کنید و ازطریق این نماینده
دارایی در بقیه برنامه خود به آن دسترسی پیدا کنید. پارامتر fileName به
DataStore میگوید از کدام فایل برای ذخیره دادهها استفاده کند و پارامتر serializer
نام کلاس سریالساز تعریفشده قبلی را به DataStore میگوید.
val Context.dataStore: DataStore<Settings> by dataStore(fileName = "settings.pb", serializer = SettingsSerializer)
خواندن از DataStore
باید نامی برای فایلی که برای ماندگار کردن دادهها استفاده میشود مشخص کنید.
Preferences DataStore
ازآنجاییکه Preferences DataStore از طرحواره ازپیش تعریفشده استفاده نمیکند، باید از تابع نوع کلید مربوطه برای تعریف کلید هر مقداری که باید در نمونه DataStore<Preferences> ذخیره کنید استفاده کنید. برای مثال، برای تعریف
کلید برای مقدار int، از intPreferencesKey استفاده کنید. سپس از دارایی
DataStore.data برای آشکار کردن مقدار ذخیرهشده مناسب بااستفاده از
جریان استفاده کنید.
fun counterFlow(): Flow<Int> = context.dataStore.data.map { preferences -> preferences[EXAMPLE_COUNTER] ?: 0 }
JSON DataStore
از DataStore.data برای آشکار کردن Flow دارایی مناسب از
شیء ذخیرهشده استفاده کنید.
fun counterFlow(): Flow<Int> = context.dataStore.data.map { settings -> settings.exampleCounter }
Proto DataStore
از DataStore.data برای آشکار کردن Flow دارایی مناسب از
شیء ذخیرهشده استفاده کنید.
fun counterFlow(): Flow<Int> = context.dataStore.data.map { settings -> settings.counter }
از collectAsStateWithLifecycle برای مصرف Flow تولیدشده توسط
ViewModel در یک عنصر ترکیبی استفاده کنید.
این تابع «جریان DataStore» را بهطور ایمن به «حالت Compose» تبدیل میکند که باعث بازترکیب میشود.
@Composable
fun SomeScreen(counterFlow: Flow<Int>) {
val counter by counterFlow.collectAsStateWithLifecycle(initialValue = 0)
Text(text = "Example counter: ${counter}")
}
برای اطلاعات بیشتر درباره collectAsStateWithLifecycle،
به حالت و Jetpack Compose مراجعه کنید.
نوشتن در DataStore
DataStore تابع updateData را ارائه میدهد که بهصورت تراکنشی شیء ذخیرهشده را بهروزرسانی میکند. updateData وضعیت فعلی دادهها را بهعنوان نمونهای از نوع داده شما ارائه میدهد و دادهها را بهصورت تراکنشی در عملیات خواندن-نوشتن-اصلاح اتمی بهروز میکند. تمام کد موجود در بلوک updateData بهعنوان یک تراکنش واحد درنظر گرفته میشود.
Preferences DataStore
suspend fun incrementCounter() { context.dataStore.updateData { it.toMutablePreferences().also { preferences -> preferences[EXAMPLE_COUNTER] = (preferences[EXAMPLE_COUNTER] ?: 0) + 1 } } }
JSON DataStore
suspend fun incrementCounter() { context.dataStore.updateData { settings -> settings.copy(exampleCounter = settings.exampleCounter + 1) } }
Proto DataStore
suspend fun incrementCounter() { context.dataStore.updateData { settings -> settings.toBuilder().setCounter(settings.counter + 1).build() } }
استفاده از DataStore در برنامه Compose
برای استفاده از DataStore در برنامه Compose، با نگه داشتن عملیات DataStore در لایه داده (مثل مخزن) و نمایان کردن دادهها در واسط کاربر ازطریق ViewModel، از دستورالعملهای معماری برنامه Android پیروی کنید.
از خواندن یا نوشتن مستقیم در DataStore در توابع ترکیبی خود اجتناب کنید.
DataStore را ازطریق ViewModel نمایان کنید. مخزن خود (که DataStore را میپیچد) را به
ViewModelمنتقل کنید وFlowرا بهStateFlowتبدیل کنید تا واسط کاربر بتواند آن را مشاهده کند، همانطور که در تکه کد زیر نشان داده شده است:class SettingsViewModel( private val userPreferencesRepository: UserPreferencesRepository ) : ViewModel() { // Expose the DataStore flow as a StateFlow for Compose val userSettings: StateFlow<UserSettings> = userPreferencesRepository.userSettingsFlow .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), initialValue = UserSettings.getDefaultInstance() ) fun updateCounter(newValue: Int) { viewModelScope.launch { userPreferencesRepository.updateCounter(newValue) } } }از عنصر ترکیبی خود مشاهده و بنویسید. از
collectAsStateWithLifecycleبرای مشاهده ایمنStateFlowدر واسط کاربر خود استفاده کنید، و برای مدیریت نوشتنها، همانطور که در گزیده زیر نشان داده شده است، توابعViewModelرا فراخوانی کنید:@Composable fun SettingsScreen( viewModel: SettingsViewModel = viewModel() ) { // Safely collect the state val settings by viewModel.userSettings.collectAsStateWithLifecycle() Column(modifier = Modifier.padding(16.dp)) { Text(text = "Current counter: ${settings.counter}") Spacer(modifier = Modifier.height(8.dp)) Button(onClick = { viewModel.updateCounter(settings.counter + 1) }) { Text("Increment Counter") } } }
استفاده از DataStore در کد چندپردازشی
میتوانید DataStore را پیکربندی کنید تا در فرایندهای مختلف به دادههای یکسانی دسترسی داشته باشد با همان ویژگیهای سازگاری داده که در یک فرایند واحد وجود دارد. بهطور خاص، DataStore ویژگیهای زیر را ارائه میدهد:
- خواندنها فقط دادههایی را برمیگرداند که در دیسک ماندگار شدهاند.
- سازگاری خواندن پساز نوشتن.
- نوشتنها سریالسازی میشوند.
- نوشتن هرگز خواندن را مسدود نمیکند.
برنامه نمونهای را درنظر بگیرید که سرویس و فعالیتی دارد که در آن سرویس در فرایندی جداگانه اجرا میشود و DataStore را بهصورت دورهای بهروزرسانی میکند.
این مثال از یک فروشگاه داده JSON استفاده میکند، اما میتوانید از Preferences یا Proto DataStore نیز استفاده کنید.
@Serializable data class Time(val lastUpdateMillis: Long)
سریالساز به DataStore میگوید چگونه نوع دادههایتان را بخواند و بنویسد. مطمئن شوید
مقدار پیشفرضی را برای سریالساز اضافه کنید تا درصورت عدم وجود فایل
ایجادشده استفاده شود. در زیر، پیادهسازی نمونهای بااستفاده از
kotlinx.serialization ارائه شده است:
object TimeSerializer : Serializer<Time> { override val defaultValue: Time = Time(lastUpdateMillis = 0L) override suspend fun readFrom(input: InputStream): Time = try { Json.decodeFromString(Time.serializer(), input.readBytes().decodeToString()) } catch (serialization: SerializationException) { throw CorruptionException("Unable to read Time", serialization) } override suspend fun writeTo(t: Time, output: OutputStream) { output.write(Json.encodeToString(Time.serializer(), t).encodeToByteArray()) } }
برای اینکه بتوانید از DataStore در فرایندهای مختلف استفاده کنید، باید
شیء DataStore را بااستفاده از MultiProcessDataStoreFactory برای هم برنامه
و هم کد سرویس بسازید:
val dataStore = MultiProcessDataStoreFactory.create( serializer = TimeSerializer, produceFile = { context.dataStoreFile("time.pb") }, corruptionHandler = null, )
مورد زیر را به AndroidManifiest.xml اضافه کنید:
<service android:name="com.example.datastore.snippets.TimestampUpdateService" android:exported="false" android:process=":service" />
این سرویس بهصورت دورهای updateLastUpdateTime را فراخوانی میکند که بااستفاده از updateData در
انبار داده مینویسد.
suspend fun updateLastUpdateTime() { dataStore.updateData { time -> time.copy(lastUpdateMillis = System.currentTimeMillis()) } }
برنامه مقدار نوشتهشده توسط سرویس را بااستفاده از جریان داده میخواند:
fun timeFlow(): Flow<Long> = dataStore.data.map { time -> time.lastUpdateMillis }
اکنون میتوانیم همه این توابع را در کلاسی بهنام
MultiProcessDataStore قرار دهیم و از آن در «برنامه» استفاده کنیم.
کد سرویس در اینجا است:
class TimestampUpdateService : Service() { val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) val multiProcessDataStore by lazy { MultiProcessDataStore(applicationContext) } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { serviceScope.launch { while (true) { multiProcessDataStore.updateLastUpdateTime() delay(1000) } } return START_NOT_STICKY } override fun onDestroy() { super.onDestroy() serviceScope.cancel() } }
و کد برنامه:
val context = LocalContext.current val coroutineScope = rememberCoroutineScope() val multiProcessDataStore = remember(context) { MultiProcessDataStore(context) } // Display time written by other process. val lastUpdateTime by multiProcessDataStore .timeFlow() .collectAsState(initial = 0, coroutineScope.coroutineContext) Text(text = "Last updated: $lastUpdateTime", fontSize = 25.sp) DisposableEffect(context) { val serviceIntent = Intent(context, TimestampUpdateService::class.java) context.startService(serviceIntent) onDispose { context.stopService(serviceIntent) } }
میتوانید از تزریق وابستگی Hilt استفاده کنید تا نمونه DataStore شما برای هر فرایند یکتا باشد:
@Provides
@Singleton
fun provideDataStore(@ApplicationContext context: Context): DataStore<Settings> =
MultiProcessDataStoreFactory.create(...)
رسیدگی به خرابی فایل
در موارد نادر، ممکن است فایل دائمی روی دیسک DataStore خراب شود. بهطور پیشفرض، DataStore بهطور خودکار از خرابی بازیابی نمیشود،
و تلاش برای خواندن از آن باعث میشود سیستم
CorruptionException را پرتاب کند.
DataStore یک API مدیریت خرابی ارائه میدهد که میتواند به شما کمک کند در چنین شرایطی بهطور مناسب بازیابی کنید و از ایجاد استثنا جلوگیری کنید. وقتی پیکربندی شود، مدیر خرابی فایل خراب را با فایل جدیدی که حاوی مقدار پیشفرض ازپیشتعیینشده است جایگزین میکند.
برای راهاندازی این مدیریتکننده، هنگام ایجاد نمونه DataStore در by dataStore یا در روش کارخانه DataStoreFactory، corruptionHandler را ارائه دهید:
val dataStore: DataStore<Settings> = DataStoreFactory.create(
serializer = SettingsSerializer(),
produceFile = {
File("${context.filesDir.path}/myapp.preferences_pb")
},
corruptionHandler = ReplaceFileCorruptionHandler { Settings(lastUpdate = 0) }
)
پشتیبانگیری و بازیابی دستگاه
فایلهای Preferences DataStore (*.preferences_pb) و فایلهای Proto DataStore در
دایرکتوری files/datastore/ برنامه ذخیره میشوند. بهطور پیشفرض، این فایلها در
پشتیبانگیری خودکار Android و انتقال دستگاه به دستگاه (D2D) گنجانده میشوند.
پیکربندی قوانین پشتیبانگیری برای DataStore
اگر DataStore شما درکنار دادههای حساس حاوی اولویتهای غیرحساس (مثل زمینه کاربر یا
پرچمهای ویژگی) است، آنها را در فایلهای DataStore متمایز جدا کنید و res/xml/data_extraction_rules.xml را پیکربندی کنید:
<data-extraction-rules>
<cloud-backup>
<!-- Include general settings -->
<include domain="file" path="datastore/user_settings.preferences_pb"/>
<!-- Exclude sensitive local state -->
<exclude domain="file" path="datastore/secure_state.preferences_pb"/>
</cloud-backup>
<device-to-device>
<!-- Transfer settings during device-to-device transfer -->
<include domain="file" path="datastore/"/>
</device-to-device>
</data-extraction-rules>
ارائه کردن بازخورد
ازطریق این منابع، بازخوردها و ایدههایتان را با ما همرسانی کنید:
- ردیاب مشکل:
- مشکلات را گزارش دهید تا بتوانیم اشکالات را برطرف کنیم.
منابع بیشتر
برای کسب اطلاعات بیشتر درباره Jetpack DataStore، منابع تکمیلی زیر را ببینید:
نمونهها
وبلاگها
Codelabs
توصیهشده برای شما
- توجه: نوشتار پیوند وقتی جاوا اسکریپت خاموش است نمایش داده میشود
- بار کردن و نمایش دادههای صفحهبندیشده
- نمای کلی دادههای زنده
- چیدمانها و عبارات پیونددهنده