توفّر "خدمة الاحتفاظ بنسخة احتياطية من البيانات في Android" ميزة الاحتفاظ بنسخة احتياطية من بيانات ملفّات "المفتاح/القيمة" واستعادتها في مساحة التخزين في السحابة الإلكترونية في تطبيق Android. أثناء عملية الاحتفاظ بنسخة احتياطية من بيانات ملفّات "المفتاح/القيمة"، يتم تمرير بيانات ملفّات "المفتاح/القيمة" الاحتياطية إلى وسيط نقل النُسخ الاحتياطية للجهاز. إذا كان الجهاز يستخدم أسلوب النقل التلقائي للنسخ الاحتياطي من Google، يتم تمرير البيانات إلى "خدمة الاحتفاظ بنسخة احتياطية" في Android لأغراض الأرشفة.
تقتصر البيانات على 5 ميغابايت لكل مستخدم لتطبيقك. ليست هناك أي رسوم مقابل التخزين بيانات النسخ الاحتياطي.
للحصول على نظرة عامة على خيارات الاحتفاظ بنسخة احتياطية من البيانات في Android وإرشادات حول البيانات التي يجب الاحتفاظ بنسخة احتياطية منها واستعادتها، يُرجى الاطّلاع على نظرة عامة على الاحتفاظ بنسخة احتياطية من البيانات.
تنفيذ ميزة الاحتفاظ بنسخة احتياطية من المفتاح/القيمة
للاحتفاظ بنسخة احتياطية من بيانات تطبيقك، عليك تنفيذ وكيل احتياطي. يُطلِق "مدير الاحتفاظ بنسخة احتياطية" وكيل الاحتفاظ بنسخة احتياطية أثناء عملية الاحتفاظ بنسخة احتياطية واستعادتها.
لتنفيذ وكيل احتياطي، يجب:
حدِّد وكيل الخدمة الاحتياطية في ملف البيان باستخدام سمة
android:backupAgent
.حدِّد الوكيل الاحتياطي من خلال تنفيذ أحد الإجراءات التالية:
-
تقدّم فئة
BackupAgent
الواجهة المركزية التي يستخدمها تطبيقك للتواصل مع مدير النسخة الاحتياطية. في حال إضافة هذه الفئة مباشرةً، عليك إلغاءonBackup()
وonRestore()
لمعالجة عمليات الاحتفاظ بنسخة احتياطية من بياناتك واستعادتها. -
تشير رسالة الأشكال البيانية
BackupAgentHelper
غلافًا مريحًا حول فئةBackupAgent
، مما يقلل من مقدار التعليمات البرمجية التي تحتاج إلى كتابتها. فيBackupAgentHelper
، يجب استخدام عنصر مساعد واحد أو أكثر، والذي يؤدي إلى الاحتفاظ بنسخة احتياطية من أنواع معيّنة من البيانات واستعادتها تلقائيًا، لكي لا تحتاج إلى تنفيذonBackup()
وonRestore()
. ما لم تكن بحاجة إلى كامل للتحكّم في النُسخ الاحتياطية من بيانات التطبيق، ننصحك باستخدامBackupAgentHelper
لمعالجة النُسخ الاحتياطية للتطبيق.يقدّم Android حاليًا أدوات مساعدة للنسخ الاحتياطي تعمل على الاحتفاظ بنسخة احتياطية من الملفات الكاملة واستعادتها من
SharedPreferences
و مساحة التخزين الداخلية.
-
إدراج وكيل الاحتفاظ بنسخة احتياطية في البيان
بعد اختيار اسم الفئة لوكيل الخدمة الاحتياطية، يجب الإفصاح عنه فيملف الادّعاء باستخدام سمة android:backupAgent
في علامة
<application>
.
مثلاً:
<manifest ... > ... <application android:label="MyApplication" android:backupAgent="MyBackupAgent"> <meta-data android:name="com.google.android.backup.api_key" android:value="unused" /> <activity ... > ... </activity> </application> </manifest>
للتوافق مع الأجهزة القديمة، ننصحك بإضافة مفتاح واجهة برمجة التطبيقات <meta-data>
إلى ملف بيان Android. لم تعد خدمة Android Backup Service تتطلب
مفتاح الخدمة، ولكن قد تستمر بعض الأجهزة القديمة في البحث عن مفتاح عند الاحتفاظ بنسخة احتياطية منه.
لأعلى. يجب ضبط السمة android:name
على com.google.android.backup.api_key
android:value
إلى unused
تأخذ سمة
android:restoreAnyVersion
قيمة منطقية للإشارة إلى ما إذا كنت تريد استعادة data
التطبيق بغض النظر عن إصدار التطبيق الحالي مقارنةً بالإصدار الذي
أنشأ البيانات الاحتياطية. القيمة التلقائية هي false
. راجع التحقق من الاستعادة
البيانات لمزيد من المعلومات.
توسيع BackupAgentHelper
يجب إنشاء وكيل النسخ الاحتياطي باستخدام BackupAgentHelper
إذا كنت تريد
الاحتفاظ بنسخة احتياطية من الملفات الكاملة من SharedPreferences
أو مساحة التخزين الداخلية.
يتطلّب إنشاء الوكيل الاحتياطي باستخدام BackupAgentHelper
رمزًا أقل بكثير من الرمز
توسيع BackupAgent
، لأنك لست مضطرًا لتنفيذ onBackup()
onRestore()
يجب أن يستخدم تطبيقك لنظام BackupAgentHelper
مساعدًا واحدًا أو أكثر لإنشاء النُسخ الاحتياطية.
مساعد الاحتفاظ بنسخة احتياطية هو مكوّن مخصّص يستدعيه BackupAgentHelper
للقيام بعمليات الاحتفاظ بنسخة احتياطية واستعادة نوع معيّن من البيانات. يقدّم
إطار عمل Android حاليًا مساعدَين مختلفَين:
SharedPreferencesBackupHelper
للحفاظ على نسخة احتياطية منSharedPreferences
ملفFileBackupHelper
للاحتفاظ بنسخة احتياطية من الملفات في وحدة التخزين الداخلية
يمكنك تضمين عدة مساعدين في BackupAgentHelper
، ولكن لا يلزم سوى
مساعد واحد لكل نوع بيانات. أي، إذا كان لديك العديد
SharedPreferences
ملف، ستحتاج إذًا إلى ملف واحد فقط.
SharedPreferencesBackupHelper
لكلّ مساعد تريد إضافته إلى BackupAgentHelper
، عليك تنفيذ الخطوات التالية أثناء استخدام أسلوب
onCreate()
:
- إنشاء مثيل لفئة المساعد المطلوبة. في الصف الدالة الإنشائية، فيجب تحديد الملفات التي تريد الاحتفاظ بنسخة احتياطية منها.
- اتّصل برقم
addHelper()
لإضافة المساعد إلىBackupAgentHelper
.
توضّح الأقسام التالية كيفية إنشاء وكيل احتياطي باستخدام كل من المساعِدين المتاحين.
الاحتفاظ بنسخة احتياطية من SharedPreferences
عند إنشاء مثيل SharedPreferencesBackupHelper
، يجب تضمين الرمز
باسم ملف أو أكثر من ملفات SharedPreferences
.
على سبيل المثال، للاحتفاظ بنسخة احتياطية من ملف SharedPreferences
باسم user_preferences
، يظهر
وكيل الاحتفاظ بنسخة احتياطية الكامل الذي يستخدم BackupAgentHelper
على النحو التالي:
Kotlin
// The name of the SharedPreferences file const val PREFS = "user_preferences" // A key to uniquely identify the set of backup data const val PREFS_BACKUP_KEY = "prefs" class MyPrefsBackupAgent : BackupAgentHelper() { override fun onCreate() { // Allocate a helper and add it to the backup agent SharedPreferencesBackupHelper(this, PREFS).also { addHelper(PREFS_BACKUP_KEY, it) } } }
Java
public class MyPrefsBackupAgent extends BackupAgentHelper { // The name of the SharedPreferences file static final String PREFS = "user_preferences"; // A key to uniquely identify the set of backup data static final String PREFS_BACKUP_KEY = "prefs"; // Allocate a helper and add it to the backup agent @Override public void onCreate() { SharedPreferencesBackupHelper helper = new SharedPreferencesBackupHelper(this, PREFS); addHelper(PREFS_BACKUP_KEY, helper); } }
يتضمن SharedPreferencesBackupHelper
جميع الرموز المطلوبة للاحتفاظ بنسخة احتياطية
استعادة ملف SharedPreferences
.
عندما يتصل "مدير الاحتفاظ بنسخة احتياطية" بـ onBackup()
وonRestore()
،
يتصل "BackupAgentHelper
" بمساعدي النسخ الاحتياطي للاحتفاظ بنسخة احتياطية واستعادتها
الملفات المحددة.
الاحتفاظ بنسخة احتياطية من الملفات الأخرى
عند إنشاء مثيل FileBackupHelper
، يجب تضمين اسم ملف واحد أو
أكثر تم حفظه في مساحة التخزين الداخلية لتطبيقك، كما هو محدّد في
getFilesDir()
،
وهو الموقع الجغرافي نفسه الذي يكتب فيه
openFileOutput()
الملفات.
على سبيل المثال، للاحتفاظ بنسخة احتياطية من ملفين باسم scores
وstats
، وكيل احتياطي
يبدو أن استخدام BackupAgentHelper
هكذا:
Kotlin
// The name of the file const val TOP_SCORES = "scores" const val PLAYER_STATS = "stats" // A key to uniquely identify the set of backup data const val FILES_BACKUP_KEY = "myfiles" class MyFileBackupAgent : BackupAgentHelper() { override fun onCreate() { // Allocate a helper and add it to the backup agent FileBackupHelper(this, TOP_SCORES, PLAYER_STATS).also { addHelper(FILES_BACKUP_KEY, it) } } }
Java
public class MyFileBackupAgent extends BackupAgentHelper { // The name of the file static final String TOP_SCORES = "scores"; static final String PLAYER_STATS = "stats"; // A key to uniquely identify the set of backup data static final String FILES_BACKUP_KEY = "myfiles"; // Allocate a helper and add it to the backup agent @Override public void onCreate() { FileBackupHelper helper = new FileBackupHelper(this, TOP_SCORES, PLAYER_STATS); addHelper(FILES_BACKUP_KEY, helper); } }
يتضمّن FileBackupHelper
كل الرموز اللازمة للاحتفاظ بنسخة احتياطية من البيانات واستعادتها.
الملفات المحفوظة في وحدة التخزين الداخلية لتطبيقك.
ومع ذلك، فإنّ قراءة الملفات وكتابتها على وحدة التخزين الداخلية غير متوافقة مع خيوط المعالجة. لضمان عدم قراءة وكيل الاحتفاظ بنسخة احتياطية لملفاتك أو كتابتها في الوقت نفسه الذي تُجري فيه أنشطتك، يجب استخدام عبارات متزامنة في كل مرة تُجري فيها عملية قراءة أو كتابة. على سبيل المثال، في أي نشاط تقرأ فيه الملف وتكتبه، تحتاج إلى عنصر لاستخدامه كقفل أساسي لصيغة المزامنة:
Kotlin
// Object for intrinsic lock companion object { val sDataLock = Any() }
Java
// Object for intrinsic lock static final Object sDataLock = new Object();
بعد ذلك، أنشئ بيانًا متزامنًا باستخدام هذا القفل في كل مرة تقرأ فيها الملفات أو تكتب فيها. على سبيل المثال، إليك عبارة متزامنة لكتابة أحدث نتيجة في لعبة في ملف:
Kotlin
try { synchronized(MyActivity.sDataLock) { val dataFile = File(filesDir, TOP_SCORES) RandomAccessFile(dataFile, "rw").apply { writeInt(score) } } } catch (e: IOException) { Log.e(TAG, "Unable to write to file") }
Java
try { synchronized (MyActivity.sDataLock) { File dataFile = new File(getFilesDir(), TOP_SCORES); RandomAccessFile raFile = new RandomAccessFile(dataFile, "rw"); raFile.writeInt(score); } } catch (IOException e) { Log.e(TAG, "Unable to write to file"); }
يجب مزامنة عبارات القراءة مع القفل نفسه.
بعد ذلك، في BackupAgentHelper
، عليك إلغاء onBackup()
و
onRestore()
لمزامنة عمليات الاحتفاظ بنسخة احتياطية والاستعادة باستخدام
القفل الأساسي نفسه. على سبيل المثال، يحتاج مثال MyFileBackupAgent
أعلاه إلى
الطرق التالية:
Kotlin
@Throws(IOException::class) override fun onBackup( oldState: ParcelFileDescriptor, data: BackupDataOutput, newState: ParcelFileDescriptor ) { // Hold the lock while the FileBackupHelper performs back up synchronized(MyActivity.sDataLock) { super.onBackup(oldState, data, newState) } } @Throws(IOException::class) override fun onRestore( data: BackupDataInput, appVersionCode: Int, newState: ParcelFileDescriptor ) { // Hold the lock while the FileBackupHelper restores the file synchronized(MyActivity.sDataLock) { super.onRestore(data, appVersionCode, newState) } }
Java
@Override public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState) throws IOException { // Hold the lock while the FileBackupHelper performs back up synchronized (MyActivity.sDataLock) { super.onBackup(oldState, data, newState); } } @Override public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) throws IOException { // Hold the lock while the FileBackupHelper restores the file synchronized (MyActivity.sDataLock) { super.onRestore(data, appVersionCode, newState); } }
Extend BackupAgent
من المفترض ألا تحتاج معظم التطبيقات إلى توسيع فئة BackupAgent
مباشرةً، ولكن يجب بدلاً من ذلك توسيع BackupAgentHelper
للاستفادة من
الفئات المساعِدة المضمّنة التي تحتفظ بنسخة احتياطية من ملفاتك وتُعيدها تلقائيًا.
ويمكنك تمديد مهلة BackupAgent
مباشرةً لتنفيذ ما يلي:
- حدِّد إصدارًا لتنسيق البيانات. على سبيل المثال، إذا كنت تتوقع الحاجة إلى ومراجعة التنسيق الذي تكتب به بيانات تطبيقك، يمكنك إنشاء نسخة احتياطية فحص إصدار تطبيقك أثناء عملية الاستعادة وتنفيذ أي توافق ضروري إذا كان الإصدار على الجهاز مختلفًا مقارنة ببيانات النسخ الاحتياطي. لمزيد من المعلومات، يُرجى الاطّلاع على التحقّق من إصدار data المراد استعادتها.
- حدِّد أجزاء البيانات التي تريد الاحتفاظ بنسخة احتياطية منها. بدلاً من إجراء نسخ احتياطي بالكامل يمكنك تحديد أجزاء البيانات المراد الاحتفاظ بنسخة احتياطية منها وكيفية ترتيب ثم تتم استعادته إلى الجهاز. ويمكن أن يساعدك ذلك أيضًا في إدارة الإصدارات المختلفة، لأنّك تقرأ بياناتك وتكتبها كعناصر فريدة، بدلاً من الملفات الكاملة.
- الاحتفاظ بنسخة احتياطية من البيانات في قاعدة بيانات إذا كانت لديك قاعدة بيانات SQLite تريد
استعادتها عندما يعيد المستخدم تثبيت تطبيقك، عليك إنشاء
BackupAgent
مخصّص يقرأ البيانات المناسبة أثناء عملية الاحتفاظ بنسخة احتياطية، ثم إنشاء الجدول وإدخال البيانات أثناء عملية الاستعادة.
إذا لم تكن بحاجة إلى تنفيذ أي من المهام المذكورة أعلاه وتريد الاحتفاظ بنسخة احتياطية
إكمال الملفات من SharedPreferences
أو وحدة التخزين الداخلية، راجع تمديد
BackupAgentHelper
الطرق المطلوبة
عند إنشاء BackupAgent
، يجب تنفيذ متدّجات callback التالية:
onBackup()
- يستدعي مدير النسخ الاحتياطي هذه الطريقة بعد طلب الاحتفاظ بنسخة احتياطية. في هذه الطريقة، تتم قراءة بيانات تطبيقك من الجهاز ونقل البيانات التي تريد الاحتفاظ بنسخة احتياطية منها إلى "مدير النسخة الاحتياطية"، كما هو описан في الاحتفاظ بنسخة احتياطية.
onRestore()
يستدعي "مدير النسخة الاحتياطية" هذه الطريقة أثناء عملية الاستعادة. هذه الطريقة يوفّر بيانات النسخة الاحتياطية، وبالتالي يمكن لتطبيقك استخدامها لاستعادة البيانات السابقة. كما هو موضح في إجراء عملية استعادة.
يطلب النظام هذه الطريقة لاستعادة أي بيانات للنسخة الاحتياطية عندما يعيد تثبيت تطبيقك، ولكن يمكن لتطبيقك أيضًا طلب استعادة البيانات.
الاحتفاظ بنسخة احتياطية
لا يؤدي طلب احتياطي إلى إجراء مكالمة فورية إلى onBackup()
طريقة الدفع. وبدلاً من ذلك، ينتظر "مدير النسخ الاحتياطي" الوقت المناسب، ثم
عمل نسخة احتياطية لجميع التطبيقات التي طلبت الاحتفاظ بنسخة احتياطية منذ آخر مرة
النسخ الاحتياطي. هذه هي النقطة التي يجب فيها تقديم بيانات تطبيقك
إلى "مدير الاحتفاظ بنسخة احتياطية" حتى يمكن حفظها في مساحة التخزين السحابي.
لا يمكن إلا لمدير الاحتفاظ بنسخة احتياطية استدعاء طريقة onBackup()
لوكيل الاحتفاظ بنسخة احتياطية. على كل
عندما تتغير بيانات التطبيق وأردت الاحتفاظ بنسخة احتياطية، يجب
فطلب إجراء عملية نسخ احتياطي من خلال الاتصال
dataChanged()
يمكنك الاطّلاع على طلب نسخة احتياطية للحصول على مزيد من المعلومات.
ملاحظة: أثناء تطوير تطبيقك، يمكنك بدء عملية الاحتفاظ بنسخة احتياطية
فورية من "مدير الاحتفاظ بنسخة احتياطية" باستخدام أداة bmgr
.
عندما يستدعي "مدير النُسخ الاحتياطية" طريقة onBackup()
، يتم تمرير ثلاث مرات
المَعلمات:
oldState
- ملف مفتوح للقراءة فقط
ParcelFileDescriptor
يشير إلى آخر حالة نسخة احتياطية يوفّرها تطبيقك. هذه ليست البيانات الاحتياطية من مساحة التخزين في السحابة الإلكترونية، بل تمثيل محلي للبيانات التي تم الاحتفاظ بنسخة احتياطية منها في آخر مرة تم فيها استدعاءonBackup()
، كما هو محدّد فيnewState
أو منonRestore()
. سيتم تناولonRestore()
في القسم التالي. بما أنّonBackup()
لا يسمح لك بقراءة البيانات الاحتياطية الحالية في مساحة تخزين السحابة الإلكترونية، يمكنك استخدام هذا التمثيل المحلي لتحديد ما إذا كانت بياناتك قد تغيّرت منذ آخر نسخة احتياطية. data
BackupDataOutput
الذي تستخدمه لتسليم بيانات النسخة الاحتياطية إلى مدير النسخ الاحتياطي.newState
ParcelFileDescriptor
مفتوح للقراءة/الكتابة يشير إلى ملف يجب فيه كتابة تمثيل للبيانات التي تم إرسالها إلىdata
يمكن أن يكون التمثيل بسيطًا مثل الطابع الزمني لآخر تعديل على ملفك. يتم عرض هذا العنصر على أنّهoldState
في المرة التالية التي يتصل فيها "مدير الاحتفاظ بنسخة احتياطية" طريقةonBackup()
. إذا لم تكتب بياناتك الاحتياطية فيnewState
، ستشيرoldState
ملفًا فارغًا في المرة التالية التي يتصل فيها "مدير النسخة الاحتياطية"onBackup()
.
باستخدام هذه المَعلمات، يمكنك تنفيذ طريقة onBackup()
لإجراء ما يلي:
تحقّق مما إذا كانت بياناتك قد تغيّرت منذ آخر عملية احتياطية من خلال مقارنة
oldState
ببياناتك الحالية. تعتمد طريقة قراءة البيانات فيoldState
على طريقة كتابتها فيnewState
في الأصل (راجِع الخطوة 3). إنّ أسهل طريقة لتسجيل حالة ملف هي باستخدام الطابع الزمني لآخر تعديل. على سبيل المثال: إليك كيفية قراءة طابع زمني من "oldState
" ومقارنته:Kotlin
val instream = FileInputStream(oldState.fileDescriptor) val dataInputStream = DataInputStream(instream) try { // Get the last modified timestamp from the state file and data file val stateModified = dataInputStream.readLong() val fileModified: Long = dataFile.lastModified() if (stateModified != fileModified) { // The file has been modified, so do a backup // Or the time on the device changed, so be safe and do a backup } else { // Don't back up because the file hasn't changed return } } catch (e: IOException) { // Unable to read state file... be safe and do a backup }
Java
// Get the oldState input stream FileInputStream instream = new FileInputStream(oldState.getFileDescriptor()); DataInputStream in = new DataInputStream(instream); try { // Get the last modified timestamp from the state file and data file long stateModified = in.readLong(); long fileModified = dataFile.lastModified(); if (stateModified != fileModified) { // The file has been modified, so do a backup // Or the time on the device changed, so be safe and do a backup } else { // Don't back up because the file hasn't changed return; } } catch (IOException e) { // Unable to read state file... be safe and do a backup }
إذا لم يحدث أي تغيير ولا تحتاج إلى الاحتفاظ بنسخة احتياطية، انتقِل إلى الخطوة 3.
إذا تغيّرت بياناتك مقارنةً بـ
oldState
، اكتب البيانات الحالية فيdata
لنسخها احتياطيًا في مساحة التخزين السحابي.يجب كتابة كل مجموعة من البيانات ككيان في
BackupDataOutput
. إنّ سجل بيانات ثنائي مسطح يتم تحديده بواسطة مفتاح فريد السلسلة. وبالتالي، فإن مجموعة البيانات التي تقوم بعمل نسخة احتياطية منها هي من الناحية النظرية مجموعة من أزواج المفتاح/القيمة.لإضافة عنصر إلى مجموعة بيانات النسخ الاحتياطي، يجب عليك:
استخدِم الدالة
writeEntityHeader()
، مع تمرير مفتاح سلسلة فريد للبيانات التي تريد كتابتها و حجم البيانات.اتصل
writeEntityData()
, تمرير مخزن بايت مؤقت يحتوي على بياناتك وعدد وحدات البايت إلى تكتب من المخزن المؤقت، والذي يجب أن يطابق الحجم الذي تم تمريره إلىwriteEntityHeader()
على سبيل المثال، يعمل الرمز التالي على تسطيح بعض البيانات في تدفق بايت تكتبه في كيان واحد:
Kotlin
val buffer: ByteArray = ByteArrayOutputStream().run { DataOutputStream(this).apply { writeInt(playerName) writeInt(playerScore) } toByteArray() } val len: Int = buffer.size data.apply { writeEntityHeader(TOPSCORE_BACKUP_KEY, len) writeEntityData(buffer, len) }
Java
// Create buffer stream and data output stream for our data ByteArrayOutputStream bufStream = new ByteArrayOutputStream(); DataOutputStream outWriter = new DataOutputStream(bufStream); // Write structured data outWriter.writeUTF(playerName); outWriter.writeInt(playerScore); // Send the data to the Backup Manager via the BackupDataOutput byte[] buffer = bufStream.toByteArray(); int len = buffer.length; data.writeEntityHeader(TOPSCORE_BACKUP_KEY, len); data.writeEntityData(buffer, len);
نفِّذ ذلك لكل جزء من البيانات التي تريد الاحتفاظ بنسخة احتياطية منها. طريقة التقسيم بياناتك إلى كيانات متروكة لك. يمكنك أيضًا استخدام عنصر واحد فقط.
سواء كنت تقوم بإجراء نسخة احتياطية (في الخطوة 2)، اكتب تمثيلاً البيانات الحالية إلى
ParcelFileDescriptor
newState
يحتفظ "مدير الاحتفاظ بنسخة احتياطية" بهذا العنصر محليًا كتمثيل للبيانات التي يتم الاحتفاظ بنسخة احتياطية منها حاليًا. يرسل هذا العنوان إليك باسمoldState
في المرة القادمة يتم استدعاء دالةonBackup()
حتى تتمكن من تحديد ما إذا كانت هناك نسخة احتياطية أخرى ضروريًا، كما تم التعامل معه في الخطوة 1. في حال عدم كتابة حالة البيانات الحالية في هذا الملف، سيكونoldState
فارغًا أثناء طلب إعادة الاتصال التالي.يحفظ المثال التالي تمثيلاً للبيانات الحالية في
newState
باستخدام الطابع الزمني لآخر تعديل على الملف:Kotlin
val modified = dataFile.lastModified() FileOutputStream(newState.fileDescriptor).also { DataOutputStream(it).apply { writeLong(modified) } }
Java
FileOutputStream outstream = new FileOutputStream(newState.getFileDescriptor()); DataOutputStream out = new DataOutputStream(outstream); long modified = dataFile.lastModified(); out.writeLong(modified);
إجراء عملية استعادة
عندما يحين وقت استعادة بيانات تطبيقك، يُطلِق "مدير الاحتفاظ بنسخة احتياطية" طريقة onRestore()
لوكيل الاحتفاظ بنسخة احتياطية. عند استدعاء هذه الطريقة، يقدّم "مدير الاحتفاظ بنسخة احتياطية"
بياناتك الاحتياطية لتتمكّن من استعادتها على الجهاز.
يمكن لمدير الاحتفاظ بنسخة احتياطية فقط الاتصال بخدمة onRestore()
، ويحدث ذلك تلقائيًا.
عندما يثبّت النظام تطبيقك ويعثر على بيانات النسخة الاحتياطية الحالية
عندما يستدعي "مدير الاحتفاظ بنسخة احتياطية" طريقة onRestore()
، يتم تمرير ثلاثة
مَعلمات:
data
- كائن
BackupDataInput
، الذي يسمح لك بقراءة بيانات النسخة الاحتياطية. appVersionCode
- عدد صحيح يمثّل قيمة سمة بيان تطبيقك
android:versionCode
، كما كانت عليه عند الاحتفاظ بنسخة احتياطية من هذه البيانات. يمكنك استخدام هذه الميزة لتحقّق من إصدار التطبيق الحالي وتحديد ما إذا كان تنسيق البيانات متوافقًا. لمزيد من المعلومات حول استخدام هذا للتعامل مع مختلف استعادة البيانات، فراجع التحقق من استعادة البيانات الإصدار. newState
- ملف
ParcelFileDescriptor
مفتوح للقراءة/الكتابة يشير إلى ملف يجب أن يكتب حالة النسخ الاحتياطي النهائية التي تم توفيرها معdata
. يتم عرض هذا العنصر كoldState
في المرة التالية التي يتم فيها استدعاءonBackup()
. تذكَّر أنّه يجب أيضًا كتابة عنصرnewState
نفسه فيonBackup()
ردّ الاتصال، ويضمن إجراء ذلك هنا أيضًا أنّ عنصرoldState
الذي تم تقديمهonBackup()
صالح حتى في المرة الأولى التي يتم فيها استدعاءonBackup()
بعد استعادة الجهاز.
في عملية تنفيذ onRestore()
، عليك استدعاء
readNextHeader()
على data
للتنقّل في جميع الكيانات في مجموعة البيانات. لكل كيان
التي تم العثور عليها، قم بما يلي:
- احصل على مفتاح الكيان باستخدام
getKey()
. مقارنة مفتاح الكيان بقائمة من القيم الأساسية المعروفة التي يجب أن تكون لديك تعريفها كسلاسل نهائية ثابتة داخل فئة
BackupAgent
. عندما يتطابق المفتاح مع إحدى سلاسل المفاتيح المعروفة، أدخِل عبارة لاستخراج بيانات العنصر وحفظها على الجهاز:- الحصول على حجم بيانات الكيان باستخدام
getDataSize()
وتُنشئ صفيفة بايت بهذا الحجم. - اتصل
readEntityData()
ونمرره صفيف البايت، وهو المكان الذي ستنتقل إليه البيانات، وحدد إزاحة البداية والحجم المراد قراءته. - صفيفة البايت لديك ممتلئة الآن. قراءة البيانات وكتابتها على الجهاز على النحو الذي تريده
- الحصول على حجم بيانات الكيان باستخدام
بعد قراءة بياناتك وكتابتها مرة أخرى على الجهاز، اكتب حالة بياناتك في المَعلمة
newState
بالطريقة نفسها التي تتبعها أثناءonBackup()
.
على سبيل المثال، إليك كيفية استعادة البيانات التي تم الاحتفاظ بنسخة احتياطية منها باستخدام المثال في القسم السابق:
Kotlin
@Throws(IOException::class) override fun onRestore(data: BackupDataInput, appVersionCode: Int, newState: ParcelFileDescriptor) { with(data) { // There should be only one entity, but the safest // way to consume it is using a while loop while (readNextHeader()) { when(key) { TOPSCORE_BACKUP_KEY -> { val dataBuf = ByteArray(dataSize).also { readEntityData(it, 0, dataSize) } ByteArrayInputStream(dataBuf).also { DataInputStream(it).apply { // Read the player name and score from the backup data playerName = readUTF() playerScore = readInt() } // Record the score on the device (to a file or something) recordScore(playerName, playerScore) } } else -> skipEntityData() } } } // Finally, write to the state blob (newState) that describes the restored data FileOutputStream(newState.fileDescriptor).also { DataOutputStream(it).apply { writeUTF(playerName) writeInt(mPlayerScore) } } }
Java
@Override public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) throws IOException { // There should be only one entity, but the safest // way to consume it is using a while loop while (data.readNextHeader()) { String key = data.getKey(); int dataSize = data.getDataSize(); // If the key is ours (for saving top score). Note this key was used when // we wrote the backup entity header if (TOPSCORE_BACKUP_KEY.equals(key)) { // Create an input stream for the BackupDataInput byte[] dataBuf = new byte[dataSize]; data.readEntityData(dataBuf, 0, dataSize); ByteArrayInputStream baStream = new ByteArrayInputStream(dataBuf); DataInputStream in = new DataInputStream(baStream); // Read the player name and score from the backup data playerName = in.readUTF(); playerScore = in.readInt(); // Record the score on the device (to a file or something) recordScore(playerName, playerScore); } else { // We don't know this entity key. Skip it. (Shouldn't happen.) data.skipEntityData(); } } // Finally, write to the state blob (newState) that describes the restored data FileOutputStream outstream = new FileOutputStream(newState.getFileDescriptor()); DataOutputStream out = new DataOutputStream(outstream); out.writeUTF(playerName); out.writeInt(mPlayerScore); }
في هذا المثال، لا يتم استخدام المَعلمة appVersionCode
التي تم تمريرها إلى onRestore()
. ومع ذلك، قد تحتاج إلى استخدامها إذا اخترت إجراء عملية احتياطية
عندما يعود المستخدم إلى إصدار سابق من التطبيق (على سبيل المثال،
انتقل المستخدم من الإصدار 1.5 من تطبيقك إلى الإصدار 1.0). لمزيد من المعلومات، يُرجى مراجعة
القسم التالي.
التحقّق من إصدار استعادة البيانات
عندما يحفظ "مدير النسخ الاحتياطي" بياناتك في التخزين في السحابة الإلكترونية، يتم تلقائيًا
يتضمّن إصدار تطبيقك، على النحو المحدّد في ملف البيان
android:versionCode
. قبل أن يطلب "مدير الاحتفاظ بنسخة احتياطية" من وكيل الاحتفاظ بنسخة احتياطية
استعادة بياناتك، يفحص android:versionCode
للتطبيق المثبَّت ويقارنه بالقيمة المسجّلة في مجموعة بيانات الاستعادة. إذا كان
الإصدار المسجَّل في مجموعة بيانات الاستعادة أحدث من إصدار التطبيق على
الجهاز، يعني ذلك أنّ المستخدم قد أعاد تثبيت إصدار أقدم من التطبيق. وفي هذه الحالة، سيوقف "مدير
الاحتفاظ بنسخة احتياطية" عملية استعادة تطبيقك ولن يستدعي onRestore()
، لأنّ مجموعة الاستعادة تُعدّ غير ذات صلة بالإصدار
الأقدم.
يمكنك إلغاء هذا السلوك باستخدام السمة android:restoreAnyVersion
.
اضبط هذه السمة على true
للإشارة إلى أنّك تريد استعادة التطبيق
بغض النظر عن إصدار مجموعة الاستعادة. تكون القيمة التلقائية false
. إذا أعددت هذا الخيار على true
، سيتجاهل "مدير النسخة الاحتياطية" القيمة android:versionCode
وسيستدعي طريقة onRestore()
في جميع الحالات. أثناء التنفيذ
لذا، يمكنك التحقّق يدويًا من اختلاف الإصدار في onRestore()
.
واتخاذ أي خطوات ضرورية لجعل البيانات متوافقة إذا كانت الإصدارات
غير متطابقين.
لمساعدتك في التعامل مع الإصدارات المختلفة أثناء عملية الاستعادة، تُرسِل لك طريقة
onRestore()
رمز الإصدار المُدرَج في مجموعة data
للاستعادة كمَعلمة appVersionCode
. يمكنك بعد ذلك طلب رمز الإصدار الحالي للتطبيق باستخدام الحقل
PackageInfo.versionCode
. مثلاً:
Kotlin
val info: PackageInfo? = try { packageManager.getPackageInfo(packageName, 0) } catch (e: PackageManager.NameNotFoundException) { null } val version: Int = info?.versionCode ?: 0
Java
PackageInfo info; try { String name = getPackageName(); info = getPackageManager().getPackageInfo(name, 0); } catch (NameNotFoundException nnfe) { info = null; } int version; if (info != null) { version = info.versionCode; }
بعد ذلك، قارِن version
الذي تم الحصول عليه من
PackageInfo
بمحاولة
appVersionCode
التي تم تمريرها إلى onRestore()
.
طلب نسخة احتياطية
يمكنك طلب إجراء عملية نسخ احتياطي في أي وقت من خلال الاتصال بالرقم dataChanged()
. هذا النمط
إلى مدير النسخ الاحتياطي أنك تريد الاحتفاظ بنسخة احتياطية من بياناتك باستخدام
الوكيل الاحتياطي لديك. يتصل "مدير النسخ الاحتياطي" بعد ذلك برقم
طريقة onBackup()
في الوقت في المستقبل. عادةً، عليك طلب ملف احتياطي في كل مرة تتغيّر فيها بياناتك (مثلاً عندما يغيّر المستخدم إعدادًا مفضّلاً في التطبيق تريد الاحتفاظ بنسخة احتياطية منه). إذا اتصلت بـ dataChanged()
عدة
مرات قبل أن يطلب "مدير الاحتفاظ بنسخة احتياطية" من موظّف الدعم إنشاء نسخة احتياطية، سيتلقّى موظّف الدعم
ما زال مكالمة واحدة فقط إلى onBackup()
.
طلب استعادة
خلال الحياة العادية لتطبيقك، لن تحتاج إلى طلب استعادة التطبيق. العملية. يتحقق النظام تلقائيًا من بيانات النسخ الاحتياطي ويجري استعادة البيانات عند تثبيت تطبيقك.
نقل البيانات إلى ميزة "الاحتفاظ التلقائي بنسخة احتياطية"
يمكنك نقل تطبيقك إلى استخدام ميزة "الاحتفاظ بنسخة احتياطية من البيانات الكاملة" من خلال ضبط قيمة android:fullBackupOnly
على true
في عنصر <application>
في ملف البيان. عند التشغيل
على جهاز يعمل بالإصدار 5.1 من نظام التشغيل Android (المستوى 22 لواجهة برمجة التطبيقات) أو إصدار أقدم، يتجاهل تطبيقك قيمة
هذه في البيان، ويواصل إجراء عمليات الاحتفاظ بنسخة احتياطية من المفاتيح والقيم. عند الجري
على جهاز يعمل بالإصدار Android 6.0 (المستوى 23 من واجهة برمجة التطبيقات) أو الإصدارات الأحدث، ينفِّذ تطبيقك ميزات تلقائية.
الاحتفاظ بنسخة احتياطية بدلاً من النسخ الاحتياطي لقيمة المفتاح.
خصوصية المستخدم
في Google، ندرك تمامًا الثقة التي يوليها المستخدمون لنا وفي مسئولية حماية حسابات المستخدمين الخصوصية. تنقل Google البيانات الاحتياطية بأمان من خوادم Google وإليها من أجل توفير ميزتَي "الاحتفاظ بنسخة احتياطية" و"استعادة البيانات". تتعامل Google مع هذه البيانات على أنّها معلومات شخصية بما يتوافق مع سياسة خصوصية Google.
بالإضافة إلى ذلك، يمكن للمستخدمين إيقاف وظيفة الاحتفاظ بنسخة احتياطية من البيانات من خلال إعدادات الاحتفاظ بنسخة احتياطية في نظام Android . عندما يوقف المستخدم ميزة "الاحتفاظ بنسخة احتياطية"، تحذف "خدمة الاحتفاظ بنسخة احتياطية" في Android جميع بيانات النسخة الاحتياطية المحفوظة. يمكن للمستخدم إعادة تفعيل ميزة "النسخ الاحتياطي" على الجهاز، ولكن لن تستعيد "خدمة النسخ الاحتياطي في Android" أي بيانات تم حذفها في السابق.