Skip to content

Instantly share code, notes, and snippets.

@jbruchanov
Created March 7, 2022 11:43
Show Gist options
  • Save jbruchanov/3f0293218de285b3c3c075859f79ea23 to your computer and use it in GitHub Desktop.
Save jbruchanov/3f0293218de285b3c3c075859f79ea23 to your computer and use it in GitHub Desktop.
Very naive debug backup of android app
import java.text.SimpleDateFormat
/*
this is very !naive! implementation by making just a copy of defined files (probably doesn't handle spaces or special chars!)
1) define following `backupMap` and `removeBeforeRestore`
2) import it into the app build.gradle using `apply from: "$rootProject.projectDir/appbackup.gradle"`
3) and then just run:
#backup (see the BackupID e.g. 20220307-113423)
./gradlew app:appBackup
#restore
./gradlew app:appRestore -PBackupId=20220307-113423
*/
ext {
packageName = "YOUR_PACKAGE_ID"
dateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss")
//!naive implementation, probably doesn't handle spaces or special chars!
backupMap = [
"app.xml" : "shared_prefs/app.xml",
"appDatabase": "databases/appDatabase"
]
removeBeforeRestore = backupMap.values() + [
"databases/appDatabase-shm",
"databases/appDatabase-wal"
]
logDiv = "-".repeat(80)
}
private def runCmd(String cmd) {
def sout = new StringBuilder(), serr = new StringBuilder()
def proc = cmd.execute()
proc.consumeProcessOutput(sout, serr)
proc.waitForOrKill(5000L)
logger.debug("$cmd\nout> $sout\nerr> $serr")
}
private def killApp(String packageName) {
runCmd("adb shell am force-stop ${packageName}")
}
private File backup(File backupFolder, String packageName) {
def now = System.currentTimeMillis()
def resultFolder = new File(backupFolder, "${dateFormat.format(now)}")
resultFolder.mkdirs()
backupMap.forEach { name, path ->
def devPath = "/data/data/$packageName/$path"
def localPath = new File(resultFolder, name)
logger.quiet("$devPath --> $localPath")
runCmd("adb rm /sdcard/$name")
runCmd("adb shell run-as $packageName cp $devPath /sdcard/$name")
runCmd("adb pull /sdcard/$name $localPath")
runCmd("adb rm /sdcard/$name")
}
return resultFolder
}
private restore(File backupFolder, String packageName) {
removeBeforeRestore.forEach {
def devPath = "/data/data/$packageName/$it"
runCmd("adb shell run-as $packageName rm $devPath")
}
backupMap.forEach { name, path ->
def devPath = "/data/data/$packageName/$path"
def devFolder = new File("/data/data/$packageName/$path").parentFile
def localPath = new File(backupFolder, name)
logger.quiet("$localPath --> $devPath")
if (!localPath.exists()) {
throw new IllegalStateException("$logDiv\nExpected file: '${localPath}' doesn't exist!\n$logDiv")
}
runCmd("adb shell rm /sdcard/$name")
runCmd("adb push \"$localPath\" /sdcard/$name")
runCmd("adb shell run-as $packageName mkdir ${devFolder.name}")
runCmd("adb shell run-as $packageName mv /sdcard/$name $devPath")
}
}
tasks.register("appBackup") {
doLast {
def backupsDir = new File(it.project.rootProject.rootDir, "backups")
killApp(packageName)
def resultFolder = backup(backupsDir, packageName)
def resultLog = "BackupId: ${resultFolder.name}\n - ${resultFolder.absolutePath}"
logger.quiet("$logDiv\n$resultLog\n$logDiv")
}
}
tasks.register("appRestore") {
doLast {
if (!it.project.hasProperty("BackupId")) {
throw new IllegalArgumentException("${logDiv}\nMissing 'BackupId', pass it via ./gradlew appRestore -PBackupId=xyz\n${logDiv}")
}
def id = it.project.getProperty("BackupId")
def backupsDir = new File(it.project.rootProject.rootDir, "backups/$id")
if (!backupsDir.exists()) {
throw new IllegalArgumentException("$logDiv\nInvalid BackupId: ${id}\n'$backupsDir' doesn't exist!\n$logDiv")
}
killApp(packageName)
restore(backupsDir, packageName)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment