גיבוי צמדי מפתח/ערך באמצעות Android Backup Service

שירות הגיבוי של Android מספק גיבוי ושחזור של נתוני מפתח/ערך באחסון בענן באפליקציות ל-Android. במהלך פעולת גיבוי של מפתח/ערך, נתוני הגיבוי של האפליקציה מועברים לתעבורת הגיבוי של המכשיר. אם המכשיר משתמש כברירת המחדל של העברת הגיבוי של Google, הנתונים מועברים אל Android Backup שירות להעברה לארכיון.

נפח הנתונים מוגבל ל-5MB לכל משתמש באפליקציה. אין חיוב על אחסון נתוני הגיבוי.

סקירה כללית על גיבוי נתונים – סקירה כללית על אפשרויות הגיבוי ב-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>

כדי לתמוך במכשירים ישנים, מומלץ להוסיף את מפתח ה-API <meta-data> לקובץ המניפסט ב-Android. ל-Android Backup Service כבר לא נדרש מפתח שירות, אבל בחלק מהמכשירים הישנים עדיין עשויה לבדוק אם יש מפתח בזמן הגיבוי למעלה. מגדירים את android:name לערך com.google.android.backup.api_key ואת android:value לערך unused.

android:restoreAnyVersion לוקח ערך בוליאני כדי לציין אם רוצים לשחזר את האפליקציה נתונים ללא קשר לגרסת האפליקציה הנוכחית בהשוואה לגרסה שיצר את נתוני הגיבוי. ערך ברירת המחדל הוא false. מידע נוסף זמין במאמר בדיקת הגרסה של נתוני השחזור.

הרחבת BackupAgentHelper

אם רוצים לגבות קבצים מלאים מ-SharedPreferences או מהאחסון הפנימי, צריך ליצור את סוכן הגיבוי באמצעות BackupAgentHelper. כדי לבנות את סוכן הגיבוי באמצעות BackupAgentHelper צריך הרבה פחות קוד מאשר עם הארכה של BackupAgent, כי לא צריך להטמיע את onBackup() וגם onRestore()

כדי להטמיע את BackupAgentHelper, צריך להשתמש בכלי עזר אחד או יותר לגיבוי. רכיב עזר לגיבוי הוא רכיב מיוחד ש-BackupAgentHelper קורא לו כדי לבצע פעולות גיבוי ושחזור של סוג מסוים של נתונים. בשלב הזה, מסגרת Android מספקת שני עוזרים שונים:

אפשר לכלול כמה עוזרים ב-BackupAgentHelper, אבל צריך רק עוזר אחד לכל סוג נתונים. כלומר, אם יש לכם כמה קבצים מסוג SharedPreferences, אתם צריכים רק קובץ אחד מסוג SharedPreferencesBackupHelper.

לכל עוזר שרוצים להוסיף ל-BackupAgentHelper, צריך לבצע את הפעולות הבאות במהלך השיטה onCreate():

  1. יוצרים מופע של מכונה של כיתת העוזרים הרצויה. ב-constructor של הכיתה צריך לציין את הקבצים שרוצים לגבות.
  2. כדי להוסיף את העוזר ל-BackupAgentHelper, צריך להתקשר למספר addHelper().

בקטעים הבאים מוסבר איך ליצור סוכן גיבוי באמצעות כל אחד מהעוזרים הזמינים.

גיבוי של 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 ישירות, אלא להרחיב את BackupAgentHelper כדי לנצל את הכיתות המועילות המובנות שמגיבות ומשחזרות את הקבצים באופן אוטומטי. עם זאת, ניתן להרחיב את BackupAgent ישירות כדי לבצע את הפעולות הבאות:

  • גרסה של פורמט הנתונים לדוגמה, אם אתם צופים שתצטרכו לשנות את הפורמט שבו אתם כותבים את נתוני האפליקציה, תוכלו ליצור סוכן גיבוי כדי לבדוק את גרסת האפליקציה במהלך פעולת השחזור, ולבצע את כל הפעולות הנדרשות לשמירה על תאימות אם הגרסה במכשיר שונה מזו של נתוני הגיבוי. מידע נוסף זמין במאמר בדיקת השחזור גרסת נתונים.
  • מציינים את החלקים של הנתונים שרוצים לגבות. במקום לגבות קובץ שלם, אפשר לציין את קטעי הנתונים שרוצים לגבות ואת אופן השחזור של כל קטע במכשיר. כך תוכלו גם לנהל גרסאות שונות, כי אתם קוראים ומזינים את הנתונים כישויות ייחודיות, ולא כקבצים מלאים.
  • גיבוי נתונים במסד נתונים. אם יש לכם מסד נתונים של SQLite שאתם רוצים לשחזר כשהמשתמש מתקין מחדש את האפליקציה, עליכם ליצור BackupAgent מותאם אישית שיקרא את הנתונים המתאימים במהלך פעולת הגיבוי, ולאחר מכן ליצור את הטבלה ולהוסיף את הנתונים במהלך פעולת השחזור.

אם לא צריך לבצע אף אחת מהמשימות שמפורטות למעלה ורוצים לגבות קבצים מלאים מ-SharedPreferences או מאחסון פנימי, ראו הרחבה BackupAgentHelper.

שיטות נדרשות

כשיוצרים BackupAgent, צריך להטמיע את הקריאה החוזרת הבאה אמצעי תשלום:

onBackup()
מנהל הגיבוי קורא לשיטה הזו אחרי שליחת בקשה גיבוי. בשיטה הזו אפשר לקרוא את נתוני האפליקציה מ במכשיר שלך ומעבירים את הנתונים שרוצים לגבות אל מנהל הגיבוי, כפי שמתואר במאמר ביצוע גיבוי.
onRestore()

מנהל הגיבויים קורא לשיטה הזו במהלך פעולת שחזור. השיטה הזו מעביר את נתוני הגיבוי, שבהם האפליקציה יכולה להשתמש כדי לשחזר את הנתונים הקודמים במצב הזה, כפי שמתואר בקטע ביצוע שחזור.

המערכת קוראת לשיטה הזו כדי לשחזר את נתוני הגיבוי כשהמשתמש מתקין מחדש את האפליקציה, אבל האפליקציה יכולה גם לבקש שחזור.

ביצוע גיבוי

בקשת גיבוי לא גורמת לקריאה מיידית לשיטה onBackup(). במקום זאת, מנהל הגיבויים ממתין למועד מתאים, ואז מבצע גיבוי של כל האפליקציות שביקשו גיבוי מאז הגיבוי האחרון. כאן צריך לספק את נתוני האפליקציה למנהל הגיבוי כדי שניתן יהיה לשמור אותו באחסון בענן.

רק מנהל הגיבוי יכול להפעיל את השיטה onBackup() של סוכן הגיבוי. בכל פעם שנתוני האפליקציה משתנים ואתם רוצים לבצע גיבוי, עליכם לבקש פעולת גיבוי באמצעות קריאה ל-dataChanged(). מידע נוסף זמין במאמר בקשת גיבוי.

טיפ: במהלך פיתוח האפליקציה, אפשר להתחיל גיבוי מיידי מ'מנהל הגיבויים' עם bmgr .

כשאפליקציית מנהל הגיבוי שולחת קריאה ל-method onBackup(), היא עוברת 3 :

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. ישות היא רשומת נתונים בינארית לא רגילה שמזוהה באמצעות מפתח ייחודי String. לכן, מערך הנתונים שמגובים הוא בעיקרון צמדי מפתח/ערך.

    כדי להוסיף ישות לקבוצת הנתונים לגיבוי, צריך:

    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. האובייקט הזה מוחזר כ-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() כמה פעמים לפני שמערכת Backup Manager תבקש מהנציג שלכם לבצע גיבוי, הנציג עדיין יקבל רק שיחה אחת למספר onBackup().

בקשה לשחזור

במהלך החיים הרגילים של האפליקציה, אין צורך לבקש שחזור פעולה. המערכת בודקת באופן אוטומטי אם יש נתוני גיבוי ומבצעת שחזור כשהאפליקציה מותקנת.

מעבר לגיבוי אוטומטי

אפשר להעביר את האפליקציה לגיבויים של נתונים מלאים באמצעות הגדרה android:fullBackupOnly אל true ברכיב <application> בקובץ המניפסט. כשהאפליקציה פועלת במכשיר עם Android מגרסה 5.1 (רמת API‏ 22) ומטה, היא מתעלמת מהערך הזה במניפסט וממשיכה לבצע גיבויים של מפתחות וערכים. בזמן הריצה במכשיר עם Android בגרסה 6.0 (רמת API 23) ואילך, האפליקציה מבצעת את הפעולות האוטומטיות אני רוצה לגבות במקום גיבוי של מפתח/ערך.

פרטיות המשתמשים

ב-Google, אנחנו מודעים היטב לאמון שמשתמשים נותנים בנו אחריות להגן על המשתמשים פרטיות. Google מעבירה נתוני גיבוי באופן מאובטח לשרתי Google ומהם כדי לספק תכונות גיבוי ושחזור. Google מתייחסת לנתונים האלה בתור מידע אישי בהתאם מדיניות הפרטיות.

בנוסף, המשתמשים יכולים להשבית את הפונקציונליות של גיבוי הנתונים דרך Android הגדרות הגיבוי של המערכת. כשמשתמש משבית את הגיבוי, Android Backup Service מוחקת את כל נתוני הגיבוי השמורים. המשתמש יכול להפעיל מחדש את הגיבוי במכשיר, אבל Android Backup Service לא ישחזר נתונים שנמחקו בעבר.