使用Xamarin.Android在Android 11上保存并读取照片的问题
解决方案:相机拍照存储与访问问题
问题根源分析
- Android 7.0+ URI限制:直接使用
Uri.FromFile()生成file://格式URI会触发FileUriExposedException,相机应用无法访问该路径。 - Scoped Storage(Android 10+):公共目录(如
ExternalStoragePublicDirectory)的直接文件操作被限制,必须通过MediaStoreAPI或申请对应权限才能读写。 - 应用私有目录特性:
GetExternalFilesDir()属于应用私有外部目录,MediaStore是用于管理公共媒体库的API,无需也不能用它来操作私有目录。
方案一:存储到应用私有外部目录(推荐)
此目录无需额外权限,应用拥有完全读写权限,卸载应用时文件会自动删除,符合隐私规范。
1. 配置FileProvider
在AndroidManifest.xml中添加FileProvider配置:
<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" /> </provider>
在res/xml下新建file_paths.xml,定义共享路径:
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-files-path name="belco_pictures" path="Pictures/BelcoPict" /> </paths>
2. 修改拍照代码
private bool DoPhoto(int KeepAsking) { Intent intent = new Intent(MediaStore.ActionImageCapture); // 检查是否有可用的相机应用 if (intent.ResolveActivity(PackageManager) == null) { return NoPhoto; } // 创建应用私有目录下的存储文件夹 var picturesDir = new File(Application.Context.GetExternalFilesDir(Environment.DirectoryPictures), "BelcoPict"); if (!picturesDir.Exists()) { picturesDir.Mkdirs(); } App._PhotoFile = new File(picturesDir, MyFile); // 使用FileProvider生成content://格式的URI Uri photoUri = FileProvider.GetUriForFile( Application.Context, $"{Application.Context.PackageName}.fileprovider", App._PhotoFile ); // 授予相机应用临时读写权限 intent.PutExtra(MediaStore.ExtraOutput, photoUri); intent.AddFlags(ActivityFlags.GrantReadUriPermission); intent.AddFlags(ActivityFlags.GrantWriteUriPermission); StartActivityForResult(intent, KeepAsking); return NoPhoto; }
方案二:存储到公共图片目录并实现访问
1. 权限申请
- Android 12及以下:动态申请
WRITE_EXTERNAL_STORAGE和READ_EXTERNAL_STORAGE权限 - Android 13及以上:动态申请
READ_MEDIA_IMAGES权限(读取),写入公共目录直接用MediaStoreAPI
2. 用MediaStore生成公共目录URI
private async Task<Uri> CreatePublicPhotoUri(string fileName) { ContentValues values = new ContentValues(); values.Put(MediaStore.Images.Media.Columns.DisplayName, fileName); values.Put(MediaStore.Images.Media.Columns.MimeType, "image/jpeg"); values.Put(MediaStore.Images.Media.Columns.RelativePath, $"{Environment.DirectoryPictures}/BelcoPict"); return Application.Context.ContentResolver.Insert(MediaStore.Images.Media.ExternalContentUri, values); } private bool DoPhoto(int KeepAsking) { Intent intent = new Intent(MediaStore.ActionImageCapture); if (intent.ResolveActivity(PackageManager) == null) { return NoPhoto; } // 生成公共目录的URI var uriTask = CreatePublicPhotoUri(MyFile); uriTask.Wait(); Uri photoUri = uriTask.Result; if (photoUri == null) { return NoPhoto; } intent.PutExtra(MediaStore.ExtraOutput, photoUri); StartActivityForResult(intent, KeepAsking); return NoPhoto; }
3. 读取公共目录照片
通过ContentResolver查询或直接访问URI即可,确保已申请对应权限:
// 示例:通过URI读取图片流 using (var stream = Application.Context.ContentResolver.OpenInputStream(photoUri)) { // 处理图片流 }
内容的提问来源于stack exchange,提问作者olivier Goossens Bara
相关产品推荐
相关产品推荐

