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

时间:2015-10-05 11:35:38

标签: android permissions runtime file-permissions android-6.0-marshmallow

这是关于请求Manifest.permission.WRITE_EXTERNAL_STORAGE权限时runtime permissions introduced in Android Marshmallow的新模型。

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

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

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

ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED

这是,我无权访问外部存储空间。

然后,我就像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);
    }
}

一旦用户允许该权限,就会调用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;
        }
    }
}

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

立即在此之后,如果我没有销毁并再次打开应用程序,我仍然没有外部存储的访问权限。更具体地说:

hasWriteExternalStoragePermission();                  // returns true
Environment.getExternalStorageDirectory().canRead();  // RETURNS FALSE!!
Environment.getExternalStorageDirectory().canWrite(); // RETURNS FALSE!!

所以,似乎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) 

如果我现在销毁应用并再次打开,行为将变为应有的状态,能够在外部存储文件夹中进行读写。

是否有人遇到此事?

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

  • 最新的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
    }
...
}

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

3 个答案:

答案 0 :(得分:7)

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

  

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

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

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

答案 1 :(得分:5)

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

长话短说,我害怕这不是模拟器中的错误,但事实上Linux和Android是一个更大的问题。

我&#34;解决了#34;这是通过在第一次执行应用程序时请求权限,并在获得权限时重新启动它:

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

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

希望这有帮助。

---编辑---

用户M66B(https://stackoverflow.com/a/32473449/1565635)找到了相关gid的列表。有关详细信息,请访问:https://android.googlesource.com/platform/frameworks/base/+/master/data/etc/platform.xml

答案 2 :(得分:0)

对于Android 10 +

就我而言,是:

如果定位为Android 10(API级别29)或更高,请在应用程序的requestLegacyExternalStorage文件中将AndroidManifest的值设置为true。 只需将以下代码添加到您的Application块中即可。

<application
        
        android:requestLegacyExternalStorage="true"
        ... >