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

توفّر خدمة Android Backup Service إمكانية الاحتفاظ بنسخة احتياطية من بيانات المفتاح/القيمة في تطبيق Android واستعادتها من خلال التخزين في السحابة الإلكترونية. أثناء عملية الاحتفاظ بنسخة احتياطية من بيانات المفتاح/القيمة، يتم نقل بيانات النسخة الاحتياطية للتطبيق إلى وسيلة نقل النسخة الاحتياطية في الجهاز. إذا كان الجهاز يستخدم خدمة Google Backup Transport التلقائية، يتم تمرير البيانات إلى 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. يمكنك الاطّلاع على التحقّق من إصدار بيانات الاستعادة لمزيد من المعلومات.

توسيع BackupAgentHelper

يجب إنشاء وكيل النسخ الاحتياطي باستخدام BackupAgentHelper إذا كنت تريد الاحتفاظ بنسخة احتياطية من الملفات الكاملة من SharedPreferences أو وحدة التخزين الداخلية. يتطلب إنشاء وكيل الاحتفاظ بنسخة احتياطية باستخدام BackupAgentHelper رمزًا أقل بكثير من توسيع BackupAgent، لأنك لست بحاجة إلى تنفيذ onBackup() وonRestore().

يجب أن يستخدم تنفيذ BackupAgentHelper مساعدًا احتياطيًا واحدًا أو أكثر. مساعد الاحتفاظ بنسخة احتياطية هو مكوّن متخصّص يستدعيه تطبيق "BackupAgentHelper" لإجراء عمليات الاحتفاظ بنسخة احتياطية واستعادتها لنوع معيّن من البيانات. يوفّر إطار عمل Android حاليًا مساعدَين مختلفَين:

  • SharedPreferencesBackupHelper للاحتفاظ بنسخة احتياطية من ملفات SharedPreferences
  • FileBackupHelper للاحتفاظ بنسخة احتياطية من الملفات من وحدة التخزين الداخلية

يمكنك تضمين عدة مساعدين في BackupAgentHelper، ولكنك تحتاج إلى مساعد واحد فقط لكل نوع بيانات. أي إذا كان لديك عدة ملفات SharedPreferences، ستحتاج إلى ملف واحد فقط من SharedPreferencesBackupHelper.

لكل مساعد تريد إضافته إلى BackupAgentHelper، عليك تنفيذ ما يلي أثناء استخدام طريقة onCreate():

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

توسيع 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 using 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. يتم عرض هذا العنصر على شكل 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:

  • ‫false (تلقائي): يلغي "مدير الاحتفاظ بنسخة احتياطية" عملية الاستعادة إذا كان الإصدار المسجَّل في مجموعة بيانات الاستعادة أحدث من إصدار التطبيق على الجهاز.
  • true: يتجاهل "مدير الاحتفاظ بنسخة احتياطية" السمة android:versionCode ويستدعي الطريقة 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) أو إصدار أقدم، يتجاهل تطبيقك هذه القيمة في ملف البيان ويواصل إجراء عمليات الاحتفاظ بنسخة احتياطية من البيانات على شكل أزواج المفتاح/القيمة. عند تشغيل تطبيقك على جهاز يعمل بالإصدار 6.0 من نظام التشغيل Android (المستوى 23 من واجهة برمجة التطبيقات) أو إصدار أحدث، سينفّذ تطبيقك ميزة "الاحتفاظ التلقائي بنسخة احتياطية" بدلاً من ميزة "الاحتفاظ بنسخة احتياطية من أزواج المفتاح/القيمة".

خصوصية المستخدم

في Google، ندرك تمامًا ثقة المستخدمين في منتجاتنا، ومن هذا المنطلق، نتحمل نحن مسؤولية الحفاظ على خصوصية المستخدمين. تنقل Google بيانات النُسخ الاحتياطية بأمان من خوادم Google وإليها لتوفير ميزات الاحتفاظ بالنُسخ الاحتياطية واستعادتها. تتعامل Google مع هذه البيانات على أنّها معلومات شخصية بما يتوافق مع سياسة الخصوصية المتّبَعة لديها.

بالإضافة إلى ذلك، يمكن للمستخدمين إيقاف وظيفة الاحتفاظ بنسخة احتياطية من البيانات من خلال إعدادات الاحتفاظ بنسخة احتياطية في نظام Android. عندما يوقف المستخدم ميزة الاحتفاظ بنسخة احتياطية، تحذف خدمة Android Backup Service جميع بيانات النُسخ الاحتياطية المحفوظة. يمكن للمستخدم إعادة تفعيل الاحتفاظ بنسخة احتياطية على الجهاز، ولكن لن تستعيد خدمة Android Backup Service أي بيانات تم حذفها سابقًا.