DataStore بخشی از Android Jetpack .
Jetpack DataStore یک راه حل ذخیره سازی داده است که به شما امکان می دهد جفت های کلید-مقدار یا اشیاء تایپ شده را با بافرهای پروتکل ذخیره کنید. DataStore از کوروتین های Kotlin و Flow برای ذخیره داده ها به صورت ناهمزمان، پیوسته و تراکنش استفاده می کند.
اگر در حال حاضر از SharedPreferences
برای ذخیره داده ها استفاده می کنید، به جای آن به DataStore مهاجرت کنید.
DataStore و Proto DataStore را ترجیح می دهد
DataStore دو پیاده سازی مختلف را ارائه می دهد: Preferences DataStore و Proto DataStore.
- Preferences DataStore داده ها را با استفاده از کلیدها ذخیره می کند و به آنها دسترسی پیدا می کند. این پیاده سازی به یک طرح از پیش تعریف شده نیاز ندارد و ایمنی نوع را فراهم نمی کند.
- Proto DataStore داده ها را به عنوان نمونه هایی از یک نوع داده سفارشی ذخیره می کند. این پیاده سازی به شما نیاز دارد که یک طرحواره را با استفاده از بافرهای پروتکل تعریف کنید، اما ایمنی نوع را فراهم می کند.
استفاده صحیح از DataStore
برای استفاده صحیح از DataStore همیشه قوانین زیر را در نظر داشته باشید:
هرگز بیش از یک نمونه از
DataStore
برای یک فایل معین در یک فرآیند ایجاد نکنید. انجام این کار می تواند تمام قابلیت های DataStore را از بین ببرد. اگر چندین DataStore برای یک فایل مشخص در یک فرآیند فعال باشد، DataStore هنگام خواندن یا بهروزرسانی دادهها،IllegalStateException
پرتاب میکند.نوع عمومی DataStore
باید تغییر ناپذیر باشد جهش یک نوع مورد استفاده در DataStore، هرگونه تضمینی را که DataStore ارائه میکند بی اعتبار میکند و اشکالات بالقوه جدی و سختگیر ایجاد میکند. اکیداً توصیه میشود که از بافرهای پروتکل استفاده کنید که تضمینهای تغییرناپذیری، API ساده و سریالسازی کارآمد را ارائه میدهند. هرگز استفاده از
SingleProcessDataStore
وMultiProcessDataStore
را برای یک فایل مخلوط نکنید . اگر قصد دارید از بیش از یک فرآیند بهDataStore
دسترسی داشته باشید، همیشه ازMultiProcessDataStore
استفاده کنید.
راه اندازی
برای استفاده از Jetpack DataStore در برنامه خود، بسته به اینکه از کدام پیاده سازی می خواهید استفاده کنید، موارد زیر را به فایل Gradle خود اضافه کنید:
دادههای برگزیده
شیار
// Preferences DataStore (SharedPreferences like APIs) dependencies { implementation "androidx.datastore:datastore-preferences:1.1.1" // optional - RxJava2 support implementation "androidx.datastore:datastore-preferences-rxjava2:1.1.1" // optional - RxJava3 support implementation "androidx.datastore:datastore-preferences-rxjava3:1.1.1" } // Alternatively - use the following artifact without an Android dependency. dependencies { implementation "androidx.datastore:datastore-preferences-core:1.1.1" }
کاتلین
// Preferences DataStore (SharedPreferences like APIs) dependencies { implementation("androidx.datastore:datastore-preferences:1.1.1") // optional - RxJava2 support implementation("androidx.datastore:datastore-preferences-rxjava2:1.1.1") // optional - RxJava3 support implementation("androidx.datastore:datastore-preferences-rxjava3:1.1.1") } // Alternatively - use the following artifact without an Android dependency. dependencies { implementation("androidx.datastore:datastore-preferences-core:1.1.1") }
Proto DataStore
شیار
// Typed DataStore (Typed API surface, such as Proto) dependencies { implementation "androidx.datastore:datastore:1.1.1" // optional - RxJava2 support implementation "androidx.datastore:datastore-rxjava2:1.1.1" // optional - RxJava3 support implementation "androidx.datastore:datastore-rxjava3:1.1.1" } // Alternatively - use the following artifact without an Android dependency. dependencies { implementation "androidx.datastore:datastore-core:1.1.1" }
کاتلین
// Typed DataStore (Typed API surface, such as Proto) dependencies { implementation("androidx.datastore:datastore:1.1.1") // optional - RxJava2 support implementation("androidx.datastore:datastore-rxjava2:1.1.1") // optional - RxJava3 support implementation("androidx.datastore:datastore-rxjava3:1.1.1") } // Alternatively - use the following artifact without an Android dependency. dependencies { implementation("androidx.datastore:datastore-core:1.1.1") }
جفتهای کلید-مقدار را با Preferences DataStore ذخیره کنید
اجرای Preferences DataStore از کلاس های DataStore
و Preferences
برای تداوم جفت های کلید-مقدار ساده روی دیسک استفاده می کند.
یک فروشگاه داده ترجیحی ایجاد کنید
برای ایجاد نمونه ای از DataStore<Preferences>
از ویژگی نماینده ایجاد شده توسط preferencesDataStore
استفاده کنید. یک بار آن را در سطح بالای فایل kotlin خود تماس بگیرید و از طریق این ویژگی در بقیه برنامه خود به آن دسترسی داشته باشید. این باعث میشود که DataStore
خود را بهصورت تکی راحتتر نگه دارید. یا اگر از RxJava استفاده می کنید، از RxPreferenceDataStoreBuilder
استفاده کنید. پارامتر name
اجباری نام Preferences DataStore است.
کاتلین
// At the top level of your kotlin file: val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
جاوا
RxDataStore<Preferences> dataStore = new RxPreferenceDataStoreBuilder(context, /*name=*/ "settings").build();
از یک Preferences DataStore بخوانید
از آنجا که Preferences DataStore از طرحی از پیش تعریف شده استفاده نمی کند، باید از تابع نوع کلید مربوطه برای تعریف یک کلید برای هر مقداری که باید در نمونه DataStore<Preferences>
ذخیره کنید استفاده کنید. به عنوان مثال، برای تعریف یک کلید برای مقدار int، از intPreferencesKey()
استفاده کنید. سپس، از ویژگی DataStore.data
برای نمایش مقدار ذخیره شده مناسب با استفاده از یک Flow
استفاده کنید.
کاتلین
val EXAMPLE_COUNTER = intPreferencesKey("example_counter") val exampleCounterFlow: Flow<Int> = context.dataStore.data .map { preferences -> // No type safety. preferences[EXAMPLE_COUNTER] ?: 0 }
جاوا
Preferences.Key<Integer> EXAMPLE_COUNTER = PreferencesKeys.int("example_counter"); Flowable<Integer> exampleCounterFlow = dataStore.data().map(prefs -> prefs.get(EXAMPLE_COUNTER));
در یک DataStore Preferences بنویسید
Preferences DataStore یک تابع edit()
ارائه می دهد که به صورت تراکنشی داده ها را در یک DataStore
به روز می کند. پارامتر transform
تابع یک بلوک کد را می پذیرد که در آن می توانید مقادیر را در صورت نیاز به روز کنید. تمام کدهای موجود در بلوک تبدیل به عنوان یک تراکنش واحد در نظر گرفته می شود.
کاتلین
suspend fun incrementCounter() { context.dataStore.edit { settings -> val currentCounterValue = settings[EXAMPLE_COUNTER] ?: 0 settings[EXAMPLE_COUNTER] = currentCounterValue + 1 } }
جاوا
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 به یک طرح از پیش تعریف شده در یک فایل پروتو در دایرکتوری app/src/main/proto/
نیاز دارد. این طرح، نوع اشیایی را که در Proto DataStore خود باقی می مانند، تعریف می کند. برای کسب اطلاعات بیشتر در مورد تعریف طرحواره پروتو، به راهنمای زبان پروتوباف مراجعه کنید.
syntax = "proto3";
option java_package = "com.example.application";
option java_multiple_files = true;
message Settings {
int32 example_counter = 1;
}
یک Proto DataStore ایجاد کنید
دو مرحله برای ایجاد یک Proto DataStore برای ذخیره اشیاء تایپ شده شما وجود دارد:
- کلاسی را تعریف کنید که
Serializer<T>
پیاده سازی کند، جایی کهT
نوع تعریف شده در فایل پروتو است. این کلاس سریال ساز به DataStore می گوید که چگونه نوع داده شما را بخواند و بنویسد. مطمئن شوید که یک مقدار پیشفرض برای سریالساز در نظر بگیرید تا اگر هنوز فایلی ایجاد نشده است، استفاده شود. - از نماینده خاصیت ایجاد شده توسط
dataStore
برای ایجاد نمونه ای ازDataStore<T>
استفاده کنید، جایی کهT
نوع تعریف شده در فایل پروتو است. این را یک بار در سطح بالای فایل kotlin خود تماس بگیرید و از طریق این ویژگی در بقیه برنامه خود به آن دسترسی داشته باشید. پارامترfilename
به DataStore می گوید که از کدام فایل برای ذخیره داده ها استفاده کند و پارامترserializer
نام کلاس سریال ساز تعریف شده در مرحله 1 را به 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) = t.writeTo(output) } val Context.settingsDataStore: DataStore<Settings> by dataStore( fileName = "settings.pb", serializer = SettingsSerializer )
جاوا
private static class SettingsSerializer implements Serializer<Settings> { @Override public Settings getDefaultValue() { Settings.getDefaultInstance(); } @Override public Settings readFrom(@NotNull InputStream input) { try { return Settings.parseFrom(input); } catch (exception: InvalidProtocolBufferException) { 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
از ویژگی مناسب از شی ذخیره شده خود استفاده کنید.
کاتلین
val exampleCounterFlow: Flow<Int> = context.settingsDataStore.data .map { settings -> // The exampleCounter property is generated from the proto schema. settings.exampleCounter }
جاوا
Flowable<Integer> exampleCounterFlow = dataStore.data().map(settings -> settings.getExampleCounter());
در Proto DataStore بنویسید
Proto DataStore یک تابع updateData()
ارائه می کند که به صورت تراکنشی یک شی ذخیره شده را به روز می کند. updateData()
وضعیت فعلی داده ها را به عنوان نمونه ای از نوع داده شما به شما می دهد و داده ها را به صورت تراکنشی در یک عملیات خواندن-نوشتن-تغییر اتمی به روز می کند.
کاتلین
suspend fun incrementCounter() { context.settingsDataStore.updateData { currentSettings -> currentSettings.toBuilder() .setExampleCounter(currentSettings.exampleCounter + 1) .build() } }
جاوا
Single<Settings> updateResult = dataStore.updateDataAsync(currentSettings -> Single.just( currentSettings.toBuilder() .setExampleCounter(currentSettings.getExampleCounter() + 1) .build()));
از DataStore در کدهای همزمان استفاده کنید
یکی از مزایای اصلی DataStore API ناهمزمان است، اما ممکن است همیشه امکان پذیر نباشد که کد اطراف خود را به ناهمزمان تغییر دهید. اگر با یک پایگاه کد موجود کار میکنید که از ورودی/خروجی دیسک همزمان استفاده میکند یا اگر وابستگی دارید که یک API ناهمزمان ارائه نمیدهد، ممکن است این مورد صادق باشد.
کوروتینهای Kotlin سازنده کوروتین runBlocking()
را برای کمک به پر کردن شکاف بین کدهای همزمان و ناهمزمان ارائه میکنند. می توانید از runBlocking()
برای خواندن همزمان داده ها از DataStore استفاده کنید. RxJava روش های مسدود کردن را در Flowable
ارائه می دهد. کد زیر تا زمانی که DataStore داده ها را برگرداند، رشته تماس گیرنده را مسدود می کند:
کاتلین
val exampleData = runBlocking { context.dataStore.data.first() }
جاوا
Settings settings = dataStore.data().blockingFirst();
انجام عملیات ورودی/خروجی همزمان روی رشته رابط کاربری میتواند باعث ANR یا جابجایی UI شود. میتوانید با بارگیری ناهمزمان دادهها از DataStore، این مشکلات را کاهش دهید:
کاتلین
override fun onCreate(savedInstanceState: Bundle?) { lifecycleScope.launch { context.dataStore.data.first() // You should also handle IOExceptions here. } }
جاوا
dataStore.data().first().subscribe();
به این ترتیب DataStore به صورت ناهمزمان داده ها را می خواند و در حافظه پنهان می کند. خواندن همزمان بعدی با استفاده از runBlocking()
ممکن است سریعتر باشد یا اگر خواندن اولیه کامل شده باشد، ممکن است به طور کلی از عملیات ورودی/خروجی دیسک جلوگیری کند.
از DataStore در کدهای چند فرآیندی استفاده کنید
میتوانید DataStore را برای دسترسی به دادههای یکسان در فرآیندهای مختلف با تضمینهای یکسانی دادهها از داخل یک فرآیند پیکربندی کنید. به طور خاص، DataStore تضمین می کند:
- Reads فقط دادههایی را که حفظ شدهاند به دیسک برمیگرداند.
- سازگاری خواندن پس از نوشتن
- نوشته ها به صورت سریالی هستند.
- خواندن هرگز توسط نوشتن مسدود نمی شود.
یک نمونه برنامه کاربردی با یک سرویس و یک فعالیت را در نظر بگیرید:
این سرویس در یک فرآیند جداگانه اجرا می شود و به طور دوره ای 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) } } }
در حالی که برنامه این تغییرات را جمع آوری کرده و رابط کاربری خود را به روز می کند
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 می گوید که چگونه نوع داده شما را بخواند و بنویسد. مطمئن شوید که یک مقدار پیشفرض برای سریالساز در نظر بگیرید تا اگر هنوز فایلی ایجاد نشده است، استفاده شود. در زیر نمونه ای از پیاده سازی با استفاده از 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): Timer =
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()
)
}
}
میتوانید از تزریق وابستگی 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.cacheDir.path}/myapp.preferences_pb")
},
corruptionHandler = ReplaceFileCorruptionHandler { Settings(lastUpdate = 0) }
)
بازخورد ارائه دهید
نظرات و ایده های خود را از طریق این منابع با ما در میان بگذارید:
- ردیاب مشکل
- مشکلات را گزارش کنید تا بتوانیم اشکالات را برطرف کنیم.
منابع اضافی
برای کسب اطلاعات بیشتر در مورد Jetpack DataStore، به منابع اضافی زیر مراجعه کنید:
نمونه ها
وبلاگ ها
Codelabs
{% کلمه به کلمه %}برای شما توصیه می شود
- توجه: وقتی جاوا اسکریپت خاموش است، متن پیوند نمایش داده می شود
- بارگیری و نمایش داده های صفحه بندی شده
- نمای کلی LiveData
- چیدمان ها و عبارات الزام آور