安卓M写入SD卡-权限被拒绝

27

我正在尝试从我的应用程序复制文件到SD卡,但是我遇到了错误eacces(权限被拒绝)。操作系统是Android M,并且我已经在应用程序信息中允许了运行时存储权限。我还在AndroidManifest.xml中设置了uses-permission。

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

如果将文件复制到SD卡上,程序无法正常工作。

Source: data/user/0/com.example.myapp/cache/SomeFile.txt
Destination: /storage/1032-2568/SomeFolder/
Error: java.io.FileNotFoundException: /storage/1032-2568/SomeFolder/SomeFile.txt: open failed: EACCES (Permission denied)

如果我将其复制到内部存储中,则可以正常工作。

Source: data/user/0/com.example.myapp/cache/SomeFile.txt
Destination: /storage/emulated/0/SomeFolder/

复制文件从源到目的地的代码

/*
 * Below are the parameters I have tried
 *
 * inputPath - data/user/0/com.example.myapp/cache or data/user/0/com.example.myapp/cache/
 * inputFile - /SomeFile.txt or SomeFile.txt
 * outputPath - /storage/1032-2568/SomeFolder/ or /storage/1032-2568/SomeFolder
 */
public static void copyFile(String inputPath, String inputFile, String outputPath) {

    InputStream in = null;
    OutputStream out = null;
    try {

        //create output directory if it doesn't exist
        File dir = new File (outputPath);
        if (!dir.exists()) {
            dir.mkdirs();
        }

        in = new FileInputStream(inputPath + inputFile);
        out = new FileOutputStream(outputPath + inputFile);

        byte[] buffer = new byte[1024];
        int read;
        while ((read = in.read(buffer)) != -1) {
            out.write(buffer, 0, read);
        }
        in.close();


        // write the output file (You have now copied the file)
        out.flush();
        out.close();
    }
    catch (FileNotFoundException fnfe1) {
        /* I get the error here */
        Log.e("tag", fnfe1.getMessage());
    }
    catch (Exception e) {
        Log.e("tag", e.getMessage());
    }
}

ES文件浏览器

我发现ES文件浏览器在红米设备上也无法向SD卡写入任何内容。 这里有一个解决方案的视频。按照步骤操作对我的设备上的ES文件浏览器有效。是否可以通过编程实现此功能?


我认为你需要这两个权限:<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> - Incognito
@David.BC。不需要WRITE 隐含包含了 READ,因此不必要。 - Phantômaxx
2
@Rotwang 我已经使用运行时权限允许了存储权限。 - Sahil Khanna
3
您无权将内容写入可移动存储设备的任意位置。请使用存储访问框架。 - CommonsWare
同意CommonsGuy所说的。你尝试写入的位置有问题。你可以尝试使用Environment.getExternalStorageDirectory().toString()+ "/YourFolder"作为输出路径吗? - Sudheesh Mohan
@Sahil,按照CommonsWare的建议,使用存储访问框架来检查我的答案。 - shadygoneinsane
7个回答

27

正如@CommonsWare建议的那样,我们需要使用Android提供的新存储访问框架,并需要向用户请求权限以写入SD卡文件,就像您所说的在文件管理器应用程序ES File Explorer中已经实现了此功能。

以下是让用户选择“SD卡”的代码:

startActivityForResult(new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE), requestCode);

这将会长成这个样子:

在此输入图片描述

在您的copyFile代码块中获取pickedDir中的文档路径,并将其传递下去,以便使用该路径写入文件:

public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
    if (resultCode != RESULT_OK)
        return;
    else {
        Uri treeUri = resultData.getData();
        DocumentFile pickedDir = DocumentFile.fromTreeUri(this, treeUri);
        grantUriPermission(getPackageName(), treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        getContentResolver().takePersistableUriPermission(treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        copyFile(sdCard.toString(), "/File.txt", path + "/new", pickedDir);
    }
}


public void copyFile(String inputPath, String inputFile, String outputPath, DocumentFile pickedDir) {

    InputStream in = null;
    OutputStream out = null;
    try {

        //create output directory if it doesn't exist
        File dir = new File(outputPath);
        if (!dir.exists()) {
            dir.mkdirs();
        }

        in = new FileInputStream(inputPath + inputFile);
        //out = new FileOutputStream(outputPath + inputFile);

        DocumentFile file = pickedDir.createFile("//MIME type", outputPath);
        out = getContentResolver().openOutputStream(file.getUri());

        byte[] buffer = new byte[1024];
        int read;
        while ((read = in.read(buffer)) != -1) {
            out.write(buffer, 0, read);
        }
        in.close();


        // write the output file (You have now copied the file)
        out.flush();
        out.close();
    } catch (FileNotFoundException fnfe1) {
    /* I get the error here */
        Log.e("tag", fnfe1.getMessage());
    } catch (Exception e) {
        Log.e("tag", e.getMessage());
    }
}

4
谢谢!但第一次之后,如何检查用户是否已授予权限?或者我们需要每次都要求用户选择路径吗? - M. Usman Khan
3
为什么要显示目标位置选择器?其他文件管理器如何在不使用此功能的情况下完成它? 为什么需要展示目标位置选择器?其他文件管理器没有这个功能怎么做到的? - Choletski
谢谢...非常有帮助。 - Chintan Rathod
@Choletski 很好的问题,但我已经尝试过不进行这一步骤,但它并没有起作用。如果您找到了任何替代方法,请务必告诉我们! - shadygoneinsane

9

您需要在Android 6.0(API Level 23)及以上版本中添加运行时权限请求,以下是官方文档

这是WRITE_EXTERNAL_STORAGE的代码

if (checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
    Log.d(TAG,"Permission is granted");

    return true;
}

请求权限,否则像这样

ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE);

2

我也遇到了这个问题,但是我通过在运行时请求权限并在强制授予权限后解决了这个问题。在 Android 设备的应用信息中授予权限之后,在清单中声明权限 => 进入您设备的设置 => 进入应用程序信息 => 进入权限,最后允许权限。请记住,我只是在讲述 API 级别 22 及更高版本即 棉花糖(Marshmallow)之后的情况。


1

似乎运行时权限已经正确实现,但问题似乎来自设备。如果您使用的是红米手机,则必须在红米安全设置中手动允许特定应用程序的权限。此链接 显示了如何在红米安全中启用权限。


1
在某些设备上,Android 4.3之后,您无法直接对SD卡上的文件系统进行写入访问。您应该使用存储访问框架来解决这个问题。

0

我可以看出你正在复制一个文件的所有内容,试图将其写入另一个文件。我可以建议更好的方法:

假设您已经检查了文件是否存在

StringWriter temp=new StringWriter();
        try{
            FileInputStream fis=new FileInputStream(inputFile+inputPath);
            int i;
            while((i=fis.read())!=-1)
            {
                temp.write((char)i);
            }
            fis.close();
            FileOutputStream fos = new FileOutputStream(outputPath, false);  // true or false based on opening mode as appending or writing
            fos.write(temp.toString(rs1).getBytes());
            fos.close();
        }
        catch (Exception e){}

这段代码在我的应用中运行良好...如果您也能正常运行,请告诉我。


你能够复制粘贴一些更多的代码,例如你尝试在文件上操作的活动文件吗?这样我就可以更好地帮助你了。 - Bharat
你说你已经从应用程序信息中授权了。如果您这样做,那么每次清除应用程序数据时都必须重新授权。所以我猜你是尝试从内部存储复制文件,然后清除了应用程序的数据。因此,它失去了对存储的访问权限,而这一次在未授权的情况下,您可能尝试访问存储。所以你才会收到错误提示。 - Bharat
我已经使用运行时权限允许了存储权限。而且我没有清除数据。 - Sahil Khanna
请展示您的文件处理代码,否则我们怎么能预测呢? - Bharat
兄弟,我已经改了我的答案,请检查一下并让我知道。 - Bharat
没起作用。我正在使用安卓M的小米红米Note 3。还要检查一下问题中的ES文件浏览器。看看你能否提供帮助。 - Sahil Khanna

0

使用第三方应用程序(如[file explorer])无法复制或删除外部存储器上的文件和文件夹。

这是在版本之后更新的数据策略。

只有系统应用程序才允许。因此,您可以使用原始文件浏览器(来自ROM)。

如果需要使用第三方应用程序,则需要对设备进行ROOT处理。(需要ROOT权限)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接