DataStore جزء من Android Jetpack.
Jetpack DataStore هو حلّ لتخزين البيانات يتيح لك تخزين أزواج ملفوظة مفاتيح/قيم أو عناصر مكتوبة باستخدام مخازن الملفات المخصّصة للبروتوكول. يستخدم DataStore وظائف Kotlin المجدولة غير المتزامنة وFlow لتخزين البيانات بشكل غير متزامن ومتسق ومعاملي.
إذا كنت تستخدِم حاليًا مكتبة
SharedPreferences
ل
تخزين البيانات، ننصحك بنقل البيانات إلى DataStore بدلاً من ذلك.
Preferences DataStore وProto DataStore
يوفّر DataStore طريقتَي تنفيذ مختلفتَين: Preferences DataStore و Proto DataStore.
- يخزن Preferences DataStore البيانات ويدخل إليها باستخدام مفاتيح. لا يتطلّب هذا الأسلوب تنفيذ مخطّط محدّد مسبقًا، ولا يقدّم أمانًا من النوع.
- يخزّن Proto DataStore البيانات كمثيلات لنوع بيانات مخصّص. يتطلّب هذا التنفيذ منك تحديد مخطّط باستخدام مخازن رسائل بروتوكول، ولكنه يضمن أمان نوع البيانات.
استخدام DataStore بشكل صحيح
لاستخدام DataStore بشكل صحيح، يجب دائمًا مراعاة القواعد التالية:
لا تُنشئ أبدًا أكثر من مثيل واحد من
DataStore
لملف معيّن في العملية نفسها. ويمكن أن يؤدي ذلك إلى إيقاف جميع وظائف DataStore. إذا كانت هناك مخازن بيانات متعددة نشطة لملف معيّن في العملية نفسها، سيُعرِض DataStore سوىIllegalStateException
عند قراءة البيانات أو تعديلها.يجب أن يكون النوع العام لمستودع البيانات
غير قابل للتغيير. يؤدي تغيير نوع يستخدم في DataStore إلى إلغاء أي ضمانات يوفّرها DataStore وإلى إنشاء أخطاء خطيرة يصعب رصدها. ننصحك بشدة باستخدام مخازن بروتوكول البيانات التي تضمن عدم التغير وواجهة برمجة تطبيقات بسيطة و تسلسلًا فعّالاً.لا تُخلط أبدًا استخدامات
SingleProcessDataStore
وMultiProcessDataStore
للملف نفسه. إذا كنت تريد الوصول إلىDataStore
من أكثر من عملية واحدة، استخدِم دائمًاMultiProcessDataStore
.
ضبط إعدادات الجهاز
لاستخدام Jetpack DataStore في تطبيقك، أضِف ما يلي إلى ملف Gradle، استنادًا إلى طريقة التنفيذ التي تريد استخدامها:
Preferences DataStore
Groovy
// 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" }
Kotlin
// 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
Groovy
// 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" }
Kotlin
// 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
للحفاظ على أزواج المفتاح/القيمة البسيطة على القرص.
إنشاء ملف Preferences DataStore
استخدِم عنصر التحكم النائب للموقع الذي أنشأه preferencesDataStore
لإنشاء مثيل من DataStore<Preferences>
. يمكنك استدعاؤها مرة واحدة في أعلى مستوى من ملف Kotlin، والوصول إليها من خلال هذه السمة في بقية أجزاء تطبيقك. يسهّل ذلك إبقاء DataStore
كعنصر فريد. بدلاً من ذلك، استخدِم 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, /*name=*/ "settings").build();
القراءة من Preferences 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 دالة
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. لمعرفة مزيد من المعلومات عن تحديد مخطّط proto
، يُرجى الاطّلاع على دليل لغة protobuf.
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
هو النوع المحدَّد في ملف proto. تُعلم فئة أداة التحويل هذه DataStore كيفية قراءة نوع بياناتك وكتابته. تأكَّد من تضمين قيمة تلقائية لسلسلة الفهرسة لكي يتم استخدامها في حال عدم إنشاء ملف حتى الآن. - استخدِم عنصر التحكم في البيانات الذي أنشأه
dataStore
لإنشاء مثيلDataStore<T>
، حيث يكونT
هو النوع المحدّد في ملف proto. يمكنك استدعاء هذه السلسلة مرة واحدة في أعلى مستوى من ملف kotlin والوصول إليها من خلال ملف ملف الخصائص المفوَّض في بقية أجزاء تطبيقك. تُخبر المَعلمةfilename
DataStore بالملف الذي يجب استخدامه لتخزين البيانات، وتُخبر المَعلمةserializer
DataStore باسم فئة أداة التحويل إلى سلسلة برمجية المحدَّدة في الخطوة 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() { 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
للسمة المناسبة من العنصر المخزّن.
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 هي أنّها غير متزامنة، ولكن قد لا يكون من الممكن دائمًا تغيير الرمز المحيط بها ليصبح غير متزامن. قد يحدث ذلك إذا كنت تعمل مع قاعدة بيانات حالية تستخدِم قراءة/كتابة ملف على القرص غير متزامنة أو إذا كانت لديك تبعية لا توفّر واجهة برمجة تطبيقات غير متزامنة.
توفّر وحدات التشغيل المتعدّد للكوتلين runBlocking()
أداة إنشاء وحدات التشغيل المتعدّد للمساعدة في سد الفجوة بين الرمز البرمجي المتزامن وغير المتزامن. يمكنك استخدام runBlocking()
لقراءة البيانات من DataStore بشكل متزامن.
يوفّر RxJava طرق حظر على Flowable
. تحظر التعليمة البرمجية التالية سلسلتَي الرسائل المُرسِلة والمُستلِمة إلى أن يعرض DataStore البيانات:
Kotlin
val exampleData = runBlocking { context.dataStore.data.first() }
Java
Settings settings = dataStore.data().blockingFirst();
يمكن أن يؤدي تنفيذ عمليات الإدخال والإخراج المتزامنة في سلسلة مهام واجهة المستخدم إلى حدوث أخطاء ANR أو تقطُّع في واجهة المستخدم. يمكنك تخفيف هذه المشاكل من خلال التحميل المُسبق غير المتزامن لข้อมูล من 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()
أسرع أو قد تتجنّب عملية نقل بيانات بين الوسائط في القرص تمامًا إذا اكتملت عملية القراءة الأولية.
استخدام DataStore في رمز متعدّد العمليات
.يمكنك ضبط DataStore للوصول إلى البيانات نفسها في عمليات مختلفة، مع ضمانات اتساق البيانات نفسها التي تحصل عليها من عملية واحدة. ويضمن DataStore، على سبيل المثال، ما يلي:
- لا تعرض عمليات القراءة سوى البيانات التي تم الاحتفاظ بها على القرص.
- اتّساق القراءة بعد الكتابة
- يتم تسلسل عمليات الكتابة.
- لا يتم حظر عمليات القراءة مطلقًا بواسطة عمليات الكتابة.
لنفترض أنّ لدينا نموذج تطبيق يتضمّن خدمة ونشاطًا:
يتم تشغيل الخدمة في عملية منفصلة وتُجري تعديلات بشكل دوري على 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 واجهة برمجة تطبيقات لمعالجة التلف يمكنها مساعدتك في الاسترداد بنجاح في مثل هذا السيناريو، وتجنُّب طرح الاستثناء. عند ضبطه، يستبدل معالج الفساد الملفّ الذي يتضمّن أخطاء بملفّ جديد يحتوي على قيمة تلقائية محدّدة مسبقًا.
لإعداد هذا المعالِج، قدِّم corruptionHandler
عند إنشاء مثيل
DataStore في by dataStore()
أو في DataStoreFactory
method:
val dataStore: DataStore<Settings> = DataStoreFactory.create(
serializer = SettingsSerializer(),
produceFile = {
File("${context.cacheDir.path}/myapp.preferences_pb")
},
corruptionHandler = ReplaceFileCorruptionHandler { Settings(lastUpdate = 0) }
)
تقديم تعليقات
يمكنك مشاركة ملاحظاتك وأفكارك معنا من خلال هذه المراجع:
- أداة تتبُّع المشاكل
- الإبلاغ عن المشاكل لنتمكّن من حلّ الأخطاء
مصادر إضافية
لمعرفة المزيد من المعلومات عن Jetpack DataStore، اطّلِع على المراجع الإضافية التالية:
نماذج
المدوّنات
الدروس التطبيقية حول الترميز
أفلام مُقترَحة لك
- ملاحظة: يتم عرض نص الرابط عندما تكون لغة JavaScript غير مفعّلة.
- تحميل البيانات المفصّلة وعرضها
- نظرة عامة على LiveData
- التنسيقات وتعبيرات الربط