You need to enable JavaScript to run this app. Android 10+如何通过URI将SharedPreferences从内部存储复制到外部存储
优惠活动
大模型
产品
解决方案
定价
更多

Android 10+如何通过URI将SharedPreferences从内部存储复制到外部存储

失效问题核心根因
  • 根本原因是Android 10+引入的**分区存储(Scoped Storage)**机制:哪怕你已经声明了MANAGE_EXTERNAL_STORAGE这类高危权限,不同厂商的ROM适配差异、版本间的权限规则收紧,都会导致传统File路径直接写公共外部存储的逻辑出现不可预期的失效,Android 13+的兼容性问题会更突出。
  • 你现有写的copyFile()方法本身没有任何逻辑问题,完全可以在所有场景复用,只需要针对不同的存储场景,替换掉输入流、输出流的获取方式即可。

方案1:应用专属外部目录备份(零权限,最高稳定性,优先推荐)

你现有copyAsset()方法里已经在用的getExternalFilesDir()路径,是完全不受分区存储限制的,不需要申请任何存储权限就能读写,不需要改动copyFile()逻辑,只需要把SharedPreferences的源文件流对接进来就行:

  • SharedPreferences的物理存储路径是固定的:应用内部存储目录下的/shared_prefs/文件夹,文件名就是你创建SP实例时传入的名称(比如默认命名格式是应用包名_preferences.xml)
  • 具体实现逻辑和你现有代码结构完全对齐:
// 备份SharedPreferences到应用专属外部目录
private void backupSPToPrivateExternal(String spFileName) {
    // 读取内部存储的SP源文件
    File spSourceFile = new File(getApplicationInfo().dataDir + "/shared_prefs/" + spFileName);
    if (!spSourceFile.exists()) {
        Toast.makeText(this, "待备份的SP文件不存在", Toast.LENGTH_SHORT).show();
        return;
    }

    // 目标目录和你copyAsset里用的路径保持一致即可
    String targetDirPath = getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) + "/MyBackup";
    File targetDir = new File(targetDirPath);
    if (!targetDir.exists()) {
        targetDir.mkdirs();
    }

    InputStream in = null;
    OutputStream out = null;
    try {
        in = new FileInputStream(spSourceFile);
        File targetFile = new File(targetDirPath, "sp_backup_" + System.currentTimeMillis() + ".xml");
        out = new FileOutputStream(targetFile);
        // 你现有写好的copyFile逻辑直接复用,无需任何修改
        copyFile(in, out);
        Toast.makeText(this, "备份成功,路径:" + targetFile.getAbsolutePath(), Toast.LENGTH_SHORT).show();
    } catch (IOException e) {
        e.printStackTrace();
        Toast.makeText(this, "备份失败", Toast.LENGTH_SHORT).show();
    } finally {
        // 流关闭逻辑和你现有代码完全一致
        if (in != null) {
            try { in.close(); } catch (IOException e) { e.printStackTrace(); }
        }
        if (out != null) {
            try { out.close(); } catch (IOException e) { e.printStackTrace(); }
        }
    }
}
  • 方案优势:全版本Android兼容,不需要动态申请存储权限,不需要引导用户跳转设置开启高危权限,完全符合系统存储规则。缺点是该目录的文件会在用户卸载应用时被自动清除,其他应用无法直接访问。

方案2:通过SAF(URI访问)备份到公共外部存储

如果需要把备份文件存到公共Documents目录,让用户能通过系统文件管理器直接访问查看,就不需要硬扛分区存储规则用传统File路径写,直接用系统自带的SAF(存储访问框架)拿到目标文件的URI,对接你现有的copyFile()逻辑即可:

  1. 触发系统文件选择器,让用户自主选择备份的保存位置,全程不需要申请任何存储权限:
// 启动SAF创建备份文件的入口
private void triggerSAFCreateBackup() {
    Intent createFileIntent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
    createFileIntent.addCategory(Intent.CATEGORY_OPENABLE);
    createFileIntent.setType("text/xml");
    createFileIntent.putExtra(Intent.EXTRA_TITLE, "sp_backup_" + System.currentTimeMillis() + ".xml");
    // 用户选完位置后,会通过Activity回调返回选中的文件URI
    startActivityForResult(createFileIntent, REQUEST_CODE_BACKUP);
}
  1. 在Activity的回调里拿到选中的URI,通过ContentResolver打开输出流,复用现有复制逻辑:
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQUEST_CODE_BACKUP && resultCode == RESULT_OK) {
        if (data == null || data.getData() == null) return;
        Uri targetFileUri = data.getData();
        // 读取内部存储的SP源文件
        File spSourceFile = new File(getApplicationInfo().dataDir + "/shared_prefs/" + "你的SP文件名.xml");
        InputStream in = null;
        OutputStream out = null;
        try {
            in = new FileInputStream(spSourceFile);
            // 核心适配点:通过URI拿到输出流,完全绕开传统File路径的权限限制
            out = getContentResolver().openOutputStream(targetFileUri);
            // 现有copyFile逻辑原封不动复用
            copyFile(in, out);
            Toast.makeText(this, "备份成功", Toast.LENGTH_SHORT).show();
        } catch (IOException e) {
            e.printStackTrace();
            Toast.makeText(this, "备份失败", Toast.LENGTH_SHORT).show();
            e.printStackTrace();
        } finally {
            // 流关闭逻辑和你现有代码完全一致
            if (in != null) {
                try { in.close(); } catch (IOException e) { e.printStackTrace(); }
            }
            if (out != null) {
                try { out.close(); } catch (IOException e) { e.printStackTrace(); }
            }
        }
    }
}
  • 恢复逻辑的思路和备份完全对称:通过SAF让用户选中之前备份的文件URI,通过ContentResolver打开输入流,将流内容覆盖写入到内部存储的SP文件路径,写完后清空SP缓存重新加载即可生效。

实际开发注意事项

  • MANAGE_EXTERNAL_STORAGE属于应用商店审核的高危权限,普通工具类应用申请该权限很容易被平台驳回,使用SAF方案完全不需要申请这类高危权限,符合平台政策要求。
  • 不建议继续使用Environment.getExternalStoragePublicDirectory()这类传统公共路径API,在Android 10以上就算持有存储权限,不同厂商ROM也会出现路径不可写的问题,稳定性远低于SAF和应用专属目录。

内容的提问来源于stack exchange,提问作者Tacker

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.03 08:36:01