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

Android 11(API 30)无需MANAGE_EXTERNAL_STORAGE权限复制TXT文件

Android 11(API 30)无需MANAGE_EXTERNAL_STORAGE权限复制Download文件夹TXT文件至应用内部目录

问题描述

是否有开发者具备在Android 11(API 30)环境下,不使用MANAGE_EXTERNAL_STORAGE权限,将外部存储Download文件夹中的.TXT文件复制到应用内部目录storage/emulated/0/Android/data/[PACKAGE_NAME]/files/MyDir的实践经验?若操作可行,请提供具体实现示例。


现有复制相关代码

复制至Download文件夹代码

recovery.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {

            // 从应用内部存储复制到Download
            try {
                File sdcard = getBaseContext().getExternalFilesDir(null);
                appRecovery(
                        new File(sdcard.getAbsolutePath()),
                        new File(String.valueOf(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS))));
            } catch (IOException e) {
                e.printStackTrace();
            }

            // 清除应用数据
            clearAppData();

        }
    });

从Download文件夹复制代码

restore.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {

            // 复制操作
            String state = Environment.getExternalStorageState();
            if (Environment.MEDIA_MOUNTED.equals(state)) {
                if (SDK_INT >= 23) {
                    if (checkPermission()) {
                        try {
                            File sdcardDic = getBaseContext().getExternalFilesDir(null);
                            File dirDic = new File(sdcardDic.getAbsolutePath() + "/Text/");
                            dirDic.mkdir();
                            File fileDic = new File(dirDic, "MyTextFile.html");
                            Toasty.success(getApplicationContext(), getString(R.string.restore_toasty_text), Toast.LENGTH_LONG).show();
                            FileOutputStream dic = null;
                            try {
                                dic = new FileOutputStream(fileDic);
                                dic.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }

                            File sdcardAudio = getBaseContext().getExternalFilesDir(null);
                            File dirAudio = new File(sdcardAudio.getAbsolutePath() + "/Audio/");
                            dirAudio.mkdir();
                            File fileAudio = new File(dirAudio, "Audio.mp3");
                            FileOutputStream audio = null;
                            try {
                                audio = new FileOutputStream(fileAudio);
                                audio.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }

                            // 复制操作
                            File sdcard = getBaseContext().getExternalFilesDir(null);
                            File dir = new File(sdcard.getAbsolutePath());
                            appRecovery(new File(String.valueOf(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS))),
                                    new File(dir.getAbsolutePath()));
                        } catch (IOException e) {
                            e.printStackTrace();
                        }

                        // 删除Download中的内容
                        File dirHtml = new File(Environment.getExternalStorageDirectory() + "/Download/" + "/Text/" );
                        if (dirHtml.isDirectory()) {
                            String[] children = dirHtml.list();
                            for (int i = 0; i < children.length; i++) {
                                new File(dirHtml, children[i]).delete();
                            }
                        }

                        File dirMp3 = new File(Environment.getExternalStorageDirectory() + "/Download/" + "/Audio/" );
                        if (dirMp3.isDirectory()) {
                            String[] children = dirMp3.list();
                            for (int i = 0; i < children.length; i++) {
                                new File(dirMp3, children[i]).delete();
                            }
                        }

                    } else {
                        requestPermission();
                    }
                }
            }
        }
    });

}

public void appRecovery(File sourceLocation, File targetLocation)
        throws IOException {

    if (sourceLocation.isDirectory()) {
        if (!targetLocation.exists()) {
            targetLocation.mkdir();
        }

        String[] children = sourceLocation.list();
        for (int i = 0; i < sourceLocation.listFiles().length; i++) {

            appRecovery(new File(sourceLocation, children[i]),
                    new File(targetLocation, children[i]));
        }
    } else {

        InputStream in = new FileInputStream(sourceLocation);

        OutputStream out = new FileOutputStream(targetLocation);

        // 复制字节流
        byte[] buf = new byte[1024];
        int len;
        while ((len = in.read(buf)) > 0) {
            out.write(buf, 0, len);
        }
        in.close();
        out.close();
    }

}

可行方案与实现示例

在Android 11(API 30)下,无需申请MANAGE_EXTERNAL_STORAGE权限即可完成该操作,核心是通过MediaStore API读取Download文件夹中的TXT文件,再将内容写入应用内部目录。

核心实现代码

// 复制Download目录下所有TXT文件到应用内部MyDir目录
private void copyTxtFromDownloadToAppDir() {
    // 创建目标目录:storage/emulated/0/Android/data/[PACKAGE_NAME]/files/MyDir
    File targetDir = new File(getExternalFilesDir(null), "MyDir");
    if (!targetDir.exists()) {
        targetDir.mkdirs(); // 多级目录需用mkdirs()而非mkdir()
    }

    // 构建MediaStore查询条件,筛选Download目录下的TXT文件
    String[] projection = {
            MediaStore.Files.FileColumns._ID,
            MediaStore.Files.FileColumns.DISPLAY_NAME
    };

    String selection = MediaStore.Files.FileColumns.MIME_TYPE + " = ? AND " +
            MediaStore.Files.FileColumns.RELATIVE_PATH + " LIKE ?";
    String[] selectionArgs = {
            "text/plain",
            "%Download%"
    };

    Cursor cursor = getContentResolver().query(
            MediaStore.Files.getContentUri("external"),
            projection,
            selection,
            selectionArgs,
            null
    );

    if (cursor != null) {
        while (cursor.moveToNext()) {
            String fileName = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DISPLAY_NAME));
            // 确保只处理TXT后缀文件
            if (fileName.toLowerCase().endsWith(".txt")) {
                try {
                    // 通过ContentResolver打开源文件输入流(API 30推荐方式,无需直接访问文件路径)
                    Uri fileUri = ContentUris.withAppendedId(MediaStore.Files.getContentUri("external"),
                            cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID)));
                    InputStream inputStream = getContentResolver().openInputStream(fileUri);

                    // 创建目标文件并打开输出流
                    File targetFile = new File(targetDir, fileName);
                    OutputStream outputStream = new FileOutputStream(targetFile);

                    // 字节流复制
                    byte[] buffer = new byte[1024];
                    int length;
                    while ((length = inputStream.read(buffer)) > 0) {
                        outputStream.write(buffer, 0, length);
                    }

                    // 关闭流资源
                    inputStream.close();
                    outputStream.close();

                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        cursor.close();
    }
}

关键注意事项

  • 权限说明:Android 11及以上版本,通过MediaStore访问公共Download目录的文件,无需申请READ_EXTERNAL_STORAGE或MANAGE_EXTERNAL_STORAGE权限
  • 目录创建:目标目录MyDir如果是多级结构,必须使用mkdirs(),mkdir()仅能创建单级目录
  • 文件删除:若需删除Download中的原文件,同样需通过MediaStore API操作,不能直接用File类删除(API 30下无权限)
  • 兼容性:如果需要兼容API 30以下版本,仍需申请READ_EXTERNAL_STORAGE权限,并在代码中做版本判断

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 17:15:42