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

Android 5设备旧APP向新APP迁移SD卡数据遇权限拒绝求助

Android 5设备SD卡跨APP数据迁移权限拒绝问题

问题背景

运营一款仅以APK分发的旧APP,现计划将改版后的新APP上架Google Play,需要在旧APP中添加迁移按钮,实现将SD卡中旧APP的数据迁移至新APP目录的功能,要求新APP不参与任何迁移操作。

Android 7及以上设备可通过DocumentsContract.moveDocument()实现文件移动,但该方法不适用于Android 5设备。自行实现的替代方案遭遇权限拒绝(Permission Denied),无法完成迁移。

现有代码

迁移处理代码

void forTEST() {
        File fileDir = getExternalFilesDir(null);
        File[] fileDirs = getExternalFilesDirs(null);

        File appAExternalFileDir = null;
        for (File dir : fileDirs) {
            if ( dir != null && !fileDir.equals(dir) && Environment.isExternalStorageRemovable(dir)) {
                appAExternalFileDir = dir;
            }
        }

        if (appAExternalFileDir == null) return;

        String appAExternalFileDirPath = appAExternalFileDir.getAbsolutePath();
        Log.d("testlog", "externalFileDirPath:" + appAExternalFileDir.getAbsolutePath()); // externalFileDirPath:/storage/MicroSD/Android/data/com.example.testAppA/files
        Log.d("testlog", appAExternalFileDir.canRead() ? "true" : "false"); // true
        Log.d("testlog", appAExternalFileDir.canWrite() ? "true" : "false"); // true

        String appBExternalFileDirPath = appAExternalFileDirPath.replace("com.example.testAppA", "testAppB");
        File appBExternalFileDir = new File(appBExternalFileDirPath);
        Log.d("testlog", "externalFileDirPath:" + appBExternalFileDir.getAbsolutePath());// externalFileDirPath:/storage/MicroSD/Android/data/com.example.testAppB/files
        Log.d("testlog", appBExternalFileDir.canRead() ? "true" : "false"); // true
        Log.d("testlog", appBExternalFileDir.canWrite() ? "true" : "false"); // true

        File[] appAFiles = appAExternalFileDir.listFiles();
        for(int i=0; i<appAFiles.length; i++) {
            String fileName = appAFiles[i].getName();
            File destFile = new File(appBExternalFileDir, appAFiles[i].getName());

            moveFileToDest(appAFiles[i], destFile);

// I tried renameTo() but only found true/false, so I created moveFileToDest() and executed it.
// renameTo() returned false.
//            if (appAFiles[i].renameTo(destFile)) {
//                Log.d("testLog", "success to renameTo()");
//            } else {
//                Log.d("testlog", "failure to renameTo()");
//            }
        }
    }

    void moveFileToDest(File sourceFile, File destinationFile) {
        try {
            // When I performed the move process described below without creating a new file first, a FileNotFoundException occurred, so I added createNewFile().
            if (destinationFile.createNewFile()) { // throw java.io.IOException: open failed: EACCES (Permission denied)
                Log.d("testlog", "create success!!");
            } else {
                Log.d("testlog", "create failed");
            }

            FileInputStream fis = new FileInputStream(sourceFile);
            FileOutputStream fos = new FileOutputStream(destinationFile);

            FileChannel sourceChannel = fis.getChannel();
            FileChannel destinationChannel = fos.getChannel();

            sourceChannel.transferTo(0, sourceChannel.size(), destinationChannel);

            sourceChannel.close();
            fis.close();
            destinationChannel.close();
            fos.close();

            sourceFile.delete();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

AndroidManifest.xml(节选)

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

遇到的问题

旧APP目录appAExternalFileDir和新APP目录appBExternalFileDir的canRead()、canWrite()均返回true,但在新APP目录内创建文件时抛出IOException: open failed: EACCES (Permission denied),调用renameTo()也返回false。

解决方案

核心原因

Android 5及以下系统中,WRITE_EXTERNAL_STORAGE权限仅允许访问公共外部存储,其他APP的私有外部存储目录(Android/data/包名/)受系统权限隔离保护,即使路径正确,旧APP也无法直接写入新APP的私有目录。canWrite()返回true仅代表文件系统层面的权限,不通过Android系统的APP级权限校验。

可行方案:使用共享UID

如果旧APP和新APP使用相同的签名密钥(改版APP上架Google Play必须与旧APP签名一致,满足此前提),可通过配置共享UID实现跨APP目录访问:

  1. 在旧APP和新APP的AndroidManifest.xml的<manifest>标签中添加相同的共享UID:
    android:sharedUserId="com.yourcompany.appshared"
    
    建议使用公司域名+唯一标识作为UID值,避免冲突。
  2. 重新编译签名两个APP,安装后旧APP即可正常读写新APP的私有外部存储目录,权限问题解决。

备选方案(若无法使用共享UID)

放弃直接写入新APP私有目录,将数据迁移至SD卡公共目录(如/storage/MicroSD/Documents/你的APP迁移文件夹/),但此方案需要新APP启动时自动读取该目录数据导入,不符合“新APP不参与迁移”的要求,仅作为兜底选择。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 18:07:03