打开失败:EACCES(权限被拒绝)

95

我在一些设备上遇到了非常奇怪的存储访问问题。该应用程序在我的测试设备(Nexus 4&7,三星GS5)上运行正常。我所有的设备都运行Android 4.4.2。但是我收到了很多用户的电子邮件,他们说该应用无法写入存储器(内部存储器和SD卡都不行)。从用户反馈接收的日志文件中,我可以看到问题出在以下代码:

try {
    if (fStream == null) {
    fStream = new FileOutputStream(filename, true);
}
    fStream.write(data, 0, bytes);
    return;
} catch (IOException ex) {
    ex.printStackTrace();
}

在创建FileOutputStream时,当执行语句fStream = new FileOutputStream(filename, true);时,抛出了异常。

堆栈日志如下:

W/System.err( 8147): Caused by: java.io.FileNotFoundException: /storage/emulated/0/my_folder/test_file_name.png: open failed: EACCES (Permission denied)
w/System.err( 8147):    at libcore.io.IoBridge.open(IoBridge.java:409)
W/System.err( 8147):    at java.io.FileOutputStream.<init>(FileOutputStream.java:88)
W/System.err( 8147):    at java.io.FileOutputStream.<init>(FileOutputStream.java:128)
W/System.err( 8147):    at myapp.save(SourceFile:515)
W/System.err( 8147):    ... 8 more
W/System.err( 8147): Caused by: libcore.io.ErrnoException: open failed: EACCES (Permission denied)
W/System.err( 8147):    at libcore.io.Posix.open(Native Method)
W/System.err( 8147):    at libcore.io.BlockGuardOs.open(BlockGuardOs.java:110)
W/System.err( 8147):    at libcore.io.IoBridge.open(IoBridge.java:393)
W/System.err( 8147):    ... 11 more

在AndroidManifest.xml中,我声明了以下权限:

 <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="19"/>
    <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> 

我已确认用户正在使用正确的应用程序的私有SD卡。更奇怪的是,即使在具有读取和写入权限的情况下,它也无法写入内部存储。如果我已经获得了读取和写入权限,那么这种情况怎么会发生呢?用户说他们当时没有将设备连接到PC。

更新

事实证明,我过于频繁地调用打开和关闭FileOutputStream,这个问题最终导致了FileNotFoundException。听起来更像是线程问题。


事实证明,我调用了太频繁的open和close FileOutputStream,这在某些时候会抛出FileNotFoundException异常。听起来更像是线程问题。 - user3613696
请查看我的答案,希望能对您有所帮助。https://dev59.com/HY_ea4cB1Zd3GeqPIgTz#58797938 - F_Z
getExternalStoragePublicDirectory(...) 已被弃用,请使用 this.getApplicationContext().getExternalFilesDir(null).toString(); 代替。 - Moonis Abidi
22个回答

179

默认情况下,针对Android Q - API 29的应用程序只能查看外部存储的筛选视图。解决方法是在AndroidManifest.xml中添加以下代码:

<manifest ... >
    <!-- This attribute is "false" by default on apps targeting Android Q. -->
    <application android:requestLegacyExternalStorage="true" ... >
     ...
    </application>
</manifest>

在这里阅读更多相关信息:https://developer.android.com/training/data-storage/compatibility


1
由于这个解决方案是临时的,这里提供了如何查询媒体文件的全部实现:https://developer.android.com/training/data-storage/shared/media。如果它是图片,则在获取到uri后,需要调用ContentResolver.loadThumbnail(uri, size, cancelSignal)来接收包含该图片的位图。 - sunlover3
Google Play控制台稍后会要求您解释为什么使用此属性,否则他们将阻止您的应用程序。 - MoxGeek
你真笨!!! - ackfloverstow
@ackfloverstow 为什么? - Uriel Frankel

133

对于API 23及以上版本,即使权限已在清单文件中声明,您仍需要请求读写权限。

// Storage Permissions
private static final int REQUEST_EXTERNAL_STORAGE = 1;
private static String[] PERMISSIONS_STORAGE = {
        Manifest.permission.READ_EXTERNAL_STORAGE,
        Manifest.permission.WRITE_EXTERNAL_STORAGE
};

/**
 * Checks if the app has permission to write to device storage
 *
 * If the app does not has permission then the user will be prompted to grant permissions
 *
 * @param activity
 */
public static void verifyStoragePermissions(Activity activity) {
    // Check if we have write permission
    int permission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);

    if (permission != PackageManager.PERMISSION_GRANTED) {
        // We don't have permission so prompt the user
        ActivityCompat.requestPermissions(
                activity,
                PERMISSIONS_STORAGE,
                REQUEST_EXTERNAL_STORAGE
        );
    }
}

AndroidManifest.xml

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

2
没错,解决了我的问题。更多信息请参考:http://developer.android.com/training/permissions/requesting.html - joaobarbosa
确切的说,我的问题很相似。如果我在Android 6及以上版本中测试应用程序,它就会出现问题,但是在低于v6的版本中,应用程序运行得非常好。正如@easyspeak所说,只需添加权限请求即可解决问题。 - Shudy
如何在仪器化测试中执行? - Ahmad Muzakki
我已经授权,但无法在SD卡中创建.zip文件。 - Bhanu Sharma
3
这是正确答案,标记的那个不正确。 - hdante
显示剩余6条评论

35

我之前遇到过类似的问题。

你的问题可能出在两个不同的地方。要么是你创建要写入的文件的方式有问题,要么是你的写入方法可能存在缺陷,它可能受手机影响。

如果你正在将文件写入SD卡上的特定位置,请尝试使用环境变量。它们应该始终指向有效位置。以下是一个示例,用于写入下载文件夹:

java.io.File xmlFile = new java.io.File(Environment
    .getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
     + "/Filename.xml");

如果你要将文件写入应用程序的内部存储,请尝试此示例:

java.io.File xmlFile = new java.io.File((getActivity()
   .getApplicationContext().getFileStreamPath("FileName.xml")
   .getPath()));

个人而言,我依赖外部库来处理向文件的流式传输。这个库到目前为止从未让我失望。

org.apache.commons.io.FileUtils.copyInputStreamToFile(is, file);

我在写入指令失败时经常会丢失数据,因此我依赖于经过广泛测试的库进行IO处理。

如果文件很大,您可能还需要考虑在后台运行IO,或使用回调。

如果您已经在使用环境变量,那么可能是权限问题。请查看Justin Fiedler下面的答案。


我会尝试使用org.apache.commons.io.FileUtils,看看是否有帮助。稍后会更新。顺便说一下,经过更多测试,发现fStream = new FileOutputStream(filename, true);并不总是无法打开流,似乎第一次可以工作,但下一次就失败了。 - user3613696
1
事实证明,我调用了太频繁的open和close FileOutputStream,这在某些时候会抛出FileNotFoundException异常。听起来更像是线程问题。我已经将您的答案标记为接受,因为您正确指出了可能的错误。 - user3613696
谢谢。是的,由于我们无法看到您的全部代码,我们只能猜测可能导致问题的原因。感谢您的接受! - Garret

9
将读写权限都添加进去,就可以解决这个问题。
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

在 Application 标签下添加以下行:
android:requestLegacyExternalStorage="true"

android:requestLegacyExternalStorage="true" .. 使用这个可以正常工作。 - Dhanraj
1
虽然建议的解决方案可以起作用,但只是一个临时修复。请查看Android文档。 - Todor Kostov

6
在我的情况下,我在中使用了错误的大小写。
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

android.permission 必须小写,但在我们的源代码中整个字符串都是大写的。


5
我也遇到了相同的问题。经过艰苦的努力,我找到了我的问题所在。我的设备通过USB电缆连接到计算机上。USB连接有不同的类型,例如大容量存储、媒体设备(MTP)、相机(PTP)等。我的连接类型是“大容量存储”,这就导致了问题。当我更改了连接类型后,问题得到解决。
在访问Android设备文件系统时,请始终记住:不要将其连接为大容量存储到计算机/个人电脑。

4
改成什么了?我只有PTP和MTP两个选项,但都不起作用。即使在设备上加载应用程序并从设备上启动它而不连接到计算机也无法解决它。 - G_V
或者使用Wifi进行调试。 - David

3

在我的情况下,这是一个权限问题。 问题在于在Android 4.0.4的设备上,我可以访问文件而没有任何错误或异常。但是在安装有Android 5.1的设备上,它失败了,因为出现了ACCESS异常(打开失败:EACCES(权限被拒绝))。 通过在清单文件中添加以下权限来解决:

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

所以我猜测,操作系统版本中权限管理的差异导致了故障。

2

首先,授予权限或检查权限,如下所示:

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

如果这两个权限都正常,那么请检查输出流的格式是否正确。
例子:
FileOutputStream fos=new FileOutputStream(Environment.getExternalStorageDirectory()+"/rahul1.jpg");

2
我遇到了同样的问题,并发现即使我已经在清单文件中声明了权限,我也必须在运行时请求权限。正如Justin Fiedler的回答所述。
关于这个问题的官方文档在这里:https://developer.android.com/training/permissions/requesting.html 我的实现与Justin Fiedler的回答略有不同,它还实现了v4片段的onRequestPermissionsResult方法来处理权限请求响应。
public static final int REQUEST_EXTERNAL_PERMISSION_CODE = 666;

@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
public static final String[] PERMISSIONS_EXTERNAL_STORAGE = {
        READ_EXTERNAL_STORAGE,
        WRITE_EXTERNAL_STORAGE
};

public boolean checkExternalStoragePermission(Activity activity) {
    if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN) {
        return true;
    }

    int readStoragePermissionState = ContextCompat.checkSelfPermission(activity, READ_EXTERNAL_STORAGE);
    int writeStoragePermissionState = ContextCompat.checkSelfPermission(activity, WRITE_EXTERNAL_STORAGE);
    boolean externalStoragePermissionGranted = readStoragePermissionState == PackageManager.PERMISSION_GRANTED &&
            writeStoragePermissionState == PackageManager.PERMISSION_GRANTED;
    if (!externalStoragePermissionGranted) {
        requestPermissions(PERMISSIONS_EXTERNAL_STORAGE, REQUEST_EXTERNAL_PERMISSION_CODE);
    }

    return externalStoragePermissionGranted;
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
        if (requestCode == REQUEST_EXTERNAL_PERMISSION_CODE) {
            if (checkExternalStoragePermission(getActivity())) {
                // Continue with your action after permission request succeed
            }
        }
    }
}

很棒的示例,救了我的一天。 :-) - TechBee

1

我遇到了同样的问题,但有时候最困难的问题有简单的答案。

我重新检查了清单权限,发现没有写入权限,真是丢人!!!


耶稣基督,这刚刚发生在我身上,非常感谢。 - Jhonycage

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