توفّر خدمة Android Backup Service نسخة احتياطية للتخزين في السحابة الإلكترونية واستعادتها لبيانات قيمة المفتاح في تطبيق Android. وأثناء عملية الاحتفاظ بنسخة احتياطية من قيمة المفتاح، يتم تمرير بيانات النسخة الاحتياطية للتطبيق إلى عملية نقل النسخة الاحتياطية للجهاز. إذا كان الجهاز يستخدم النقل التلقائي للنسخة الاحتياطية من Google، يتم تمرير البيانات إلى Android Backup Service للأرشفة.
تقتصر البيانات على 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
قيمة منطقية للإشارة إلى ما إذا كنت تريد استعادة بيانات التطبيق
بغض النظر عن إصدار التطبيق الحالي مقارنةً بالإصدار الذي
أنشأ البيانات الاحتياطية. القيمة التلقائية هي false
. اطلع على التحقق من إصدار
استعادة البيانات للحصول على مزيد من المعلومات.
Extend BackupAgentHelper
يجب إنشاء وكيل النسخ الاحتياطي باستخدام BackupAgentHelper
إذا كنت تريد الاحتفاظ بنسخة احتياطية من الملفات الكاملة إما من SharedPreferences
أو وحدة التخزين الداخلية.
لإنشاء وكيل النسخ الاحتياطي باستخدام BackupAgentHelper
، يجب استخدام رمز أقل بكثير من تمديد
BackupAgent
، لأنه ليس عليك تنفيذ onBackup()
و
onRestore()
.
يجب أن تستعين عملية تنفيذ BackupAgentHelper
بمساعد واحد أو أكثر للنسخ الاحتياطي.
مساعد النسخ الاحتياطي هو مكوّن متخصص يستدعي BackupAgentHelper
لإجراء عمليات النسخ الاحتياطي واستعادة البيانات لنوع معيّن من البيانات. يوفّر إطار عمل Android
حاليًا مساعدَين مختلفَين:
SharedPreferencesBackupHelper
للاحتفاظ بنسخة احتياطية من ملفاتSharedPreferences
.FileBackupHelper
للاحتفاظ بنسخة احتياطية من الملفات من وحدة التخزين الداخلية.
يمكنك تضمين عدة أدوات مساعدة في BackupAgentHelper
، ولكن
لن تحتاج إلى أكثر من مساعد واحد لكل نوع بيانات. مثلاً، إذا كانت لديك عدة ملفات
SharedPreferences
، ستحتاج إلى ملف
SharedPreferencesBackupHelper
واحد فقط.
لكل مساعد تريد إضافته إلى BackupAgentHelper
، عليك تنفيذ ما يلي أثناء استخدام طريقة onCreate()
:
- إنشاء مثيل لفئة المساعدة المطلوبة في الدالة الإنشائية للفئة، يجب تحديد الملفات التي تريد الاحتفاظ بنسخة احتياطية منها.
- يُرجى الاتصال بالرقم
addHelper()
لإضافة المساعد إلىBackupAgentHelper
.
توضّح الأقسام التالية طريقة إنشاء وكيل احتياطي باستخدام كل وسيلة مساعدة متاحة.
الاحتفاظ بنسخة احتياطية من التفضيلات المشتركة
عند إنشاء مثيل لـ 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
" مباشرةً لتنفيذ ما يلي:
- تعديل تنسيق بياناتك: على سبيل المثال، إذا كنت تتوقع أن تحتاج إلى مراجعة التنسيق الذي تكتب به بيانات تطبيقك، يمكنك إنشاء وكيل احتياطي للتحقق من إصدار تطبيقك أثناء عملية الاستعادة وتنفيذ أي أعمال توافق لازمة إذا كان الإصدار على الجهاز مختلفًا عن إصدار بيانات النسخ الاحتياطي. لمزيد من المعلومات، يُرجى الاطّلاع على التحقُّق من إصدار بيانات الاستعادة.
- حدِّد أجزاء البيانات التي تريد الاحتفاظ بنسخة احتياطية منها. وبدلاً من الاحتفاظ بنسخة احتياطية من ملف كامل، يمكنك تحديد أجزاء البيانات التي تريد الاحتفاظ بنسخة احتياطية منها وكيفية استعادة كل جزء بعد ذلك إلى الجهاز. ويمكن أن يساعدك هذا أيضًا في إدارة نسخ مختلفة، لأنك تقرأ بياناتك وتكتبها ككيانات فريدة، بدلاً من ملفات كاملة.
- الاحتفاظ بنسخة احتياطية من البيانات في قاعدة بيانات إذا كانت لديك قاعدة بيانات SQLite تريد استعادتها عندما يعيد المستخدم تثبيت تطبيقك، عليك إنشاء قاعدة بيانات
BackupAgent
مخصّصة تقرأ البيانات المناسبة أثناء عملية النسخ الاحتياطي، ثم أنشئ الجدول وأدرِج البيانات أثناء عملية الاستعادة.
إذا لم تكن بحاجة إلى تنفيذ أي من المهام المذكورة أعلاه وتريد الاحتفاظ بنسخة احتياطية من
الملفات الكاملة من SharedPreferences
أو وحدة التخزين الداخلية، يمكنك الاطّلاع على التمديد
BackupAgentHelper
.
الطرق المطلوبة
عند إنشاء BackupAgent
، يجب تنفيذ الطرق التالية لرد الاتصال:
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)، اكتب تمثيلاً للبيانات الحالية في
newState
ParcelFileDescriptor
. ويحتفظ "مدير الاحتفاظ بنسخة احتياطية" بهذا الكائن محليًا كتمثيل للبيانات التي يتم الاحتفاظ بنسخة احتياطية منها حاليًا. ويعيد إرسال هذا الطلب إليك باسم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
، وهي عبارة عن ملفParcelFileDescriptor
. ويتم عرض هذا الكائن باسم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()
إليك رمز الإصدار المضمّن في مجموعة بيانات الاستعادة
كمَعلمة 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>
في ملف البيان. وعند تشغيل التطبيق على جهاز يعمل بالإصدار Android 5.1 (المستوى 22 من واجهة برمجة التطبيقات) أو إصدار أقدم، يتجاهل تطبيقك هذه القيمة الواردة في البيان ويواصل إجراء عمليات نسخ احتياطية ذات قيمة أساسية. عند تشغيل التطبيق على جهاز يعمل بالإصدار Android 6.0 (المستوى 23 لواجهة برمجة التطبيقات) أو إصدار أحدث، يُجري تطبيقك نسخًا احتياطية تلقائية بدلاً من الاحتفاظ بنسخة احتياطية من قيمة المفتاح.
خصوصية المستخدم
نحن في Google على دراية تامة بالثقة التي يوليها المستخدمون فينا، وعلى مسؤوليتنا تجاه حماية خصوصية المستخدمين. وتنقل Google بيانات النسخ الاحتياطي بأمان إلى خوادم Google ومنها لتوفير ميزات النسخ الاحتياطي والاستعادة. تتعامل Google مع هذه البيانات باعتبارها معلومات شخصية وفقًا لسياسة الخصوصية المتّبعة في Google.
بالإضافة إلى ذلك، يمكن للمستخدمين إيقاف وظيفة الاحتفاظ بنسخة احتياطية من البيانات من خلال إعدادات الاحتفاظ بنسخة احتياطية في نظام Android. عندما يوقف المستخدم ميزة "الاحتفاظ بنسخة احتياطية"، تحذف خدمة Android Backup Service جميع بيانات النسخ الاحتياطي المحفوظة. يمكن للمستخدم إعادة تفعيل الاحتفاظ بنسخة احتياطية على الجهاز، ولكن لن تستعيد خدمة Android Backup Service أي بيانات محذوفة سابقًا.