You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 07:05:32