Android应用升级时如何迁移旧版外部存储文件至Android/data目录?
Android应用升级:旧公共目录文件迁移至应用专属目录方案
核心思路
把旧版本存在/sdcard/emulated/0/myapp的文件,迁移到Android 10+支持的应用专属目录Android/data/[你的应用包名]/files——这个目录无需特殊权限即可读写,完全符合谷歌Scoped Storage政策要求。
具体步骤
1. 权限配置与请求
- 在
AndroidManifest.xml中保留READ_EXTERNAL_STORAGE权限(迁移完成后可在后续版本移除):<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="33" /> - 应用启动时先检查权限状态,未授予则发起请求,请求弹窗里明确说明用途:比如“需要读取您之前保存的文件,迁移后即可正常使用应用”。
- 针对Android 10(API 29),可在
application标签添加android:requestLegacyExternalStorage="true",确保升级后能正常访问旧公共目录,迁移完成后可移除该配置。
2. 标记迁移状态
用SharedPreferences记录迁移完成状态,避免重复执行迁移操作:
// 检查是否已完成迁移 fun isMigrationDone(context: Context): Boolean { return context.getSharedPreferences("app_prefs", Context.MODE_PRIVATE) .getBoolean("has_migrated", false) } // 标记迁移完成 fun markMigrationDone(context: Context) { context.getSharedPreferences("app_prefs", Context.MODE_PRIVATE) .edit() .putBoolean("has_migrated", true) .apply() }
3. 后台执行文件迁移
推荐用WorkManager执行迁移任务(即使应用退出也能继续执行,适合大文件迁移场景),核心是实现递归复制文件/目录的逻辑:
// 递归复制文件或目录的工具方法 fun copyFileOrDir(oldPath: String, newPath: String) { val oldFile = File(oldPath) val newFile = File(newPath) if (oldFile.isDirectory) { // 创建目标目录 newFile.mkdirs() // 遍历子目录与文件,递归复制 oldFile.listFiles()?.forEach { child -> copyFileOrDir(child.path, "${newFile.path}/${child.name}") } } else { // 复制单个文件 try { FileInputStream(oldFile).use { input -> FileOutputStream(newFile).use { output -> input.copyTo(output) } } } catch (e: IOException) { // 记录异常日志,后续可提示用户迁移失败 e.printStackTrace() } } } // 触发迁移的入口逻辑 fun startMigration(context: Context) { val oldDir = File(Environment.getExternalStorageDirectory(), "myapp") val newDir = context.getExternalFilesDir(null) ?: return if (!oldDir.exists()) { // 旧目录不存在,直接标记迁移完成 markMigrationDone(context) return } // 用WorkManager发起后台迁移任务 val migrationWork = OneTimeWorkRequestBuilder<MigrationWorker>().build() WorkManager.getInstance(context).enqueue(migrationWork) } // 迁移任务Worker类 class MigrationWorker(context: Context, params: WorkerParameters) : Worker(context, params) { override fun doWork(): Result { val oldDir = File(Environment.getExternalStorageDirectory(), "myapp") val newDir = applicationContext.getExternalFilesDir(null) ?: return Result.failure() copyFileOrDir(oldDir.path, newDir.path) // 可选:验证复制完整性(比如对比文件数量、大小) // 验证通过后标记迁移完成 markMigrationDone(applicationContext) return Result.success() } }
4. 异常处理与用户提示
- 捕获IO异常并记录日志,通过通知或弹窗提示用户迁移失败,提供重试选项。
- 迁移完成后,可弹窗提示用户:“文件已迁移完成,旧目录
/sdcard/myapp的文件可手动删除以释放空间”,避免强制删除导致用户数据丢失。
5. 后续适配
迁移完成后,应用所有新的文件读写操作都要使用getExternalFilesDir()或getFilesDir()提供的路径,彻底放弃对旧公共目录的访问。
内容的提问来源于stack exchange,提问作者BlackCursor
相关产品推荐
相关产品推荐

