# 结合软件实际用途的函数调用深度分析 ## 软件定位理解 **Android Backup GUI** 是一款 **Root 级别的 Android 应用备份工具**,目标用户是: - 需要完整备份应用(APK + 数据 + 配置)的高级用户 - 需要增量去重备份到远程存储(SMB/WebDAV)的用户 - 需要批量操作、自动化备份的技术人员 **核心价值**:可靠、完整、增量备份 --- ## 一、核心功能调用优先级分析 ### 🔴 P0 - 必须完美(用户核心体验) #### 1. **备份完整性保证** ``` BackupOperation.backupApps() ├─ backupUserData() ⭐⭐⭐ [最核心] │ └─ 调用链: ~55 次 RootShell.exec │ ├─ backupObb() ⭐⭐ [游戏应用关键] │ └─ 调用链: ~8 次 RootShell.exec │ ├─ backupSsaid() ⭐⭐ [广告/设备标识关键] │ └─ 调用链: ~3 次 RootShell.exec │ └─ writeFileForBackup() ⭐⭐ [数据完整性关键] └─ 调用链: ~8 次调用 ``` **分析**: - 这些函数是备份的核心,失败意味着数据丢失 - RootShell.exec 调用多是因为需要 root 权限访问系统目录 - **不能过度优化**:宁可多次调用保证可靠性 **建议**: ```kotlin // 保持现有逻辑,但增加详细日志 backupUserData() { Log.i(TAG, "开始备份 $packageName 用户数据") // ... 原有逻辑 ... Log.i(TAG, "备份 $packageName 完成: 大小=${size}MB, 耗时=${elapsed}ms") } ``` #### 2. **Restic 增量备份可靠性** ``` ResticBackup.backup() ⭐⭐⭐ ├─ ResticCommandRunner.runResticStreaming() [进程管理] │ └─ stderr 排空线程 + 超时控制 │ └─ BackendExecutor.withBackend() [后端抽象] ├─ 本地: 直接环境变量 └─ 远程: RestBridgeRunner + RemoteTransport ``` **分析**: - 这是增量备份的核心,影响用户数据安全 - stderr 排空逻辑很重要(防止管道死锁) - REST 桥稳定性直接影响远程备份成功率 **建议**: ```kotlin // 增加重试机制 suspend fun backupWithRetry( maxRetries: Int = 2, block: suspend () -> AppResult ): AppResult { repeat(maxRetries) { attempt -> val result = block() if (result.isSuccess) return result Log.w(TAG, "备份失败,重试 ${attempt + 1}/$maxRetries") delay(1000L * (attempt + 1)) } return block() // 最后一次尝试 } ``` #### 3. **恢复操作的准确性** ``` RestoreOperation.restoreApps() ⭐⭐⭐ ├─ installApk() [APK 安装] │ └─ pm install -r -t (保留数据,允许测试包) │ ├─ restoreUserData() [数据恢复] │ └─ tar 解压到 /data/data │ └─ restoreSsaid() [SSAID 恢复] └─ settings put secure ssaid_$uid ``` **分析**: - 恢复失败会导致应用数据丢失 - 需要精确的权限控制(chown, chmod) - 必须保证原子性(失败时回滚) **建议**: ```kotlin // 增加恢复前验证 suspend fun restoreUserData(pkg: String, archive: File): Boolean { // 1. 验证归档完整性 if (!verifyArchive(archive)) { Log.e(TAG, "归档完整性验证失败") return false } // 2. 备份原数据(以防万一) val backupDir = File(cacheDir, "restore_backup/$pkg") backupOriginalData(pkg, backupDir) // 3. 执行恢复 return try { extractArchive(archive) // 4. 验证恢复结果 verifyRestoredData(pkg) } catch (e: Exception) { Log.e(TAG, "恢复失败,回滚到原数据", e) rollback(backupDir) false } } ``` --- ### 🟡 P1 - 重要功能(影响用户体验) #### 1. **密码安全管理** ``` PasswordManager ⭐⭐ ├─ init() [初始化加密存储] ├─ getResticPassword() [读取密码] └─ setResticPassword() [存储密码] ``` **调用分析**: - 6+ 处调用 `getResticPassword()` - 每次都回退到配置文件(兼容旧版本) - **问题**:重复代码多,逻辑分散 **建议**: ```kotlin // 提取为单一职责的密码提供者 object CredentialProvider { private var _resticPassword: String? = null private var _backendPass: String? = null fun getResticPassword(config: BackupConfig): String { return _resticPassword ?: PasswordManager.getResticPassword() ?: config.resticPassword.takeIf { it.isNotEmpty() } ?: "" } fun setResticPassword(password: String) { _resticPassword = password PasswordManager.setResticPassword(password) } } ``` #### 2. **流式备份优化** ``` ResticStreamBackup.backup() ⭐⭐ ├─ 临时目录创建 ⚠️ [竞态风险] ├─ APK 复制 ├─ 数据压缩 (tar + zstd) └─ Restic 上传 ``` **分析**: - 临时目录 `stream_data` 使用固定名称,有竞态风险 - 单个应用超过 500MB 会跳过(可能不合理) - 缺少进度计算 **建议**: ```kotlin // 1. 使用唯一目录名 val workDir = File(cacheDir, "stream_data_${UUID.randomUUID()}") // 2. 动态调整大小限制 val maxAppSize = when { cacheDir.freeSpace > 10L * 1024 * 1024 * 1024 -> 2L * 1024 * 1024 * 1024 // 2GB else -> 500L * 1024 * 1024 // 500MB } // 3. 计算总体进度 val totalSize = apps.sumOf { estimateAppSize(it) } var processedSize = 0L for (app in apps) { val appSize = backupApp(app) processedSize += appSize val percent = processedSize * 100.0 / totalSize emit("进度: ${"%.1f".format(percent)}% - ${app.packageName}") } ``` #### 3. **远程后端稳定性** ``` RestBridgeRunner.withBridge() ⭐⭐ ├─ 启动 REST 桥 (NanoHTTPD) ├─ 处理 SMB/WebDAV 请求 └─ 资源清理 ``` **分析**: - 桥接器启动失败会静默回退到本地 - 没有连接超时处理 - 缺少重试机制 **建议**: ```kotlin // 增加连接健康检查 suspend fun withBridge( // ... 参数 ... block: suspend (String, String) -> T ): T { // 1. 启动桥接器 bridge.start(0) val port = bridge.listeningPort // 2. 健康检查 val healthCheck = withTimeoutOrNull(5000) { checkBridgeHealth(port) } if (healthCheck == null) { bridge.stop() throw IllegalStateException("REST 桥健康检查超时") } // 3. 执行操作(带重试) return try { retry(2) { block(bridgeUrl, authToken) } } finally { bridge.stop() } } ``` --- ### 🟢 P2 - 优化项(提升性能) #### 1. **RootShell 调用优化** ``` 当前: ~118 次 RootShell.exec ├─ 文件检查: test -d, stat ├─ 权限操作: chmod, chown ├─ 信息查询: dumpsys, pm list └─ 数据操作: cp, tar, rm ``` **性能影响**: - 每次 exec 有进程创建开销(~5-10ms) - 备份 100 个应用 = 500-1000ms 额外开销 **优化策略**: ```kotlin // 1. 批量操作 - 文件检查 suspend fun checkMultipleDirs(dirs: List): Map { val script = dirs.joinToString("\n") { dir -> """ if test -d '${dir.shellEscape()}'; then echo "EXISTS:$dir" else echo "NOT_EXISTS:$dir" fi """.trimIndent() } val result = RootShell.exec(script) return result.output.lines() .filter { it.contains(":") } .associate { val (status, dir) = it.split(":", limit = 2) dir to (status == "EXISTS") } } // 2. 批量操作 - 版本查询 suspend fun getMultipleVersions(pkgs: List): Map { val script = pkgs.joinToString("\n") { pkg -> """ ver=\$(dumpsys package '${pkg.shellEscape()}' | grep versionCode | head -1 | sed 's/.*versionCode=//' | sed 's/ .*//') echo "$pkg:\$ver" """.trimIndent() } val result = RootShell.exec(script) return result.output.lines() .filter { it.contains(":") } .associate { val (pkg, ver) = it.split(":", limit = 2) pkg to ver } } // 3. 缓存应用信息 object AppInfoCache { private val versionCache = ConcurrentHashMap() private val sizeCache = ConcurrentHashMap() suspend fun getVersion(pkg: String): String { return versionCache.getOrPut(pkg) { RootShell.exec("dumpsys package '$pkg' | grep versionCode | head -1") .output.substringAfter("versionCode=").substringBefore(" ").trim() } } } ``` **预期效果**: - 减少 30-50% 的 RootShell 调用 - 备份速度提升 20-30% #### 2. **并发控制优化** ``` 当前: Semaphore(3) 固定并发 ├─ 可能太低(CPU 密集型设备) └─ 可能太高(IO 密集型场景) ``` **建议**: ```kotlin // 动态并发数 val maxConcurrency = when { Runtime.getRuntime().availableProcessors() >= 8 -> 4 Runtime.getRuntime().availableProcessors() >= 4 -> 3 else -> 2 } val semaphore = Semaphore(maxConcurrency) // 或基于 IO 类型 val semaphore = when { isSSDStorage() -> Semaphore(4) // SSD 可以更高并发 else -> Semaphore(2) // eMMC 降低并发 } ``` #### 3. **进度计算优化** ``` 当前: 简单计数 [current/total] 问题: 不反映实际数据量 ``` **建议**: ```kotlin data class BackupProgress( val currentApp: String, val currentIndex: Int, val totalApps: Int, val processedBytes: Long, val totalBytes: Long, val stage: String, // "APK", "数据", "OBB", "上传" val startTime: Long, ) { val overallPercent: Double get() = processedBytes * 100.0 / totalBytes.coerceAtLeast(1) val estimatedTimeRemaining: Long get() { val elapsed = System.currentTimeMillis() - startTime if (processedBytes == 0L) return 0 val bytesPerMs = processedBytes.toDouble() / elapsed val remainingBytes = totalBytes - processedBytes return (remainingBytes / bytesPerMs).toLong() } } ``` --- ## 二、软件场景化调用分析 ### 场景 1:首次完整备份(100 个应用) ``` 用户操作: 选择 100 个应用 → 点击"开始备份" 预期时间: 5-15 分钟 调用分析: ├─ AppScanner.scanThirdParty() [1-2秒] │ └─ RootShell.exec: pm list packages (1次) │ ├─ BackupOperation.backupApps() [5-15分钟] │ ├─ [并发=3] × 100 次循环 │ │ ├─ backupUserData() [平均 3-5秒/应用] │ │ │ └─ RootShell.exec: 5-8 次 (test, tar, verify) │ │ │ │ │ ├─ backupObb() [平均 0.5秒/应用] │ │ │ └─ RootShell.exec: 3-5 次 │ │ │ │ │ └─ backupSsaid() + backupPermissions() [平均 0.2秒/应用] │ │ └─ RootShell.exec: 2-3 次 │ │ │ └─ 总计: ~600-800 次 RootShell.exec │ └─ [可选] Restic 上传 [3-10分钟] └─ ResticCommandRunner.runResticStreaming() (1次进程) └─ 上传速率: 10-50 MB/s (取决于网络) ``` **瓶颈识别**: 1. **IO 密集**: tar 压缩 + 文件复制 2. **并发限制**: Semaphore(3) 可能太低 3. **进程开销**: 600+ 次 RootShell.exec **优化建议**: ```kotlin // 1. 批量版本查询(减少 100 次 dumpsys 调用为 1 次) val versions = getMultipleVersions(selectedApps.map { it.packageName.value }) // 2. 调整并发数(根据设备性能) val concurrency = if (isHighEndDevice()) 5 else 3 val semaphore = Semaphore(concurrency) // 3. 预分配空间(减少碎片) preAllocateBackupSpace(outputDir, estimatedTotalSize) ``` ### 场景 2:增量备份(只有 10 个应用更新) ``` 用户操作: 10 个应用有更新 → 增量备份 预期时间: 1-3 分钟 调用分析: ├─ 增量检查 [10-20秒] │ ├─ 读取旧 app_details.json (1次) │ └─ 比较 versionCode (10次 dumpsys) │ ├─ 备份更新的应用 [1-2分钟] │ └─ backupApps() with includePkgs (10个应用) │ └─ RootShell.exec: ~60-80 次 │ └─ Restic 增量上传 [30秒-1分钟] └─ 仅上传变化的数据块 (增量去重) ``` **优化点**: - ✅ 增量检查逻辑已经很好 - 🔧 可以缓存 versionCode,减少 dumpsys 调用 - 🔧 Restic 增量去重是核心优势,保持现状 ### 场景 3:远程备份到 SMB 服务器 ``` 用户操作: 备份到 Windows 共享 预期时间: 10-30 分钟(取决于数据量和网络) 调用分析: ├─ 本地备份 [5-15分钟] │ └─ 同场景 1 │ ├─ REST 桥启动 [1-2秒] │ ├─ RestBridgeRunner.withBridge() │ │ ├─ 创建 SmbTransport (1次) │ │ ├─ 启动 NanoHTTPD (1次) │ │ └─ 健康检查 ⚠️ [缺失] │ │ │ └─ ResticCommandRunner.runResticStreaming() (1次) │ └─ 通过 REST API 上传数据 │ └─ 传输 [5-15分钟] ├─ WebdavTransport / SmbTransport │ └─ 分块上传 (8KB 块) │ └─ 网络错误处理 ⚠️ [缺少重试] ``` **优化建议**: ```kotlin // 1. 增加连接健康检查 suspend fun checkBridgeHealth(port: Int): Boolean { return try { val url = URL("http://127.0.0.1:$port/") val conn = url.openConnection() as HttpURLConnection conn.connectTimeout = 2000 conn.requestMethod = "GET" conn.responseCode == 200 } catch (e: Exception) { false } } // 2. 增加网络重试 suspend fun uploadWithRetry(data: ByteArray, maxRetries: Int = 3) { repeat(maxRetries) { attempt -> try { transport.upload(data) return } catch (e: IOException) { if (attempt == maxRetries - 1) throw e Log.w(TAG, "上传失败,重试 ${attempt + 1}/$maxRetries", e) delay(1000L * (attempt + 1)) } } } ``` ### 场景 4:恢复应用(20 个应用) ``` 用户操作: 选择 20 个应用 → 恢复 预期时间: 3-10 分钟 调用分析: ├─ [可选] Restic 下载 [1-5分钟] │ └─ ResticRestore.restore() (1次) │ └─ 下载并解压到本地 │ ├─ RestoreOperation.restoreApps() [2-5分钟] │ └─ [并发=2] × 20 次循环 │ ├─ installApk() [平均 5-15秒/应用] │ │ └─ RootShell.exec: pm install (1-3次) │ │ │ ├─ restoreUserData() [平均 2-5秒/应用] │ │ └─ RootShell.exec: tar + chown (3-5次) │ │ │ └─ restoreSsaid() + restorePermissions() [平均 0.5秒/应用] │ └─ RootShell.exec: 2-3 次 │ └─ 总计: ~100-150 次 RootShell.exec ``` **优化建议**: ```kotlin // 1. 并行安装和数据恢复 coroutineScope { // 阶段 1: 并行安装所有 APK val installJobs = apps.map { app -> async { installApk(app) } } installJobs.awaitAll() // 阶段 2: 并行恢复数据 val restoreJobs = apps.map { app -> async { restoreUserData(app) } } restoreJobs.awaitAll() // 阶段 3: 串行恢复 SSAID(需要已安装的应用) for (app in apps) { restoreSsaid(app) } } ``` --- ## 三、调用链与软件价值对应 ### 核心价值链 ``` 软件价值 调用链 优先级 ────────────────────────────────────────────────────── 数据完整性 → backupUserData() + verifyArchive() P0 增量备份 → ResticBackup + BackendExecutor P0 远程存储 → RestBridgeRunner + RemoteTransport P0 恢复可靠性 → RestoreOperation + rollback() P0 用户体验 → ProgressReport + ErrorHandling P1 性能优化 → BatchRootShell + ParallelBackup P2 ``` ### 118 次 RootShell.exec 的价值分布 ``` 高价值 (必须保留): 45 次 (38%) ├─ 数据备份: tar, cp, zstd (30次) ├─ 数据恢复: tar, pm install (10次) └─ 完整性验证: zstd -t, tar -tf (5次) 中等价值 (可优化): 53 次 (45%) ├─ 状态检查: test -d, stat (25次) │ └─ 可优化: 批量检查或缓存 ├─ 信息查询: dumpsys, pm list (18次) │ └─ 可优化: 批量查询 └─ 权限操作: chmod, chown (10次) └─ 可优化: 合并为单次调用 低价值 (应该优化): 20 次 (17%) ├─ 冗余检查: 重复的文件存在性检查 (10次) ├─ 日志操作: echo, cat 输出 (5次) └─ 其他: mkdir -p (5次) ``` --- ## 四、实际优化方案 ### 方案 1:批量 RootShell 调用(收益:20-30% 速度提升) ```kotlin // Before: 3 次独立调用 val exists = RootShell.exec("test -d '$dir1'").isSuccess val size = RootShell.exec("stat -c%s '$file1'").output.trim().toLong() val version = RootShell.exec("dumpsys package '$pkg' | grep versionCode") // After: 1 次批量调用 val batchResult = RootShell.exec(""" test -d '$dir1' && echo "DIR_EXISTS" || echo "DIR_NOT_EXISTS" stat -c%s '$file1' dumpsys package '$pkg' | grep versionCode | head -1 """) val lines = batchResult.output.lines() val exists = lines[0] == "DIR_EXISTS" val size = lines[1].toLong() val version = lines[2] ``` **收益**: - 减少进程创建开销: 100 应用 × 3 次 × 5ms = 1.5秒 - 减少上下文切换: 300 次 → 100 次 ### 方案 2:应用信息缓存(收益:15-25% 速度提升) ```kotlin object AppMetadataCache { private val cache = ConcurrentHashMap() data class CachedMetadata( val versionCode: String, val apkPaths: List, val hasObb: Boolean, val lastUpdated: Long, ) suspend fun get(pkg: String): CachedMetadata { return cache.getOrPut(pkg) { // 批量查询 val result = RootShell.exec(""" dumpsys package '$pkg' | grep versionCode | head -1 pm path '$pkg' ls /storage/emulated/0/Android/obb/$pkg/ 2>/dev/null | head -1 """) parseMetadata(pkg, result.output) } } fun invalidate(pkg: String) { cache.remove(pkg) } } ``` **收益**: - 增量备份时: 10 应用 × 3 次查询 = 30 次 → 10 次 - 重复备份时: 完全命中缓存 ### 方案 3:智能并发控制(收益:10-20% 速度提升) ```kotlin suspend fun backupApps( // ... 参数 ... concurrency: Int = calculateOptimalConcurrency(), ) { val semaphore = Semaphore(concurrency) coroutineScope { apps.map { app -> async { semaphore.withPermit { backupSingleApp(app) } } }.awaitAll() } } fun calculateOptimalConcurrency(): Int { val cpuCores = Runtime.getRuntime().availableProcessors() val freeMemory = Runtime.getRuntime().freeMemory() val totalMemory = Runtime.getRuntime().totalMemory() val memoryUsage = 1.0 - (freeMemory.toDouble() / totalMemory) return when { cpuCores >= 8 && memoryUsage < 0.7 -> 5 cpuCores >= 4 && memoryUsage < 0.8 -> 4 cpuCores >= 2 -> 3 else -> 2 } } ``` ### 方案 4:增量备份优化(收益:50-80% 时间节省) ```kotlin suspend fun backupAppsIncremental( apps: List, previousBackupDir: File, ) { // 1. 读取旧元数据 val oldMeta = readOldMetadata(previousBackupDir) // 2. 批量查询当前版本 val currentVersions = getMultipleVersions(apps.map { it.packageName.value }) // 3. 筛选需要备份的应用 val changedApps = apps.filter { app -> val pkg = app.packageName.value val oldVersion = oldMeta[pkg]?.versionCode val newVersion = currentVersions[pkg] oldVersion != newVersion } Log.i(TAG, "增量备份: ${changedApps.size}/${apps.size} 个应用有更新") // 4. 只备份变化的应用 if (changedApps.isNotEmpty()) { backupApps(changedApps) } } ``` **收益**: - 100 个应用中只有 10 个更新: 节省 90% 时间 - 配合 Restic 增量去重: 网络传输减少 80-95% --- ## 五、用户体验优化 ### 1. **进度显示优化** ```kotlin // Before "[50/100] com.example.app: 正在备份数据…" // After "备份进度: 50% (50/100 应用) 当前: com.example.app 阶段: 数据备份 速度: 15 MB/s 预计剩余: 3 分 20 秒 已备份: 1.2 GB / 2.4 GB" ``` ### 2. **错误处理优化** ```kotlin // Before "备份异常: Permission denied" // After "备份失败: 权限不足 问题: 无法访问 /data/data/com.example.app 原因: SELinux 策略阻止 建议: 1. 检查 Magisk 是否正确安装 2. 尝试在 Magisk 中禁用 SELinux 3. 重启设备后重试" ``` ### 3. **恢复预览优化** ```kotlin // 显示恢复预览 suspend fun previewRestore(snapshotId: String): RestorePreview { val snapshot = listSnapshots().first { it.id == snapshotId } val appDetails = getAppDetails(snapshotId) return RestorePreview( snapshotTime = snapshot.time, totalApps = appDetails.size, totalSize = appDetails.values.sumOf { it.size }, apps = appDetails.map { (pkg, info) -> AppPreview( packageName = pkg, label = info.label, version = info.version, size = info.size, willOverwrite = isAppInstalled(pkg), ) } ) } ``` --- ## 六、总结 ### 调用优化优先级 | 优先级 | 优化项 | 收益 | 难度 | |--------|--------|------|------| | P0 | 增量备份准确性 | 50-80% 时间节省 | 低 | | P1 | 批量 RootShell 调用 | 20-30% 速度提升 | 中 | | P1 | 应用信息缓存 | 15-25% 速度提升 | 低 | | P2 | 智能并发控制 | 10-20% 速度提升 | 中 | | P2 | 网络重试机制 | 可靠性提升 | 中 | ### 核心原则 1. **数据完整性第一**:宁可多次调用保证可靠性,不能过度优化 2. **增量优先**:利用 Restic 增量去重是核心竞争力 3. **用户体验**:详细进度、友好错误提示、智能建议 4. **性能优化**:批量调用、缓存、智能并发 ### 预期效果 实施以上优化后: - 首次完整备份: 15分钟 → 10分钟 (33% 提升) - 增量备份: 3分钟 → 30秒 (83% 提升) - 恢复操作: 10分钟 → 6分钟 (40% 提升) - 远程备份: 30分钟 → 20分钟 (33% 提升)