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

Posted

技术标签:

【中文标题】在运行时请求和允许 WRITE_EXTERNAL_STORAGE 权限对当前会话没有影响【英文标题】:Requesting and allowing WRITE_EXTERNAL_STORAGE permission at runtime has no effects on the current session 【发布时间】:2016-01-02 01:16:02 【问题描述】:

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

简而言之,我遇到的是,如果我请求(并且用户允许)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 系统映像、API 23、Rev 1 的模拟器。

我使用以下方式构建应用程序:

android 
    compileSdkVersion 23
    buildToolsVersion "22.0.1"

    defaultConfig 
        minSdkVersion 16
        targetSdkVersion 23
    
...

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

【问题讨论】:

"有人遇到这种情况吗?" - 我不是,但我没有在模拟器上尝试过。我一直在使用硬件。 code.google.com/p/android-developer-preview/issues/… @GaRRaPeTa 我现在有同样的问题,症状相同。您是否以其他方式管理过这个? 我遇到了同样的问题。你解决了吗? 我在模拟器中也遇到了同样的问题,任何人都可以在不重启应用的情况下解决这个问题 【参考方案1】:

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

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

运行时权限从 API 级别 23 开始,显然高于 19,因此不再需要该权限,除非您访问 getExternalFilesDir() 指向的文件夹之外的数据。因此,我认为这是一个仅在模拟器上测试时才会出现的错误。

19级以下的target,不支持在运行时请求权限,直接在manifest中申请权限即可。

【讨论】:

这只是事实的一半。不幸的是,Lollipop 上的某些设备仍然需要特定权限,因此您应该避免删除 API 级别 19 及更高级别的权限(我这样做了,现在我必须重新引入它)。见***.com/questions/27016647/… @sven 是的。但是根据文档,我仍然认为这是一个错误,即使我在实践中请求存储权限。 @crazil:100% 同意。只是想在这里指出这一点,以避免其他开发人员删除权限只是为了认识到他们需要再次添加它...... 这不是但是。看我的回答。新的权限系统改变了linux端的组ID,需要重启进程。 @TobiasReich 不,你错过了我的意思。根据文档,对于 API 级别 19 及以上,运行时权限不需要来访问 getExternalFilesDir(),因为 访问 getExternalFilesDir() 不需要 WRITE_EXTERNAL_STORAGE 权限。我认为这是一个错误,因为这种行为与文档不同,或者文档不清楚。至少他们应该更改文档,使其不再混淆。我不知道他们是否已经更改了。【参考方案2】:

我遇到了同样的问题。事实证明,这似乎是一个更大的问题。更改写入外部存储的权限会更改此进程的 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);

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

希望这会有所帮助。

--- 编辑---

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

【讨论】:

是的,我想这是可行的,应该被接受的答案 很高兴听到这个消息! :) 请提供演示此问题的示例项目。然后,考虑filing an issue。我没有看到与此相关的问题报告,而且我从未在相当多的应用程序中看到过这种行为。如果问题仅限于canRead()/canWrite(),我可以看到可能存在问题。但是,对于真正的读写来说,不需要重启进程,授权的权限才会生效。【参考方案3】:

适用于 Android 10+

就我而言,它是:

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

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

【讨论】:

以上是关于在运行时请求和允许 WRITE_EXTERNAL_STORAGE 权限对当前会话没有影响的主要内容,如果未能解决你的问题,请参考以下文章

尝试在 .net core web api 上运行 ASE sql (sybase) 时出错“不允许发送或接收数据的请求......”

请求的注册表允许访问而没有安全漏洞

C# HttpHandler 接受 PUT 请求上传内容

Activity 在调用运行时权限时变黑

允许多个运行时权限

AJAX 请求中不允许使用 Laravel 405 方法