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

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

تقتصر البيانات على 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 قيمة منطقية للإشارة إلى ما إذا كنت تريد استعادة data التطبيق بغض النظر عن إصدار التطبيق الحالي مقارنةً بالإصدار الذي أنشأ البيانات الاحتياطية. القيمة التلقائية هي 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);
    }
}

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() لإجراء ما يلي:

  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)، اكتب تمثيلاً البيانات الحالية إلى 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 للتنقّل في جميع الكيانات في مجموعة البيانات. لكل كيان التي تم العثور عليها، قم بما يلي:

  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() رمز الإصدار المُدرَج في مجموعة 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" أي بيانات تم حذفها في السابق.