我正在try 让应用程序用户 Select 一个文件夹来保存从相机拍摄的照片.如果我需要使用与我正在使用的NuGet包或内置功能不同的NuGet包,我可以做到.我正在使用的是我发现的第一台有文件夹 Select 器的电脑,我可以开始工作了.

我已经阅读了几十篇文章、答案、问题、技术文档、演练、观看的视频等,试图访问以 Select 要保存的相机图像的文件夹.不幸的是,似乎什么都不起作用,因为我一直收到一个异常,说我没有"存储"权限,尽管我已经try 了几种方法来获得它们.我还申请了几乎所有与"存储"相关的权限,即使我不认为是真正的权限,但在我读到的一份文件中提到了它.

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

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

我知道这与我判断权限的定制方式无关,因为我使用它来请求蓝牙和other permissions.

接近底部的"FolderPickerResult"代码行返回异常.它不抛出它,而是将它返回给"Result"对象/变量.例外消息是"未授予存储权限".

这是返回的整个对象:

{
    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
    }
}

根据标题,我在毛伊岛的一个项目中使用C#.NET7.

我错过了什么/做错了什么?当我读到的每一本书都说它应该起作用的时候,为什么这个不起作用?

Here's my code, as minimal as possible:
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>

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
}

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;
        }
    }

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

Error message App Permissions page

推荐答案

这是CommunityToolkit.Maui包的问题.根据resource code on the github,如果var status = await Permissions.RequestAsync<Permissions.StorageRead>().WaitAsync(cancellationToken).ConfigureAwait(false);的值是PermissionStatus.Denied,则抛出此异常错误.

而对于目标安卓13,<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />已经被移除,所以关于权限请求的对话框永远不会出现在安卓13或更高版本上.Permissions.RequestAsync<Permissions.StorageRead>的值始终为PermissionStatus.Denied.

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

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="31" />

你也可以在GitHub上发布一期新的社区工具包.

Csharp相关问答推荐

[0-n]范围内有多少个Integer在其小数表示中至少包含一个9?

为什么我不能更改尚未设置的模拟对象属性的值?

ASP.NET核心结果.文件vs结果.流

属性getter和setter之间的空性不匹配?

是否可以使用EF—Core进行临时部分更新?

如何注册实现同一接口的多个服务并注入到控制器构造函数中

try 在Blazor项目中生成html

如何忽略API JSON响应中的空字符串?

实体框架核心中的ComplexType和OwnsOne有什么不同?

如何使用自定义负载均衡器管理Ocelot负载均衡器中的多线程和批读取

try 在.Net核心身份注册页面中使用AJAX,但没有成功..NET Core 5.0 Razor页面应用程序

使用switch 类型模式时出现奇怪的编译器行为

在C#ASP.NET内核中使用INT AS-1进行控制器场景的单元测试

我的命名管道在第一次连接后工作正常,但后来我得到了System.ObjectDisposedException:无法访问关闭的管道

如何从非异步任务中正确返回TypeResult

.NET Google Workspace API获取错误CS0266

VS代码扩展无法在新版本扩展C#中运行从v2.10.28开始

Xamarin.Forms-如何创建可 Select 的显示alert 或弹出窗口?

多个参数的最小API删除

无法通过服务控制台启动.NET Core 6.0服务(错误1053)