在运行时请求和允许WRITE_EXTERNAL_STORAGE权限对当前会话没有影响

GaR*_*eTa 25 permissions android file-permissions runtime android-6.0-marshmallow

这是关于在请求权限时Android Marshmallow中引入的新运行时权限模型Manifest.permission.WRITE_EXTERNAL_STORAGE.

简而言之,我所遇到的是,如果我请求(和用户允许)Manifest.permission.WRITE_EXTERNAL_STORAGE权限,应用程序将无法从外部存储目录读取和写入,直到我销毁并重新启动应用程序.

这就是我正在做/经历的事情:

我的应用程序从以下状态开始:

ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
Run Code Online (Sandbox Code Playgroud)

这是,我没有权限访问外部存储.

然后,我就像Google解释的那样请求Manifest.permission.WRITE_EXTERNAL_STORAGE的许可

private void requestWriteExternalStoragePermission() {
    // Should we show an explanation?
    if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
        new AlertDialog.Builder(this)
                .setTitle("Inform and request")
                .setMessage("You need to enable permissions, bla bla bla")
                .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        ActivityCompat.requestPermissions(MendeleyActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, RC_PERMISSION_WRITE_EXTERNAL_STORAGE);
                    }
                })
                .show();
    } else {
        ActivityCompat.requestPermissions(MendeleyActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, RC_PERMISSION_WRITE_EXTERNAL_STORAGE);
    }
}
Run Code Online (Sandbox Code Playgroud)

一旦用户允许该权限,onRequestPermissionsResult就会被调用.

@Override
public void onRequestPermissionsResult(int requestCode,  String permissions[], int[] grantResults) {
    switch (requestCode) {
        case RC_PERMISSION_WRITE_EXTERNAL_STORAGE: {
            // If request is cancelled, the result arrays are empty.
            if (grantResults.length > 0 && PackageManager.PERMISSION_GRANTED
                // allowed 
            } else {
                // denied
            }
            break;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

allowed执行该块,确认用户已授予权限.

紧接着在此之后,如果我不破坏,然后再次打开该应用程序,我仍然没有访问权限到外部存储器.进一步来说:

hasWriteExternalStoragePermission();                  // returns true
Environment.getExternalStorageDirectory().canRead();  // RETURNS FALSE!!
Environment.getExternalStorageDirectory().canWrite(); // RETURNS FALSE!!
Run Code Online (Sandbox Code Playgroud)

所以,似乎Android运行时认为我有权限,但文件系统没有...确实,尝试访问Environment.getExternalStorageDirectory()会抛出异常:

android.system.ErrnoException: open failed: EACCES (Permission denied)
at libcore.io.Posix.open(Native Method)
at libcore.io.BlockGuardOs.open(BlockGuardOs.java:186)
at libcore.io.IoBridge.open(IoBridge.java:438)
at java.io.FileOutputStream.<init>(FileOutputStream.java:87) 
at java.io.FileOutputStream.<init>(FileOutputStream.java:72) 
Run Code Online (Sandbox Code Playgroud)

如果我现在销毁应用程序并再次打开它,行为将变为应有的,能够在外部存储文件夹中读取和写入.

有没有人遇到过这种情况?

我正在使用一个官方模拟器:

  • 最新的Android 6.0(API 23)API 23,Rev 1.
  • 仿真器运行Intel x86 Atom System Image,API 23,Rev 1.

我用以下内容构建应用:

android {
    compileSdkVersion 23
    buildToolsVersion "22.0.1"

    defaultConfig {
        minSdkVersion 16
        targetSdkVersion 23
    }
...
}
Run Code Online (Sandbox Code Playgroud)

如果有人确认这一点并且我不是唯一一个我想我们需要打开一个bug,但我希望我做错了,因为我认为这样的核心功能不太可能在SDK中出错.

cra*_*zii 7

http://developer.android.com/reference/android/Manifest.permission.html#WRITE_EXTERNAL_STORAGE:

从API级别19开始,读取/写入getExternalFilesDir(String)和getExternalCacheDir()返回的特定于应用程序的目录中的文件不需要此权限.

"运行时权限请求"从API级别23开始,显然高于19,因此不再需要权限,除非您正在访问getExternalFilesDir()指向的文件夹之外的数据.所以我相信这是模拟器的一个bug.

在19级以下的较低目标上,它们不支持在运行时请求权限,只需在清单中对权限进行权限处理即可.


Tob*_*ich 5

我有同样的问题.事实证明这似乎是一个更大的问题.更改写入外部存储的权限会更改此进程的GID(在linux端).为了更改ID,必须重新启动进程.下次打开应用程序时,将设置新的groupID并授予权限.

长话短说,我担心这不是模拟器中的错误,但实际上是Linux和Android的一个更大的问题.

我通过在第一次执行应用程序时请求权限来"解决"这个问题,并在获得权限时重新启动它,如下所示:

PackageManager packageManager = getPackageManager();
                    Intent intent = packageManager.getLaunchIntentForPackage(getPackageName());
                    ComponentName componentName = intent.getComponent();
                    Intent mainIntent = IntentCompat.makeRestartActivityTask(componentName);
                    startActivity(mainIntent);
                    System.exit(0);
Run Code Online (Sandbox Code Playgroud)

您可以尝试创建在后台运行的服务(具有另一个进程ID)并授予其权限.这样你只需要重启服务而不是完整的应用程序.在不利方面,这可能会为你做更多的工作.

希望这可以帮助.

---编辑---

用户M66B(/sf/answers/2273141461/)找到了相关gid的列表.更多信息可以在这里找到:https://android.googlesource.com/platform/frameworks/base/+/master/data/etc/platform.xml