Upgrade to Pro — share decks privately, control downloads, hide ads and more …

How to deal with backup & restore on Android

Marco Gomiero
December 17, 2016

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
Tweet

More Decks by Marco Gomiero

Other Decks in Programming

Transcript

  1. Overview ▣ Available from API 23 ▣ Upload data to

    Google Drive ▣ One backup for each device ▣ Max 25 MB per app
  2. 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
  3. 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
  4. How to enable? Add the following attribute in the Manifest

    <application ... android:allowBackup="true"> </application> Very simple!
  5. 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>
  6. 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
  7. Overview ▣ Available from API 8 ▣ An alternative of

    Auto Backup for API lower than 23 ▣ Need more work :(
  8. 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
  9. How to implement? Declare backup agent in the Manifest <application

    … android:backupAgent="MyBackupAgent"> </application>
  10. 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>
  11. How to implement? Define a Backup Agent by either: ▣

    Extending BackupAgent ▣ Extending BackupAgentHelper Spoiler: with the helper you’ll write far less code!
  12. 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); } }
  13. No!

  14. Why? ▣ Only 5 MB per app Also Key Value

    Backup is perfect for settings backup
  15. 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(); } }
  16. 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 }
  17. 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(); } }
  18. 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); } } }
  19. 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); } ….
  20. 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); } } }
  21. 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); } } }
  22. 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); …. }