שירות הגיבוי של Android מספק גיבוי ושחזור של נתוני מפתח/ערך באחסון בענן באפליקציות ל-Android. במהלך פעולת גיבוי של מפתח/ערך, נתוני הגיבוי של האפליקציה מועברים לתעבורת הגיבוי של המכשיר. אם המכשיר משתמש כברירת המחדל של העברת הגיבוי של Google, הנתונים מועברים אל Android Backup שירות להעברה לארכיון.
נפח הנתונים מוגבל ל-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 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 מספקת שני עוזרים שונים:
SharedPreferencesBackupHelper
כדי לגבות קבצים מסוגSharedPreferences
.FileBackupHelper
עד לחזרה קבצים למעלה מהאחסון הפנימי.
אפשר לכלול כמה עוזרים ב-BackupAgentHelper
, אבל צריך רק עוזר אחד לכל סוג נתונים. כלומר, אם יש לכם כמה קבצים מסוג SharedPreferences
, אתם צריכים רק קובץ אחד מסוג SharedPreferencesBackupHelper
.
לכל עוזר שרוצים להוסיף ל-BackupAgentHelper
, צריך לבצע את הפעולות הבאות במהלך השיטה onCreate()
:
- יוצרים מופע של מכונה של כיתת העוזרים הרצויה. ב-constructor של הכיתה צריך לציין את הקבצים שרוצים לגבות.
- כדי להוסיף את העוזר ל-
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()
כדי לבצע את הפעולות הבאות:
כדי לבדוק אם הנתונים שלכם השתנו מאז הגיבוי האחרון,
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
. ישות היא רשומת נתונים בינארית לא רגילה שמזוהה באמצעות מפתח ייחודי String. לכן, מערך הנתונים שמגובים הוא בעיקרון צמדי מפתח/ערך.כדי להוסיף ישות לקבוצת הנתונים לגיבוי, צריך:
קוראים ל-
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 via the BackupDataOutput byte[] buffer = bufStream.toByteArray(); int len = buffer.length; data.writeEntityHeader(TOPSCORE_BACKUP_KEY, len); data.writeEntityData(buffer, len);
מבצעים את הפעולה הזו לכל פיסת נתונים שרוצים לגבות. אתם יכולים לחלק את הנתונים לישות לפי הצורך. אפשר אפילו להשתמש בישות אחת בלבד.
בין שאתם מבצעים גיבוי (בשלב 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
כדי לעבור על כל הישויות בקבוצת הנתונים. לכל ישות שנמצאה, מבצעים את הפעולות הבאות:
- מקבלים את מפתח הישות באמצעות
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
.
יש להגדיר את המאפיין הזה ל-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 לא ישחזר נתונים שנמחקו בעבר.