Android 11 Scoped 存储权限

Rob*_*b B 44 android file android-developer-api android-storage android-11

我试图更好地了解 Android 11 版本发布后我将能够做什么。

我的应用程序使用提供的图像的文件路径Environment.getExternalStorageDirectory()来创建照片相册,但使用Android 11 我将无法直接访问 files

根据Android开发人员文档,他们最近引入了该MANAGE_EXTERNAL_STORAGE权限,但我不明白添加此权限是否可以继续访问文件Environment

有没有人有想法???

谢谢

2021 年 1 月更新

我在 Android 11 虚拟设备上尝试了我的应用程序,即使没有请求MANAGE_EXTERNAL_STORAGE许可,它似乎也能完美运行!

阅读 Android Developers 上的文档,似乎使用FILEAPI 仅访问照片和媒体位置的应用程序可以继续工作,但我不确定

有没有人更了解Android文档???

Tho*_*lad 70

安卓 11

如果您的目标是 Android 11 ( targetSdkVersion 30),那么您需要 AndroidManifest.xml 中的以下权限以进行修改和文档访问。

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    android:maxSdkVersion="28" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
Run Code Online (Sandbox Code Playgroud)

对于 Android 10,您将以下行放在 AndroidManifest.xml 标记中

android:requestLegacyExternalStorage="true"
Run Code Online (Sandbox Code Playgroud)

下面的方法检查权限是被允许还是被拒绝

private boolean checkPermission() {
    if (SDK_INT >= Build.VERSION_CODES.R) {
        return Environment.isExternalStorageManager();
    } else {
        int result = ContextCompat.checkSelfPermission(PermissionActivity.this, READ_EXTERNAL_STORAGE);
        int result1 = ContextCompat.checkSelfPermission(PermissionActivity.this, WRITE_EXTERNAL_STORAGE);
        return result == PackageManager.PERMISSION_GRANTED && result1 == PackageManager.PERMISSION_GRANTED;
    }
}
Run Code Online (Sandbox Code Playgroud)

以下方法可用于在 android 11 或更低版本中请求权限

private void requestPermission() {
    if (SDK_INT >= Build.VERSION_CODES.R) {
        try {
            Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
            intent.addCategory("android.intent.category.DEFAULT");
            intent.setData(Uri.parse(String.format("package:%s",getApplicationContext().getPackageName())));
            startActivityForResult(intent, 2296);
        } catch (Exception e) {
            Intent intent = new Intent();
            intent.setAction(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
            startActivityForResult(intent, 2296);
        }
    } else {
        //below android 11
        ActivityCompat.requestPermissions(PermissionActivity.this, new String[]{WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE);
    }
}
Run Code Online (Sandbox Code Playgroud)

处理Android 11及以上版本的权限回调

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == 2296) {
        if (SDK_INT >= Build.VERSION_CODES.R) {
            if (Environment.isExternalStorageManager()) {
                // perform action when allow permission success
            } else {
                Toast.makeText(this, "Allow permission for storage access!", Toast.LENGTH_SHORT).show();
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

处理 Android 11 以下操作系统版本的权限回调

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    switch (requestCode) {
        case PERMISSION_REQUEST_CODE:
            if (grantResults.length > 0) {
                boolean READ_EXTERNAL_STORAGE = grantResults[0] == PackageManager.PERMISSION_GRANTED;
                boolean WRITE_EXTERNAL_STORAGE = grantResults[1] == PackageManager.PERMISSION_GRANTED;

                if (READ_EXTERNAL_STORAGE && WRITE_EXTERNAL_STORAGE) {
                    // perform action when allow permission success
                } else {
                    Toast.makeText(this, "Allow permission for storage access!", Toast.LENGTH_SHORT).show();
                }
            }
            break;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • MANAGE_EXTERNAL_STORAGE 是一种特殊权限,仅允许防病毒、文件管理器等少数应用程序使用。在将应用程序发布到 Playstore 时,您必须证明原因。https://support.google.com/googleplay/android-developer/answer/9956427?hl=en#zippy=%2Cexceptions%2Cinvalid-uses%2Calternatives-to-common-uses%2Cpermissed-uses-of-the-all -文件访问权限 (6认同)
  • 这应该是公认的答案! (4认同)
  • 当我提交到 Playstore 时,我得到: Your APK or Android App Bundle requests the 'android.permission.MANAGE_EXTERNAL_STORAGE' 权限,Google Play 尚不支持。 (4认同)
  • 这是有风险的权限,因为谷歌只允许某些应用程序使用此权限,例如文件管理器,防病毒应用程序等。请仔细阅读策略程序 (3认同)
  • 实施此解决方案后,Google 拒绝了我的应用。 (2认同)

小智 15

Android 11 不允许直接访问存储中的文件,您必须从存储中选择文件并将该文件复制到您的应用程序包 chache com.android.myapp 中。下面是将文件表单存储复制到应用程序包缓存的方法

private String copyFileToInternalStorage(Uri uri, String newDirName) {
    Uri returnUri = uri;

    Cursor returnCursor = mContext.getContentResolver().query(returnUri, new String[]{
            OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE
    }, null, null, null);


    /*
     * Get the column indexes of the data in the Cursor,
     *     * move to the first row in the Cursor, get the data,
     *     * and display it.
     * */
    int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
    int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE);
    returnCursor.moveToFirst();
    String name = (returnCursor.getString(nameIndex));
    String size = (Long.toString(returnCursor.getLong(sizeIndex)));

    File output;
    if (!newDirName.equals("")) {
        File dir = new File(mContext.getFilesDir() + "/" + newDirName);
        if (!dir.exists()) {
            dir.mkdir();
        }
        output = new File(mContext.getFilesDir() + "/" + newDirName + "/" + name);
    } else {
        output = new File(mContext.getFilesDir() + "/" + name);
    }
    try {
        InputStream inputStream = mContext.getContentResolver().openInputStream(uri);
        FileOutputStream outputStream = new FileOutputStream(output);
        int read = 0;
        int bufferSize = 1024;
        final byte[] buffers = new byte[bufferSize];
        while ((read = inputStream.read(buffers)) != -1) {
            outputStream.write(buffers, 0, read);
        }

        inputStream.close();
        outputStream.close();

    } catch (Exception e) {

        Log.e("Exception", e.getMessage());
    }

    return output.getPath();
}
Run Code Online (Sandbox Code Playgroud)

  • 不需要 Playstore 的 MANAGE_ALL_FILES 权限? (2认同)
  • 我收到错误尝试在空对象引用上调用接口方法“int android.database.Cursor.getColumnIndex(java.lang.String)” (2认同)

Naj*_*Ali 10

在此处查看 Android 11 Scoped Storage 更新

快速解决方案在这里

对于快速解决方案,如果您放置 android 目标并编译 sdk 版本为 29,那么您的应用程序将在 android 11 上运行,其实现与您在此处在 android 10 上所做的相同

gradle 在这里

在 mainfest 文件中

在盛大

当您将 android 设备从 api 10(29) 更新到 android 11(30) Api 时,无法从您的设备存储或移动目录中检索数据存储它们不在 android 11 上工作,因为 android 11 引入了新的范围存储更新,您必须在其中实施新方法以使用 MediaStore 对象获取媒体文件,

在阅读了 android 文档后,我想与您分享的一些有用信息列在此处:

在 android 11 中,您只能访问自己特定应用的缓存。

应用程序无法在外部存储上创建自己的应用程序特定目录。要访问系统为您的应用程序提供的目录,请调用 getExternalFilesDirs()

如果您的应用面向 Android 11,则它无法访问任何其他应用的数据目录中的文件,即使其他应用面向 Android 8.1(API 级别 27)或更低版本并使其数据目录中的文件可供所有人阅读

在 Android 11 上,应用无法再访问外部存储中任何其他应用的专用应用特定目录中的文件。

在 Android 11 上运行但面向 Android 10(API 级别 29)的应用仍然可以请求 requestLegacyExternalStorage 属性。此标志允许应用程序暂时退出与范围存储相关的更改,例如授予对不同目录和不同类型媒体文件的访问权限。在您将应用更新为面向 Android 11 后,系统会忽略 requestLegacyExternalStorage 标志。

在此之前,我们在 android 10 上使用

 android:requestLegacyExternalStorage="true"
    tools:targetApi="q"
Run Code Online (Sandbox Code Playgroud)

现在在应用程序属性下的清单中,此方法在 android 11 中不起作用。

所以现在迁移到新的更新谢谢

在此处查看范围存储更新

遵循此处的教程指南 遵循 GitHub 上的 Scoped Storage 教程

  • 现在不是有效的答案,我们必须使用更新的 targetSdkVersion 才能在 Play 商店上发布应用程序至少 31。检查文档 - https://developer.android.com/google/play/requirements/target-sdk (3认同)

Mor*_*ori 8

我为Android 11 (SDK R - 30)找到了这种方式:

1- 在Manifest 中必须添加此权限:(仅适用于 R)

<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
        tools:ignore="ScopedStorage" />
Run Code Online (Sandbox Code Playgroud)

2- 请求操作系统对话以请求许可:

ActivityCompat.requestPermissions(this,
                        new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,
                                Manifest.permission.MANAGE_EXTERNAL_STORAGE}, 1);
Run Code Online (Sandbox Code Playgroud)

3- 检查您的应用可以访问存储空间

if (!Environment.isExternalStorageManager()) 
Run Code Online (Sandbox Code Playgroud)

4- 使用 Intent为您的应用打开“所有文件访问”

Intent intent = new Intent();
                    intent.setAction(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
                    Uri uri = Uri.fromParts("package", this.getPackageName(), null);
                    intent.setData(uri);
                    startActivity(intent);
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

在此处输入图片说明

  • 在 android 10 及更高版本中,我们无法使用 MANAGE_EXTERNAL_STORAGE 来提交 Play 商店应用程序,除非该应用程序是文件管理器或防病毒软件。Google 将拒绝您的应用。 (3认同)
  • 当我提交到 Playstore 时,我得到: Your APK or Android App Bundle requests the 'android.permission.MANAGE_EXTERNAL_STORAGE' 权限,Google Play 尚不支持。 (2认同)
  • @DragonFire,是的,你是对的。正如谷歌提到的那样,该权限将在 5 月 5 日之后提供。 (2认同)

Sau*_*age 7

注:此回答无需MANAGE_EXTERNAL_STORAGE许可

在 Android 10 及更高版本中,MANAGE_EXTERNAL_STORAGE我们无法将其用于 Play 商店应用程序,除非文件管理器或防病毒软件使其变得毫无用处。

因此,要在没有 MANAGE_EXTERNAL_STORAGE 的情况下从存储中访问照片,下面的答案会很有用

在清单中

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    android:maxSdkVersion="28" />
Run Code Online (Sandbox Code Playgroud)

访问媒体文件

// Need the READ_EXTERNAL_STORAGE permission if accessing video files that your
// app didn't create.

// Container for information about each video.


data class Image(val uri: Uri,
    val name: String,
    val duration: Int,
    val size: Int
)
val imgList = mutableListOf<Image>()

val collection =
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        MediaStore.Images.Media.getContentUri(
            MediaStore.VOLUME_EXTERNAL
        )
    } else {
        MediaStore.Images.Media.EXTERNAL_CONTENT_URI
    }

val projection = arrayOf(
    MediaStore.Images.Media._ID,
    MediaStore.Images.Media.DISPLAY_NAME,
 
   MediaStore.Images.Media.SIZE
)



// Display videos in alphabetical order based on their display name.
val sortOrder = "${MediaStore.Images.Media.DISPLAY_NAME} ASC"

val query = ContentResolver.query(
    collection,
    projection,
   null,
   null,
    sortOrder
)
query?.use { cursor ->
    // Cache column indices.
    val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
    val nameColumn =
            cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)
 
    val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.SIZE)

    while (cursor.moveToNext()) {
        
        val id = cursor.getLong(idColumn)
        val name = cursor.getString(nameColumn)
     
        val size = cursor.getInt(sizeColumn)

        val contentUri: Uri = ContentUris.withAppendedId(
           MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            id
        )

        // Stores column values and the contentUri in a local object
        // that represents the media file.
       imgList += Image(contentUri, name, size)
    }
}
Run Code Online (Sandbox Code Playgroud)

创建文件

// Request code 
const val CREATE_FILE = 1

private fun createFile(pickerInitialUri: Uri) {
    val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
        addCategory(Intent.CATEGORY_OPENABLE)
        type = "Type of file"
        putExtra(Intent.EXTRA_TITLE, "Name of File")

  // Optionally, specify a URI for the directory that should be  opened in
    
  // the system file picker before your app creates the document.
        putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri)
    }
    startActivityForResult(intent, CREATE_FILE)
}
Run Code Online (Sandbox Code Playgroud)


Com*_*are 6

根据 Android 开发人员文档,他们最近引入了 MANAGE_EXTERNAL_STORAGE 权限,但我不明白添加此权限是否可以继续按环境访问文件。

是的你将会。但是,请记住,如果您打算在 Play 商店(也可能是其他地方)上分发您的应用程序,您需要证明请求该许可的理由。所以,除非你有一个非常好的理由来使用MANAGE_EXTERNAL_STORAGE,请用别的东西

  • @blackapps:“添加到清单中没有任何作用”——添加什么?如果你的意思是“MANAGE_EXTERNAL_STORAGE”,它在我上次尝试的 Pixel 2 上有效。“在应用程序的设置中什么也做不了”——对我来说,当启动“Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION”时。请参阅 https://gitlab.com/commonsguy/cw-android-r/-/tree/v0.3/RawPaths。“令我惊讶的是,默认情况下,有对外部存储的读取访问权限,而 29 上却没有这种情况”——即“原始路径”。大多数外部存储都被覆盖,但不是全部。 (2认同)