工具做到現在功能都滿完整了,但一直有個擔心:萬一使用者不小心刪錯重要的 APP 怎麼辦?
雖然有做安全檢查和確認對話框,但人總是會手滑。如果能在刪除前先備份 APK,刪錯了還能救回來。
ADB 可以把手機裡的 APP 提取出來:
# 先找到 APK 路徑
adb shell pm path com.example.app
# 然後下載 APK
adb pull /data/app/com.example.app-xxx/base.apk
包裝成函數:
async extractAPK(deviceId, packageName) {
// 取得 APK 路徑
const pathCmd = `"${this.adbPath}" -s ${deviceId} shell pm path ${packageName}`;
const pathResult = await this.executeCommand(pathCmd);
const apkPath = pathResult.replace('package:', '').trim();
// 下載 APK
const savePath = path.join(this.backupDir, `${packageName}.apk`);
const pullCmd = `"${this.adbPath}" -s ${deviceId} pull ${apkPath} "${savePath}"`;
await this.executeCommand(pullCmd);
return savePath;
}
需要一個地方存放備份的 APK:
getBackupDir() {
const userDataPath = app.getPath('userData');
const backupPath = path.join(userDataPath, 'backups');
if (!fs.existsSync(backupPath)) {
fs.mkdirSync(backupPath, { recursive: true });
}
return backupPath;
}
修改刪除流程,加入自動備份:
async deleteWithBackup(deviceId, packageName) {
try {
// 先備份
mdui.snackbar({ message: '正在備份 APK...' });
await this.extractAPK(deviceId, packageName);
// 再刪除
await this.uninstallApp(deviceId, packageName);
mdui.snackbar({ message: '刪除成功,APK 已備份' });
} catch (error) {
console.error('操作失敗:', error);
}
}
提取出來的 APK 可以重新安裝:
async restoreAPK(deviceId, apkPath) {
const cmd = `"${this.adbPath}" -s ${deviceId} install "${apkPath}"`;
await this.executeCommand(cmd);
mdui.snackbar({ message: 'APP 已還原' });
}
加個頁面讓使用者管理備份:
<div class="backup-list">
<mdui-list>
<mdui-list-item>
<div slot="headline">Chrome 瀏覽器</div>
<div slot="supporting-text">備份時間: 2024-01-15</div>
<mdui-button-group slot="end">
<mdui-button onclick="restoreBackup()">還原</mdui-button>
<mdui-button onclick="deleteBackup()">刪除</mdui-button>
</mdui-button-group>
</mdui-list-item>
</mdui-list>
</div>
實際測試流程:
成功了!刪掉的 APP 又回來了。
有些遊戲 APP 的 APK 可能有好幾 GB,下載很慢。
現在很多 APP 用 Split APK,要下載多個檔案才能還原。
async extractSplitAPK(deviceId, packageName) {
const pathResult = await this.executeCommand(`adb shell pm path ${packageName}`);
const apkPaths = pathResult.split('\n')
.filter(line => line.startsWith('package:'))
.map(line => line.replace('package:', '').trim());
// 下載所有 split APK
for (const apkPath of apkPaths) {
// ...
}
}
APK 只是程式本體,使用者資料(如遊戲進度)不會一起備份。