Android Backup Service מספק גיבוי ושחזור של אחסון בענן לנתוני מפתח/ערך באפליקציה ל-Android. במהלך פעולת גיבוי של מפתח/ערך, נתוני הגיבוי של האפליקציה מועברים לטרנספורט הגיבוי של המכשיר. אם המכשיר משתמש ב-Google Backup Transport שמוגדר כברירת מחדל, הנתונים מועברים ל-Android Backup Service לצורך העברה לארכיון.
הנתונים מוגבלים ל-5MB לכל משתמש באפליקציה. אין חיוב על אחסון נתוני הגיבוי.
בסקירה הכללית על גיבוי נתונים תוכלו לקרוא על אפשרויות הגיבוי ב-Android ולקבל הנחיות לגבי הנתונים שצריך לגבות ולשחזר.
הטמעת גיבוי של ערכי מפתח
כדי לגבות את נתוני האפליקציה, צריך להטמיע סוכן גיבוי. מנהל הגיבוי קורא לסוכן הגיבוי גם במהלך הגיבוי וגם במהלך השחזור.
כדי להטמיע סוכן גיבוי, צריך:
מצהירים על סוכן הגיבוי בקובץ המניפסט באמצעות המאפיין
android:backupAgent.מגדירים סוכן גיבוי באחת מהדרכים הבאות:
-
הכיתה
BackupAgentמספקת את הממשק המרכזי שבו האפליקציה משתמשת כדי לתקשר עם מנהל הגיבוי. אם מרחיבים את המחלקה הזו ישירות, צריך לשנות אתonBackup()ואתonRestore()כדי לטפל בפעולות הגיבוי והשחזור של הנתונים. -
הכיתה
BackupAgentHelperמספקת מעטפת נוחה סביב הכיתהBackupAgent, ומצמצמת את כמות הקוד שצריך לכתוב. ב-BackupAgentHelper, אתם צריכים להשתמש באובייקט עזר אחד או יותר, שמגבים ומשחזרים באופן אוטומטי סוגים מסוימים של נתונים, כדי שלא תצטרכו להטמיע אתonBackup()ו-onRestore(). אם אתם לא צריכים שליטה מלאה על הגיבויים של האפליקציה, מומלץ להשתמש ב-BackupAgentHelperכדי לטפל בגיבויים של האפליקציה.ב-Android יש כרגע כלי עזר לגיבוי שיגבו וישחזרו קבצים שלמים מ-
SharedPreferencesומאחסון פנימי.
-
הצהרה על סוכן הגיבוי במניפסט
אחרי שבוחרים את שם המחלקה של סוכן הגיבוי, צריך להצהיר עליו במניפסט באמצעות המאפיין android:backupAgent בתג <application>.
לדוגמה:
<manifest ... >
...
<application android:label="MyApplication"
android:backupAgent="MyBackupAgent">
<meta-data android:name="com.google.android.backup.api_key"
android:value="unused" />
<activity ... >
...
</activity>
</application>
</manifest>
כדי לתמוך במכשירים ישנים יותר, מומלץ להוסיף את מפתח ה-API <meta-data> לקובץ המניפסט של Android. שירות הגיבוי של Android כבר לא דורש מפתח שירות, אבל יכול להיות שבמכשירים ישנים יותר עדיין תתבצע בדיקה של מפתח בזמן הגיבוי. מגדירים את android:name לערך com.google.android.backup.api_key ואת android:value לערך unused.
המאפיין android:restoreAnyVersion מקבל ערך בוליאני שמציין אם אתם רוצים לשחזר את נתוני האפליקציה, בלי קשר לגרסת האפליקציה הנוכחית בהשוואה לגרסה שיצרה את נתוני הגיבוי. ערך ברירת המחדל הוא false. מידע נוסף זמין במאמר בדיקת הגרסה של נתוני השחזור.
הרחבת BackupAgentHelper
כדי לגבות קבצים שלמים מ-SharedPreferences או מהאחסון הפנימי, צריך ליצור את סוכן הגיבוי באמצעות BackupAgentHelper.
כדי ליצור סוכן גיבוי באמצעות BackupAgentHelper נדרש הרבה פחות קוד מאשר כדי להרחיב את BackupAgent, כי לא צריך להטמיע את onBackup() ואת onRestore().
ההטמעה של BackupAgentHelper צריכה להשתמש בעוזר גיבוי אחד או יותר.
עוזר גיבוי הוא רכיב מיוחד ש-BackupAgentHelper מזמן כדי לבצע פעולות גיבוי ושחזור של סוג נתונים מסוים. ב-Android framework יש כרגע שני כלי עזר שונים:
SharedPreferencesBackupHelperכדי לגבותSharedPreferencesקבצים.FileBackupHelperכדי לגבות קבצים מהאחסון הפנימי.
אפשר לכלול כמה רכיבי עזר ב-BackupAgentHelper, אבל צריך רק רכיב עזר אחד לכל סוג נתונים. כלומר, אם יש לכם כמה קובצי SharedPreferences, אתם צריכים רק קובץ SharedPreferencesBackupHelper אחד.
לכל עוזר שרוצים להוסיף ל-BackupAgentHelper, צריך לבצע את הפעולות הבאות במהלך השיטה onCreate():
- יוצרים מופע של מחלקת העזר שנבחרה. ב-constructor של המחלקה, צריך לציין את הקבצים שרוצים לגבות.
- מתקשרים אל
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 כולל את כל הקוד שנדרש כדי לגבות ולשחזר קבצים שנשמרו באחסון הפנימי של האפליקציה.
עם זאת, קריאה וכתיבה של קבצים באחסון הפנימי לא בטוחה ל-thread. כדי לוודא שסוכן הגיבוי לא קורא או כותב את הקבצים שלכם במקביל לפעילויות שלכם, אתם צריכים להשתמש בהצהרות מסונכרנות בכל פעם שאתם מבצעים קריאה או כתיבה. לדוגמה, בכל פעילות שבה קוראים וכותבים את הקובץ, צריך אובייקט שישמש כנעילה פנימית להצהרות המסונכרנות:
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()- מנהל הגיבויים קורא ל-method הזו אחרי שמבקשים גיבוי. בשיטה הזו, קוראים את נתוני האפליקציה מהמכשיר ומעבירים את הנתונים שרוצים לגבות למנהל הגיבוי, כמו שמתואר בקטע ביצוע גיבוי.
onRestore()מנהל הגיבויים מפעיל את השיטה הזו במהלך פעולת שחזור. השיטה הזו מספקת את נתוני הגיבוי, שבהם האפליקציה יכולה להשתמש כדי לשחזר את המצב הקודם שלה, כפי שמתואר בקטע ביצוע שחזור.
המערכת קוראת לשיטה הזו כדי לשחזר את נתוני הגיבוי כשהמשתמש מתקין מחדש את האפליקציה, אבל האפליקציה יכולה גם לבקש שחזור.
ביצוע גיבוי
בקשת גיבוי לא גורמת לקריאה מיידית ל-method onBackup(). במקום זאת, מנהל הגיבויים ממתין לזמן מתאים ואז מבצע גיבוי לכל האפליקציות שביקשו גיבוי מאז הגיבוי האחרון שבוצע. בשלב הזה צריך לספק את נתוני האפליקציה למנהל הגיבוי כדי שיוכל לשמור אותם באחסון בענן.
רק מנהל הגיבוי יכול לקרוא ל-method onBackup() של סוכן הגיבוי. בכל פעם שנתוני האפליקציה משתנים ואתם רוצים לבצע גיבוי, אתם צריכים לבקש פעולת גיבוי באמצעות קריאה ל-dataChanged().
מידע נוסף זמין במאמר בקשת גיבוי.
טיפ: בזמן פיתוח האפליקציה, אפשר להפעיל פעולת גיבוי מיידית ממנהל הגיבוי באמצעות הכלי bmgr.
כשמנהל הגיבוי קורא ל-method onBackup(), הוא מעביר שלושה פרמטרים:
oldStateParcelFileDescriptorפתוח לקריאה בלבד שמצביע על מצב הגיבוי האחרון שסופק על ידי האפליקציה. אלה לא נתוני הגיבוי מאחסון בענן, אלא ייצוג מקומי של הנתונים שגובו בפעם האחרונה שבה בוצעה קריאה ל-onBackup(), כפי שהוגדר על ידיnewStateאו מ-onRestore(). הסבר עלonRestore()מופיע בקטע הבא. מכיוון ש-onBackup()לא מאפשר לקרוא נתוני גיבוי קיימים באחסון בענן, אפשר להשתמש בייצוג המקומי הזה כדי לקבוע אם הנתונים השתנו מאז הגיבוי האחרון.data- אובייקט
BackupDataOutputשמשמש להעברת נתוני הגיבוי אל Backup Manager. newStateParcelFileDescriptorפתוח לקריאה ולכתיבה שמפנה לקובץ שבו צריך לכתוב ייצוג של הנתונים שהעברתם אלdata. ייצוג יכול להיות חותמת הזמן של השינוי האחרון בקובץ. האובייקט הזה מוחזר בתורoldStateבפעם הבאה שמנהל הגיבוי קורא ל-methodonBackup(). אם לא כותבים את נתוני הגיבוי ב-newState, אזoldStateיצביע על קובץ ריק בפעם הבאה שמנהל הגיבויים יקרא ל-onBackup().
בעזרת הפרמטרים האלה, מטמיעים את השיטה onBackup() כדי לבצע את הפעולות הבאות:
כדי לבדוק אם הנתונים השתנו מאז הגיבוי האחרון, משווים בין
oldStateלבין הנתונים הנוכחיים. האופן שבו קוראים נתונים ב-oldStateתלוי באופן שבו כתבתם אותם במקור ב-newState(ראו שלב 3). הדרך הקלה ביותר לתעד את מצב הקובץ היא באמצעות חותמת הזמן של השינוי האחרון. לדוגמה, כך אפשר לקרוא ולהשוות חותמת זמן מ-oldState:Kotlin
val instream = FileInputStream(oldState.fileDescriptor) val dataInputStream = DataInputStream(instream) try { // Get the last modified timestamp from the state file and data file val stateModified = dataInputStream.readLong() val fileModified: Long = dataFile.lastModified() if (stateModified != fileModified) { // The file has been modified, so do a backup // Or the time on the device changed, so be safe and do a backup } else { // Don't back up because the file hasn't changed return } } catch (e: IOException) { // Unable to read state file... be safe and do a backup }Java
// Get the oldState input stream FileInputStream instream = new FileInputStream(oldState.getFileDescriptor()); DataInputStream in = new DataInputStream(instream); try { // Get the last modified timestamp from the state file and data file long stateModified = in.readLong(); long fileModified = dataFile.lastModified(); if (stateModified != fileModified) { // The file has been modified, so do a backup // Or the time on the device changed, so be safe and do a backup } else { // Don't back up because the file hasn't changed return; } } catch (IOException e) { // Unable to read state file... be safe and do a backup }אם לא השתנה שום דבר ואין צורך לגבות, דלגו לשלב 3.
אם הנתונים השתנו בהשוואה ל-
oldState, כותבים את הנתונים הנוכחיים ב-dataכדי לגבות אותם באחסון בענן.צריך לכתוב כל מקטע נתונים כישות ב-
BackupDataOutput. ישות היא רשומת נתונים בינאריים שטוחה שמזוהה באמצעות מחרוזת מפתח ייחודית. לכן, קבוצת הנתונים שאתם מגבים היא מבחינה רעיונית קבוצה של צמדי מפתח/ערך.כדי להוסיף ישות למערך הנתונים של הגיבוי, צריך:
קוראים ל-
writeEntityHeader()ומעבירים מפתח מחרוזת ייחודי לנתונים שרוצים לכתוב ואת גודל הנתונים.קוראים ל-
writeEntityData()ומעבירים מאגר נתונים זמני של בייטים שמכיל את הנתונים ואת מספר הבייטים שצריך לכתוב מהמאגר הזמני. המספר הזה צריך להיות זהה לגודל שהועבר ל-writeEntityHeader().
לדוגמה, הקוד הבא משטח נתונים מסוימים לזרם בייטים וכותב אותם לישות אחת:
Kotlin
val buffer: ByteArray = ByteArrayOutputStream().run { DataOutputStream(this).apply { writeInt(playerName) writeInt(playerScore) } toByteArray() } val len: Int = buffer.size data.apply { writeEntityHeader(TOPSCORE_BACKUP_KEY, len) writeEntityData(buffer, len) }Java
// Create buffer stream and data output stream for our data ByteArrayOutputStream bufStream = new ByteArrayOutputStream(); DataOutputStream outWriter = new DataOutputStream(bufStream); // Write structured data outWriter.writeUTF(playerName); outWriter.writeInt(playerScore); // Send the data to the Backup Manager using the BackupDataOutput byte[] buffer = bufStream.toByteArray(); int len = buffer.length; data.writeEntityHeader(TOPSCORE_BACKUP_KEY, len); data.writeEntityData(buffer, len);מבצעים את הפעולה הזו לכל פריט נתונים שרוצים לגבות. אתם יכולים להחליט איך לחלק את הנתונים לישויות. יכול להיות שתשתמשו רק בישות אחת.
בין אם אתם מבצעים גיבוי (בשלב 2) ובין אם לא, כותבים ייצוג של הנתונים הנוכחיים ב-
newStateParcelFileDescriptor. מנהל הגיבוי שומר את האובייקט הזה באופן מקומי כייצוג של הנתונים שמגובים כרגע. הוא מעביר את הערך הזה בחזרה אליכם בתור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(), וזה קורה באופן אוטומטי כשהמערכת מתקינה את האפליקציה ומוצאת נתוני גיבוי קיימים.
כשמנהל הגיבוי קורא ל-method onRestore(), הוא מעביר שלושה פרמטרים:
data- אובייקט
BackupDataInput, שמאפשר לקרוא את נתוני הגיבוי. appVersionCode- מספר שלם שמייצג את הערך של מאפיין המניפסט
android:versionCodeשל האפליקציה, כפי שהיה כשהנתונים האלה גובו. אתם יכולים להשתמש בזה כדי להצליב את גרסת האפליקציה הנוכחית ולקבוע אם פורמט הנתונים תואם. למידע נוסף על השימוש בזה כדי לטפל בגרסאות שונות של נתוני שחזור, ראו בדיקת הגרסה של נתוני השחזור. newState-
ParcelFileDescriptorפתוח לקריאה ולכתיבה שמצביע על קובץ שבו צריך לכתוב את מצב הגיבוי הסופי שסופק עםdata. האובייקט הזה מוחזר בתורoldStateבפעם הבאה שמתבצעת קריאה ל-onBackup(). חשוב לזכור שצריך גם לכתוב את אותו אובייקטnewStateבקריאה החוזרת (callback) שלonBackup(). אם עושים את זה גם כאן, זה מבטיח שאובייקטoldStateשניתן ל-onBackup()יהיה תקין גם בפעם הראשונה שבה מתבצעת קריאה ל-onBackup()אחרי שחזור המכשיר.
בהטמעה של onRestore(), צריך להפעיל את readNextHeader() ב-data כדי לעבור על כל הישויות בקבוצת הנתונים. לכל ישות שנמצאה, מבצעים את הפעולות הבאות:
- משיגים את מפתח הישות באמצעות
getKey(). משווים את מפתח הישות לרשימה של ערכי מפתחות ידועים, שאותם צריך להצהיר כמחרוזות סטטיות סופיות בתוך המחלקה
BackupAgent. כשהמפתח תואם לאחת ממחרוזות המפתחות המוכרות, מזינים הצהרה כדי לחלץ את נתוני הישות ולשמור אותם במכשיר:- מקבלים את גודל נתוני הישות באמצעות
getDataSize()ויוצרים מערך בייטים בגודל הזה. - מפעילים את
readEntityData()ומעבירים אליה את מערך הבייטים, שבו הנתונים יאוחסנו, ומציינים את היסט ההתחלה ואת הגודל לקריאה. - מערך הבייטים מלא. קוראים את הנתונים וכותבים אותם במכשיר איך שרוצים.
- מקבלים את גודל נתוני הישות באמצעות
אחרי שקוראים את הנתונים וכותבים אותם בחזרה למכשיר, כותבים את מצב הנתונים בפרמטר
newState, כמו שעושים במהלךonBackup().
לדוגמה, כך אפשר לשחזר את הנתונים שגובו בדוגמה שבקטע הקודם:
Kotlin
@Throws(IOException::class)
override fun onRestore(data: BackupDataInput, appVersionCode: Int,
newState: ParcelFileDescriptor) {
with(data) {
// There should be only one entity, but the safest
// way to consume it is using a while loop
while (readNextHeader()) {
when(key) {
TOPSCORE_BACKUP_KEY -> {
val dataBuf = ByteArray(dataSize).also {
readEntityData(it, 0, dataSize)
}
ByteArrayInputStream(dataBuf).also {
DataInputStream(it).apply {
// Read the player name and score from the backup data
playerName = readUTF()
playerScore = readInt()
}
// Record the score on the device (to a file or something)
recordScore(playerName, playerScore)
}
}
else -> skipEntityData()
}
}
}
// Finally, write to the state blob (newState) that describes the restored data
FileOutputStream(newState.fileDescriptor).also {
DataOutputStream(it).apply {
writeUTF(playerName)
writeInt(mPlayerScore)
}
}
}
Java
@Override
public void onRestore(BackupDataInput data, int appVersionCode,
ParcelFileDescriptor newState) throws IOException {
// There should be only one entity, but the safest
// way to consume it is using a while loop
while (data.readNextHeader()) {
String key = data.getKey();
int dataSize = data.getDataSize();
// If the key is ours (for saving top score). Note this key was used when
// we wrote the backup entity header
if (TOPSCORE_BACKUP_KEY.equals(key)) {
// Create an input stream for the BackupDataInput
byte[] dataBuf = new byte[dataSize];
data.readEntityData(dataBuf, 0, dataSize);
ByteArrayInputStream baStream = new ByteArrayInputStream(dataBuf);
DataInputStream in = new DataInputStream(baStream);
// Read the player name and score from the backup data
playerName = in.readUTF();
playerScore = in.readInt();
// Record the score on the device (to a file or something)
recordScore(playerName, playerScore);
} else {
// We don't know this entity key. Skip it. (Shouldn't happen.)
data.skipEntityData();
}
}
// Finally, write to the state blob (newState) that describes the restored data
FileOutputStream outstream = new FileOutputStream(newState.getFileDescriptor());
DataOutputStream out = new DataOutputStream(outstream);
out.writeUTF(playerName);
out.writeInt(mPlayerScore);
}
בדוגמה הזו, הפרמטר appVersionCode שמועבר אל onRestore() לא נמצא בשימוש. עם זאת, יכול להיות שתרצו להשתמש בו אם בחרתם לבצע גיבוי כשגרסת האפליקציה של המשתמש למעשה חזרה אחורה (לדוגמה, המשתמש עבר מגרסה 1.5 של האפליקציה לגרסה 1.0). מידע נוסף מופיע בקטע הבא.
בדיקת הגרסה של הנתונים לשחזור
כשהמנהל של הגיבוי שומר את הנתונים שלכם באחסון בענן, הוא כולל באופן אוטומטי את גרסת האפליקציה, כפי שהיא מוגדרת במאפיין android:versionCode של קובץ המניפסט. לפני שמנהל הגיבוי קורא לסוכן הגיבוי כדי לשחזר את הנתונים, הוא בודק את android:versionCode של האפליקציה המותקנת ומשווה אותו לערך שרשום בקבוצת הנתונים לשחזור. אם הגרסה שמתועדת בקבוצת הנתונים לשחזור חדשה יותר מגרסת האפליקציה במכשיר, המשתמש שדרג לאחור את האפליקציה. במקרה כזה, מנהל הגיבוי יבטל את פעולת השחזור של האפליקציה ולא יפעיל את השיטה onRestore(), כי קבוצת הנתונים לשחזור נחשבת חסרת משמעות לגרסה ישנה יותר.
אפשר לשנות את ההתנהגות הזו באמצעות המאפיין android:restoreAnyVersion:
-
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(). השיטה הזו מודיעה למנהל הגיבוי שאתם רוצים לגבות את הנתונים באמצעות סוכן הגיבוי. לאחר מכן, מנהל הגיבוי קורא ל-method onBackup() של סוכן הגיבוי במועד עתידי. בדרך כלל, כדאי לבקש גיבוי בכל פעם שהנתונים משתנים (למשל, כשהמשתמש משנה העדפה באפליקציה שרוצים לגבות). אם קוראים ל-dataChanged() כמה פעמים לפני שמנהל הגיבוי מבקש גיבוי מהסוכן, הסוכן עדיין מקבל רק קריאה אחת ל-onBackup().
בקשה לשחזור
במהלך מחזור החיים הרגיל של האפליקציה, לא אמור להיות צורך לבקש פעולת שחזור. המערכת בודקת באופן אוטומטי אם יש נתוני גיבוי ומבצעת שחזור כשהאפליקציה מותקנת.
מעבר לגיבוי אוטומטי
כדי להעביר את האפליקציה לגיבויים של נתונים מלאים, צריך להגדיר את android:fullBackupOnly לערך true ברכיב <application> בקובץ המניפסט. אם האפליקציה פועלת במכשיר עם Android 5.1 (רמת API 22) או גרסה ישנה יותר, היא מתעלמת מהערך הזה בקובץ המניפסט וממשיכה לבצע גיבויים של מפתח/ערך. כשמפעילים את האפליקציה במכשיר עם Android בגרסה 6.0 (רמת API 23) ומעלה, היא מבצעת גיבוי אוטומטי במקום גיבוי של ערכי מפתח.
פרטיות המשתמשים
ב-Google, אנחנו מודעים היטב לאמון שהמשתמשים נותנים בנו ולאחריות שלנו להגן על הפרטיות שלהם. כדי לספק תכונות גיבוי ושחזור, Google מעבירה באופן מאובטח נתוני גיבוי אל השרתים שלה ומהם. Google מתייחסת לנתונים האלה בתור מידע אישי בהתאם למדיניות הפרטיות שלה.
בנוסף, המשתמשים יכולים להשבית את פונקציונליות הגיבוי של הנתונים דרך הגדרות הגיבוי של מערכת Android. כשמשתמש משבית את הגיבוי, Android Backup Service מוחק את כל נתוני הגיבוי שנשמרו. המשתמש יכול להפעיל מחדש את הגיבוי במכשיר, אבל Android Backup Service לא ישחזר נתונים שנמחקו בעבר.