Share
Embed
How to deal with backup & restore on Android
One of the worst things is to lose all your data when you change a phone or when you reset it. With this talk we'll see how to deal with this problem and how to secure your application's data.
Marco Gomiero
December 17, 2016
Transcript
49 slides
-
Typical Scenario A fantastic brand new phone A fantastic ROM
that I must try -
Overview ▣ Available from API 23 ▣ Upload data to
Google Drive -
Overview ▣ Available from API 23 ▣ Upload data to
Google Drive ▣ One backup for each device ▣ Max 25 MB per app -
What files are backed up? Auto Backup includes files in
most of the directories that are assigned to the app by the system: ▣ Shared preferences files ▣ Files in the directory returned by: □ getFilesDir() □ getDatabasePath(String) □ getExternalFilesDir(String) ▣ Files in directories created with getDir(String, int). Every temporary cached file is excluded from the backup -
Restore Data is restored during ▣ App install from Play
Store ▣ Device setup The restore is performed after installation, before the app is available to be launched by the user -
How to enable? Add the following attribute in the Manifest
<application ... android:allowBackup="true"> </application> Very simple! -
How to enable? You can write custom rules on backup
via xml.. <application … android:fullBackupContent="@xml/my_backup_rules"> </application> <full-backup-content> <include domain=["file" | "database" | "sharedpref" | "external" | "root"] path="string" /> <exclude domain=["file" | "database" | "sharedpref" | "external" | "root"] path="string" /> </full-backup-content> -
Backup Conditions ▣ The user has enabled backup on device
▣ At least 24 hours have elapsed from previous backup ▣ The device is idle and charging ▣ The device is connected to a WiFi Network -
Conclusions Auto Backup is perfect for settings backup, not for
complete data backup -
Overview ▣ Available from API 8 ▣ An alternative of
Auto Backup for API lower than 23 ▣ Need more work :( -
How to deal with fragmentation? ▣ You can move to
full-data backup by settings android:fullBackupOnly="true" in the Manifest ▣ On API 22 and lower, the app ignores this value and perform Key/Value Backup ▣ On API 23 and higher the app perform Auto Backup -
How to implement? Declare backup agent in the Manifest <application
… android:backupAgent="MyBackupAgent"> </application> -
How to implement? Declare Backup Service KEY in the Manifest
<application … <meta-data android:name="com.google.android.backup.api_key" android:value="AEdPqrEAAAAIDaYEVgU6DJnyJdBmU7KLH3kszDXLv _4DIsEIyQ" /> </application> -
How to implement? Define a Backup Agent by either: ▣
Extending BackupAgent ▣ Extending BackupAgentHelper Spoiler: with the helper you’ll write far less code! -
SharedPreferencesBackupHelper 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); } } -
Why? ▣ Only 5 MB per app Also Key Value
Backup is perfect for settings backup -
Custom Backup ▣ Database Dump ▣ Local Backup/Restore ▣ Online
Backup/Restore with Google Drive API -
public void backup(String outFileName) { //database path final String inFileName
= mContext.getDatabasePath(DATABASE_NAME).toString(); try { File dbFile = new File(inFileName); FileInputStream fis = new FileInputStream(dbFile); // Open the empty db as the output stream OutputStream output = new FileOutputStream(outFileName); // Transfer bytes from the input file to the output file byte[] buffer = new byte[1024]; int length; while ((length = fis.read(buffer)) > 0) { output.write(buffer, 0, length); } // Close the streams output.flush(); output.close(); fis.close(); Toast.makeText(mContext, "Backup Completed", Toast.LENGTH_SHORT).show(); } catch (Exception e) { Toast.makeText(mContext, "Unable to backup database. Retry", Toast.LENGTH_SHORT).show(); e.printStackTrace(); } } -
Where to save? Custom folder for the app: File folder
= new File(Environment.getExternalStorageDirectory() + File.separator + getResources().getString(R.string.app_name)); boolean success = true; if (!folder.exists()) success = folder.mkdirs(); if (success) { //perform backup } -
public void importDB(String inFileName) { final String outFileName = mContext.getDatabasePath(DATABASE_NAME).toString();
try { File dbFile = new File(inFileName); FileInputStream fis = new FileInputStream(dbFile); // Open the empty db as the output stream OutputStream output = new FileOutputStream(outFileName); // Transfer bytes from the input file to the output file byte[] buffer = new byte[1024]; int length; while ((length = fis.read(buffer)) > 0) { output.write(buffer, 0, length); } // Close the streams output.flush(); output.close(); fis.close(); Toast.makeText(mContext, "Import Completed", Toast.LENGTH_SHORT).show(); } catch (Exception e) { Toast.makeText(mContext, "Unable to import database. Retry", Toast.LENGTH_SHORT).show(); e.printStackTrace(); } } -
Save to Google Drive @Override public void onConnected(Bundle connectionHint) {
Log.i(TAG, "API client connected."); //when the client is connected i have two possibility: backup (bckORrst -> true) or restore (bckORrst -> false) if (bckORrst) saveFileToDrive(); else { IntentSender intentSender = Drive.DriveApi .newOpenFileActivityBuilder() .setMimeType(new String[]{"application/db"}) .build(mGoogleApiClient); try { startIntentSenderForResult(intentSender, REQUEST_CODE_OPENER, null, 0, 0, 0); Log.i(TAG, "Open File Intent send"); } catch (IntentSender.SendIntentException e) { Log.w(TAG, "Unable to send Open File Intent", e); } } } -
private void saveFileToDrive() { Drive.DriveApi.newDriveContents(mGoogleApiClient).setResultCallback(new ResultCallback<DriveApi.DriveContentsResult>() { @Override public void
onResult(@NonNull DriveApi.DriveContentsResult driveContentsResult) { if (!driveContentsResult.getStatus().isSuccess()) { //deal with failure } try { …. //same as before, except for: OutputStream outputStream = driveContentsResult.getDriveContents().getOutputStream(); …. } //drive file metadata MetadataChangeSet metadataChangeSet = new MetadataChangeSet.Builder() .setTitle("database_backup.db") .setMimeType("application/db") .build(); // Create an intent for the file chooser, and start it. IntentSender intentSender = Drive.DriveApi .newCreateFileActivityBuilder() .setInitialMetadata(metadataChangeSet) .setInitialDriveContents(driveContentsResult.getDriveContents()) .build(mGoogleApiClient); startIntentSenderForResult(intentSender, REQUEST_CODE_CREATOR, null, 0, 0, 0); } …. -
Import from Google Drive @Override public void onConnected(Bundle connectionHint) {
Log.i(TAG, "API client connected."); //when the client is connected i have two possibility: backup (bckORrst -> true) or restore (bckORrst -> false) if (bckORrst) saveFileToDrive(); else { IntentSender intentSender = Drive.DriveApi .newOpenFileActivityBuilder() .setMimeType(new String[]{"application/db"}) .build(mGoogleApiClient); try { startIntentSenderForResult(intentSender, REQUEST_CODE_OPENER, null, 0, 0, 0); Log.i(TAG, "Open File Intent send"); } catch (IntentSender.SendIntentException e) { Log.w(TAG, "Unable to send Open File Intent", e); } } } -
Import from Google Drive @Override protected void onActivityResult(final int requestCode,
final int resultCode, final Intent data) { switch (requestCode) { case REQUEST_CODE_CREATOR: // Called after a file is saved to Drive. if (resultCode == RESULT_OK) { Log.i(TAG, "Backup successfully saved."); Toast.makeText(this, "Backup successufly loaded!", Toast.LENGTH_SHORT).show(); } break; case REQUEST_CODE_OPENER: if (resultCode == RESULT_OK) { DriveId driveId = data.getParcelableExtra(OpenFileActivityBuilder.EXTRA_RESPONSE_DRIVE_ID); DriveFile file = driveId.asDriveFile(); importFromDrive(file); } } } -
private void importFromDrive(DriveFile dbFile) { dbFile.open(mGoogleApiClient, DriveFile.MODE_READ_ONLY, null).setResultCallback(new ResultCallback<DriveApi.DriveContentsResult>() {
@Override public void onResult(@NonNull DriveApi.DriveContentsResult driveContentsResult) { if (!driveContentsResult.getStatus().isSuccess()) { //deal with failure } // DriveContents object contains pointers to the actual byte stream DriveContents contents = driveContentsResult.getDriveContents(); try { ParcelFileDescriptor parcelFileDescriptor = contents.getParcelFileDescriptor(); FileInputStream fileInputStream = new FileInputStream(parcelFileDescriptor.getFileDescriptor()); // Open the empty db as the output stream OutputStream output = new FileOutputStream(inFileName); …. }