Xamarin Android写入外部存储DCIM目录遇权限拒绝问题求助
我的问题场景
我在PCL项目中做视频下载功能,遇到两个存储相关的问题:
1. 写入公共DCIM目录被拒绝
我用以下代码获取DCIM目录路径,返回"/storage/emulated/0/DCIM/":
#if __ANDROID__ var dir = Android.OS.Environment .GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDcim); var publicPath= dir.AbsolutePath; var newFilepath = System.IO.Path.Combine(publicPath, fileName); video.path = newFilepath; #endif
已经在Android.Manifest中添加了权限:
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
但执行下载函数到target.Write(buffer, 0, bytes);时,抛出Access to the path is denied异常。我尝试用Plugin.permissions动态申请权限:
Dictionary<Permission, PermissionStatus> results = await CrossPermissions.Current.RequestPermissionsAsync(Permission.Storage); PermissionStatus status = results[Permission.Storage];
结果status = Permission.Unknown(原问题中的笔误已修正)。
2. 私有路径可写入但无法查看文件
我尝试把视频保存到应用私有路径,代码返回"/data/user/0/com.companyname.VideoDownloader/files/":
var dir = Environment.GetFolderPath(Environment.SpecialFolder.Personal); video.path = System.IO.Path.Combine(dir, filename);
此时下载函数能成功写入所有字节,但我在设备上找不到这个文件。
我的SDK配置:
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="25" />
问题原因分析
DCIM写入失败的核心原因
- 动态权限未正确申请:Android 6.0(API 23)及以上,
WRITE_EXTERNAL_STORAGE属于危险权限,即使在Manifest中声明,也需要在运行时动态申请。你的targetSdkVersion是25,覆盖了API 23+的设备,这一步是必须的。你用的Plugin.permissions返回Unknown,大概率是插件初始化不完整或者版本适配问题,导致权限请求没有正确执行。 - 另外,虽然你的targetSdk是25,还没到Android 10(API 29)的存储沙箱限制,但如果设备系统版本高于25,也可能受影响,但主要问题还是动态权限。
私有路径文件无法查看的原因
/data/user/0/com.companyname.VideoDownloader/files/是应用的私有内部存储目录,这个路径只有你的应用拥有访问权限,系统默认的文件管理器是看不到的,除非设备获取了Root权限,或者通过ADB命令来访问。
可行的解决方案
解决DCIM写入权限问题
换用更可靠的权限申请方式:推荐使用Xamarin.Essentials的权限API,比第三方插件更稳定:
// 先检查权限状态 var status = await Permissions.CheckStatusAsync<Permissions.StorageWrite>(); if (status != PermissionStatus.Granted) { // 请求权限 status = await Permissions.RequestAsync<Permissions.StorageWrite>(); } if (status == PermissionStatus.Granted) { // 权限已授予,执行下载逻辑 downloadVideo(video, false); } else { // 权限被拒绝,提示用户开启权限 Console.WriteLine("存储权限被拒绝,无法保存视频到DCIM"); }注意:权限请求需要在Activity上下文里执行,确保你的代码是在MainActivity或其他Activity中调用的。
修复Plugin.permissions的使用问题:如果你坚持用这个插件,要确保在Android项目的
MainActivity中完成初始化:protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); // 初始化插件 CrossPermissions.Current.Init(this); // 其他初始化代码 } // 重写权限回调方法 public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Android.Content.PM.Permission[] grantResults) { PermissionsImplementation.Current.OnRequestPermissionsResult(requestCode, permissions, grantResults); base.OnRequestPermissionsResult(requestCode, permissions, grantResults); }确保目录存在:在下载前先检查DCIM目录是否存在,不存在就创建:
if (!Directory.Exists(publicPath)) { Directory.CreateDirectory(publicPath); }
解决私有路径文件无法查看的问题
复制到公共目录:下载完成后,把私有路径的文件复制到DCIM等公共目录(记得先申请存储权限):
string privatePath = video.path; string publicPath = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDcim).AbsolutePath; string destPath = Path.Combine(publicPath, fileName); File.Copy(privatePath, destPath, overwrite: true);通过MediaStore注册文件到媒体库:即使文件在私有路径,也可以把它的信息注册到系统媒体库,这样相册就能识别:
// 需要Android原生的ContentResolver,可通过Activity获取 ContentValues values = new ContentValues(); values.Put(MediaStore.Video.Media.Columns.DISPLAY_NAME, fileName); values.Put(MediaStore.Video.Media.Columns.MIME_TYPE, "video/mp4"); values.Put(MediaStore.Video.Media.Columns.RELATIVE_PATH, Environment.DirectoryDcim); Android.Net.Uri uri = ContentResolver.Insert(MediaStore.Video.Media.ExternalContentUri, values); using (Stream outputStream = ContentResolver.OpenOutputStream(uri)) { using (Stream inputStream = File.OpenRead(privatePath)) { inputStream.CopyTo(outputStream); } }调试时用ADB导出文件:如果只是调试需要查看文件,用ADB命令把文件拉到电脑:
adb shell run-as com.companyname.VideoDownloader cat files/你的视频文件名.mp4 > 本地保存路径.mp4
内容的提问来源于stack exchange,提问作者user2905416

