Xamarin:Android:System.UnauthorizedAccessException:拒绝访问路径

Xamarin : Android : System.UnauthorizedAccessException: Access to the path is denied

所以我正在尝试创建一个文件,我得到了 System.UnauthorizedAccessException: 拒绝访问路径“/DownloadJitters”。我不确定这是不是权限问题(我已经尝试写入外部存储以防万一,但那没有用)还是其他原因。此外,我正在尝试找出一个写这些文件的好地方,因为我希望它们不容易被发现。有任何想法吗?这也是代码:

public void favouriteList(MainActivity av, Ordering o, string favouriteName, string totalCost, JittersListView jlv)
    {
        //Checks Directory exists
        if (File.Exists(Android.OS.Environment.DirectoryDownloads + "/Jitters/FavouritesListAdded.txt") == false)
        {
            Directory.CreateDirectory(Android.OS.Environment.DirectoryDownloads + "Jitters/FavouriteList/");
            File.Create(Android.OS.Environment.DirectoryDownloads + "/Jitters/FavouritesListAdded.txt");
        }

        if (File.Exists(Android.OS.Environment.DirectoryDownloads + "Jitters/FavouriteList/" + favouriteName + ".txt") == false)
        {
            var fav = File.Create(Android.OS.Environment.DirectoryDownloads + "Jitters/FavouriteList/" + favouriteName + ".txt");
            fav.Close();
            string file = Android.OS.Environment.DirectoryDownloads + "Jitters/FavouriteList/" + favouriteName + ".txt";
            string added = null;
            int current = 0;
            while (true)
            {
                if (current < jlv.Count)
                {
                    JittersListItem jli = jlv[current];
                    added += jli.Top + "|" + jli.Bottom + "|" + jli.itemPic + "|" + jli.itemDes + System.Environment.NewLine;
                    current++;
                }
                else
                {
                    break;
                }
            }
            File.AppendAllText(file, favouriteName + "|" + totalCost + added);
        }
        else
        {
            new AlertDialog.Builder(av)
                    .SetMessage("Please use a different name, this one has been taken.")
                    .Show();
        }
    }

这看起来像是复制粘贴错误 - 您应该学习将通用代码和表达式重构为一个值并重新使用它。

//Checks Directory exists
if (File.Exists(Android.OS.Environment.DirectoryDownloads + "/Jitters/FavouritesListAdded.txt") == false)
{
    Directory.CreateDirectory(Android.OS.Environment.DirectoryDownloads + "Jitters/FavouriteList/");
    File.Create(Android.OS.Environment.DirectoryDownloads + "/Jitters/FavouritesListAdded.txt");
}

我们假设 Android.OS.Environment.DirectoryDownloads 的值为 /Downloads。现在逐行检查代码(你真的应该用调试器来做):

File.Exists(Android.OS.Environment.DirectoryDownloads + "/Jitters/FavouritesListAdded.txt")

这里的参数值会是"/Downloads/Jitters/FavouritesListAdded.txt" - OK

Directory.CreateDirectory(Android.OS.Environment.DirectoryDownloads + "Jitters/FavouriteList/");

这里的文字字符串没有前导斜线,所以值将是:/DownloadsJitters/FavouriteList - 我猜你可能是想 /Downloads/Jitters/FavouriteList.

与其确保将斜线添加到代码中的所有 6 个路径表达式,不如创建一个具有路径值 的变量并重复使用它

好的我通过将保存位置更改为来修复它 System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal)

当他们需要相同的权限时,不要问我为什么这样做有效。

首先将此权限添加到您的清单中:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

从 Android 6.0 (API 23) 开始,您还需要手动请求权限,将此代码添加到 Xamarin.Android 项目的 MainActivity.cs 中:

if ((ContextCompat.CheckSelfPermission(this, Manifest.Permission.WriteExternalStorage) != (int)Permission.Granted)
            || (ContextCompat.CheckSelfPermission(this, Manifest.Permission.ReadExternalStorage) != (int)Permission.Granted))
        {
            ActivityCompat.RequestPermissions(this, new string[] { Manifest.Permission.ReadExternalStorage, Manifest.Permission.WriteExternalStorage }, REQUEST);
        }

从 Android 10 开始,您可能还需要像这样向清单添加 android:requestLegacyExternalStorage 属性:

<application android:requestLegacyExternalStorage="true" />

更新

完成所有这些后,您可能仍然会在 Android 11 或更高版本上遇到异常如果您尝试在 SD 卡的任何路径上保存文件,则只能使用您的外部存储和内部存储路径默认应用。

您应用程序的私有外部存储路径:

Android.App.Application.Context.GetExternalFilesDir(null).AbsolutePath

您应用程序的私有内部存储路径:

System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData)

如果您需要访问Android 11或更高版本的SDCARD的任何路径,您应该请求管理所有文件访问权限。

private void RequestStorageAccess()
{
    if (!Environment.IsExternalStorageManager) 
    {
        StartActivityForResult(new Intent(Android.Provider.Settings.ActionManageAllFilesAccessPermission), 3);
    }
}

这将弹出一个 Activity 列出所有有权访问所有文件访问权限的应用程序,用户应点击您的应用程序并启用权限。

但是为了让您的应用出现在那里,您应该将此权限添加到您的清单中:

<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>

并且您还需要确保使用 Android 11 或更高版本编译您的应用程序。

这里可以看到权限Activity:

出于某种原因,我可以在没有此权限的情况下将文件保存到文档文件夹,但是删除文件后我无法再次创建具有相同名称的相同文件,而且我无法列出所有文件,这就是为什么即使使用文档目录也需要此弹出窗口的原因。

仅在您确实需要时使用管理所有文件权限

如果您想在 Google Play 中使用“管理所有文件权限”发布您的应用程序,您必须在 Google Play 中填写“权限声明表”,因为它是被认为是 “高危或敏感权限”这里有更多信息,Google应该批准:

https://support.google.com/googleplay/android-developer/answer/9214102?hl=en#zippy=

何时请求此权限:

https://support.google.com/googleplay/android-developer/answer/10467955?hl=en

如果您在 Xamarin 中写入或读取文件时仍然收到 UnauthorizedAccessException Android。我刚刚写了一篇文章来解决它 http://bsubramanyamraju.blogspot.com/2019/12/resolved-unauthorizedaccessexception.html

Xamarin.Forms(Android解)

MainActivity.cs

  • For apps that target Android 5.1(API level 22) or lower, there is nothing more that needs to be done.
  • Apps that will run on Android 6.0(API 23 level 23) or higher should ask Run time permission checks.
protected override void OnCreate(Bundle bundle)
{
    if (Build.VERSION.SdkInt >= BuildVersionCodes.M)
    {
        if (!(CheckPermissionGranted(Manifest.Permission.ReadExternalStorage) && !CheckPermissionGranted(Manifest.Permission.WriteExternalStorage)))
        {
            RequestPermission();
        }
    }
    LoadApplication(new App());
}

private void RequestPermission()
{
    ActivityCompat.RequestPermissions(this, new string[] { Manifest.Permission.ReadExternalStorage, Manifest.Permission.WriteExternalStorage }, 0);
}

public bool CheckPermissionGranted(string Permissions)
{
    // Check if the permission is already available.
    if (ActivityCompat.CheckSelfPermission(this, Permissions) != Permission.Granted)
    {
        return false;
    }
    else
    {
        return true;
    }
}