C# .Net 7 Maui 中的 Android API 33 存储权限被拒绝

com*_*guy 5 c# android maui

我试图让应用程序用户选择一个文件夹来保存从相机拍摄的照片。如果我需要使用与我正在使用的不同的 NuGet 包或内置功能,我可以这样做。我使用的是我发现的第一个有文件夹选择器并且我可以开始工作的。

我阅读了数十篇文章、答案、问题、技术文档、演练、观看视频等,尝试访问并选择要保存相机图像的文件夹。不幸的是,似乎没有任何效果,因为我不断收到异常,说我没有“存储”权限,尽管我已经尝试了多种方法来获取它们。我还请求几乎所有与“存储”相关的权限,甚至是我认为不是真正权限的权限,但在我读过的其中一份文档中引用了它。

这适用于运行 Android 9 (API 28) 和 Android 12 (API 31) 的 2 台真实设备,但不适用于 Android 13 (API 33) 设备。我正在为FolderPicker使用最新的CommunityToolkit NuGet 包。

查看专门针对该应用程序的权限,“拒绝”部分中没有列出任何权限。

我知道这与我检查权限的自定义方式无关,因为我使用它来请求蓝牙和其他权限

靠近底部的“FolderPickerResult”代码行返回异常。它不会抛出它,而是将其返回到“结果”对象/变量。异常消息是“未授予存储权限”。

这是返回的整个对象:

{
    FolderPickerResult {
        Folder = ,
        Exception = Microsoft.Maui.ApplicationModel.PermissionException: Storage permission is not granted.at CommunityToolkit.Maui.Storage.FolderPickerImplementation.InternalPickAsync(String initialPath, CancellationToken cancellationToken)in / _ / src / CommunityToolkit.Maui.Core / Essentials / FolderPicker / FolderPickerImplementation.android.cs: line 18 at CommunityToolkit.Maui.Storage.FolderPickerImplementation.PickAsync(String initialPath, CancellationToken cancellationToken)in / _ / src / CommunityToolkit.Maui.Core / Essentials / FolderPicker / FolderPickerImplementation.shared.cs: line 11,
        IsSuccessful = False
    }
}
Run Code Online (Sandbox Code Playgroud)

根据标题,我在毛伊岛项目中使用 C# .Net 7。

所以,我的问题是:我错过了什么/做错了什么?当我读到的所有内容都说它应该起作用时,为什么这不起作用?

这是我的代码,尽可能少:
AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.XXX.XXX" android:versionCode="1">
    <application android:allowBackup="true" android:icon="@mipmap/appicon" android:supportsRtl="true" android:label="XXX">
        <provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.fileprovider" android:exported="false" android:grantUriPermissions="true">
            <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths"></meta-data>
        </provider>
    </application>
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_STORAGE_PERMISSION" />
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.MANAGE_MEDIA" />
    <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
    <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
    <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
    <queries>
        <intent>
        <action android:name="android.media.action.IMAGE_CAPTURE" />
        </intent>
    </queries> 
</manifest>
Run Code Online (Sandbox Code Playgroud)

GalleryPermissions.cs

using static Microsoft.Maui.ApplicationModel.Permissions;

namespace Namespace;

internal class GalleryPermissions : BasePlatformPermission
{
#if ANDROID
    public override (string androidPermission, bool isRuntime)[] RequiredPermissions =>
        new List<(string permission, bool isRuntime)>
        {
            ("android.permission.WRITE_EXTERNAL_STORAGE", true),
            ("android.permission.READ_EXTERNAL_STORAGE", true),
            ("android.permission.MANAGE_EXTERNAL_STORAGE", true),
            ("android.permission.MANAGE_MEDIA", true),
            ("android.permission.READ_MEDIA_IMAGES", true),
            ("android.permission.READ_MEDIA_AUDIO", true),
            ("android.permission.READ_MEDIA_VIDEO", true),
            ("android.permission.READ_STORAGE_PERMISSION", true),
            ("android.permission.MEDIA_CONTENT_CONTROL", true)
        }.ToArray();
#endif
}

Run Code Online (Sandbox Code Playgroud)

MainPage.xaml.cs

using CommunityToolkit.Maui.Storage;

    protected override async void OnAppearing()
    {
        base.OnAppearing();

        PermissionStatus storageWriteStatus = await CheckPermission<Permissions.StorageWrite>();  // always "denied"
        PermissionStatus storageReadStatus = await CheckPermission<Permissions.StorageRead>();  // always "denied"

        PermissionStatus galleryStatus = await CheckPermission<GalleryPermissions>();  // always "denied"
        ...
    }

    private static async Task<PermissionStatus> CheckPermission<TPermission>() where TPermission : Permissions.BasePermission, new()
    {
        PermissionStatus status = await Permissions.CheckStatusAsync<TPermission>();

        if (status != PermissionStatus.Granted)
        {
            status = await Permissions.RequestAsync<TPermission>();
        }

        return status;
    }

    private async void SelectButton_Clicked(object sender, EventArgs e)
    {
        CancellationTokenSource source = new();

        // SaveLocation.Text is originally string.Empty, but even if I put in a breakpoint and set 
        // it to the same "/storage/emulated/0/DCIM/Camera" that I get returned on other devices, 
        // I still get an Exception and result.IsSuccessful is `false`
        FolderPickerResult result = await FolderPicker.PickAsync(SaveLocation.Text, source.Token);
        //FolderPickerResult result = await FolderPicker.Default.PickAsync(SaveLocation.Text, source.Token); // This is the same as the line above
        if (result.IsSuccessful)
        {
            SaveLocation.Text = result.Folder.Path;
        }
        else
        {
            // On API 33, this always prints "Storage Permission is not granted.", except when 
            // I try to remove permissions I've read aren't needed for API 33.
            await Toast.Make($"{result.Exception.Message}").Show(source.Token);
            Error.Text = result.Exception.Message;
        }
    }

Run Code Online (Sandbox Code Playgroud)

以下是我用来调试应用程序并运行 Android 13 (API 33) 的真实 Android 设备的屏幕截图,该设备恰好是三星 Galaxy A03s Tracfone。我已经包含了该应用程序的权限页面。

错误信息 应用程序权限页面

更新:

当我尝试将我的应用程序发布到 Google Play 商店时,我最初被拒绝,因为我试图使用该MANAGE_EXTERNAL_STORAGE权限。我删除了它,我的应用程序被允许发布。几周后,该应用程序被从商店中删除,因为它不符合 API 33 要求,尽管它是在早期 API 要求的截止日期之前发布的。我更新了 API 级别并在 API 33 设备上重新测试了文件夹选择器,没有任何问题。

这让我认为权限MANAGE_EXTERNAL_STORAGE和 API 33 的某种组合干扰了文件夹选择器功能。我不知道它是什么,但这是我改变的唯一两件事(技术上是一件事),现在它按预期工作。

Liy*_*SFT 3

这是 CommunityToolkit.Maui 包的问题。根据github上的资源代码var status = await Permissions.RequestAsync<Permissions.StorageRead>().WaitAsync(cancellationToken).ConfigureAwait(false);,如果的值为PermissionStatus.Denied,则会抛出此异常错误。

对于目标android 13,<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />已删除,因此有关权限请求的对话框永远不会出现在android 13或更高版本上。的值Permissions.RequestAsync<Permissions.StorageRead>将永远是PermissionStatus.Denied

因此,您可以将应用程序的目标 Android 版本更改为 Android 12,以使READ_EXTERNAL_STORAGE权限请求警报仍然出现在 Android 13 或更高版本的设备上。在AndroidManifest.xml中添加以下代码:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="31" />
Run Code Online (Sandbox Code Playgroud)

您还可以在 github 上发布 CommunityToolkit.Maui 包的新问题。

  • 从 2023 年 8 月 31 日开始,Google 要求新应用的目标 Android 版本必须为 33 或更高版本。但现在是六月。@computercarguy (2认同)