الاحتفاظ بنسخة احتياطية من أزواج المفتاح/القيمة باستخدام Android Backup Service

توفّر خدمة Android Backup Service نسخة احتياطية للتخزين في السحابة الإلكترونية واستعادتها لبيانات قيمة المفتاح في تطبيق Android. وأثناء عملية الاحتفاظ بنسخة احتياطية من قيمة المفتاح، يتم تمرير بيانات النسخة الاحتياطية للتطبيق إلى عملية نقل النسخة الاحتياطية للجهاز. إذا كان الجهاز يستخدم النقل التلقائي للنسخة الاحتياطية من Google، يتم تمرير البيانات إلى Android Backup Service للأرشفة.

تقتصر البيانات على 5 ميغابايت لكل مستخدم للتطبيق. ولا يتم تحصيل أي رسوم مقابل تخزين البيانات الاحتياطية.

للحصول على نظرة عامة حول خيارات الاحتفاظ بنسخة احتياطية من البيانات على Android وإرشادات حول البيانات التي يجب الاحتفاظ بنسخة احتياطية منها واستعادتها، يمكنك الاطّلاع على نظرة عامة على الاحتفاظ بنسخة احتياطية من البيانات.

تنفيذ النسخ الاحتياطي للقيمة الرئيسية

للاحتفاظ بنسخة احتياطية من بيانات تطبيقك، عليك استخدام وكيل احتياطي. يستدعي مدير النسخ الاحتياطي وكيل النسخ الاحتياطي أثناء النسخ الاحتياطي والاستعادة.

لاستخدام وكيل احتياطي، يجب استيفاء الشروط التالية:

  1. يمكنك تعريف الوكيل الاحتياطي في ملف البيان باستخدام السمة android:backupAgent.

  2. تحديد وكيل احتياطي من خلال تنفيذ أحد الإجراءات التالية:

    • تمديد مهلة BackupAgent

      توفّر فئة BackupAgent الواجهة المركزية التي يستخدمها تطبيقك للتواصل مع "مدير النسخ الاحتياطي". في حال توسيع هذه الفئة مباشرةً، عليك إلغاء onBackup() وonRestore() للتعامل مع عمليات الاحتفاظ بنسخة احتياطية من بياناتك واستعادتها.

    • تمديد مهلة BackupAgentHelper

      توفّر الفئة 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():

  1. إنشاء مثيل لفئة المساعدة المطلوبة في الدالة الإنشائية للفئة، يجب تحديد الملفات التي تريد الاحتفاظ بنسخة احتياطية منها.
  2. يُرجى الاتصال بالرقم 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() لإجراء ما يلي:

  1. تحقق مما إذا كانت بياناتك قد تغيّرت منذ آخر نسخة احتياطية من خلال مقارنة 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.

  2. إذا تغيّرت بياناتك، مقارنةً بـ oldState، اكتب البيانات الحالية على data للاحتفاظ بنسخة احتياطية منها على السحابة الإلكترونية.

    يجب كتابة كل مجموعة من البيانات ككيان في BackupDataOutput. الكيان عبارة عن سجل بيانات ثنائي مسطح يتم تحديده بواسطة سلسلة مفتاح فريدة. وبالتالي، فإن مجموعة البيانات التي تحتفظ بنسخة احتياطية منها هي من الناحية النظرية مجموعة من أزواج المفتاح/القيمة.

    لإضافة كيان إلى مجموعة بيانات النسخ الاحتياطي، يجب:

    1. عليك استدعاء writeEntityHeader()، وتمرير مفتاح سلسلة فريد للبيانات التي توشك على كتابتها وحجم البيانات.

    2. استدعاء 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);
    

    نفِّذ هذا الإجراء لكل جزء من البيانات تريد الاحتفاظ بنسخة احتياطية منه. الأمر متروك لك في كيفية تقسيم بياناتك إلى كيانات. يمكنك حتى استخدام كيان واحد فقط.

  3. سواء استخدمت نسخة احتياطية أم لا (في الخطوة 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 لتكرار جميع العناصر في مجموعة البيانات. لكل كيان تم العثور عليه، قم بما يلي:

  1. احصل على مفتاح الكيان من خلال getKey().
  2. قارِن مفتاح الكيان بقائمة قيم المفاتيح المعروفة التي كان يجب أن تحدّدها كسلاسل نهائية ثابتة داخل فئة BackupAgent. عندما يتطابق المفتاح مع إحدى سلاسل المفاتيح المعروفة، أدخِل عبارة لاستخراج بيانات الكيان وحفظها على الجهاز:

    1. يمكنك الحصول على حجم بيانات الكيان باستخدام getDataSize() وإنشاء مصفوفة بايت بهذا الحجم.
    2. عليك استدعاء readEntityData() وتمرير صفيف البايت إليه، وهو المكان الذي ستنتقل إليه البيانات، وتحديد إزاحة البداية والحجم المراد قراءته.
    3. صفيفة البايت ممتلئة الآن. اقرأ البيانات واكتبها على الجهاز كيفما تشاء.
  3. بعد قراءة بياناتك وكتابتها على الجهاز، اكتب حالة بياناتك في المَعلمة 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 أي بيانات محذوفة سابقًا.