升级Android应用至API 33后,相机Intent返回Result.Cancelled问题排查
问题:升级到API 33后调用相机OnActivityResult始终返回Result.Cancelled
我正在将原本适配API 9(Pie)的可用应用升级至API 33(Tiramisu),但调用相机后,OnActivityResult始终返回Result.Cancelled。开发环境为Windows 11专业版上的Visual Studio 2022 17.3.6。
相关代码
相机点击事件代码
camerabutton.Click += (sender, evt) => { var cameraispresent = checkCameraHardware(this); if (cameraispresent) { try { CreateDirectoryForPictures(); MySubjectInfo.Name = MySubjectInfo.Name.Replace(",", ""); MySubjectInfo.Name = MySubjectInfo.Name.Replace(" ", ""); MySubjectInfo.Name = MySubjectInfo.Name.Replace(".", ""); var filename = MySubjectInfo.Name + ".jpg"; Intent intent = new Intent(MediaStore.ActionImageCapture); App._file = new File(App._dir, String.Format(filename, Guid.NewGuid())); intent.PutExtra(MediaStore.ExtraOutput, Uri.FromFile(App._file)); StartActivityForResult(intent, TakePictureRequestCode); } catch (Exception e) { var result = e.Message; }; } };
创建目录代码
private void CreateDirectoryForPictures() { int version = (int)Android.OS.Build.VERSION.SdkInt; var root = ""; if (Android.OS.Environment.IsExternalStorageEmulated) { root = Android.OS.Environment.ExternalStorageDirectory.ToString(); } else { root = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyPictures); } if (version >= Convert.ToInt32(BuildVersionCodes.Q)) { App._dir = new File(root + "/PhotoManager"); } else { App._dir = new File( Environment.GetExternalStoragePublicDirectory( Environment.DirectoryPictures), "PhotoManager"); } if (!App._dir.Exists()) { App._dir.Mkdirs(); } }
存储数据的App类
public static class App { public static File _file; public static File _dir; public static Bitmap bitmap; }
OnActivityResult代码
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) { base.OnActivityResult(requestCode, resultCode, data); if (requestCode == TakePictureRequestCode && resultCode == Result.Ok) { //Intent mediaScanIntent = new Intent(Intent.ActionMediaScannerScanFile); //Uri contentUri = Uri.FromFile(App._file); //mediaScanIntent.SetData(contentUri); //SendBroadcast(mediaScanIntent); Bitmap photo = (Bitmap)data.GetByteArrayExtra("data"); App.bitmap = photo; //int height = 160; //Resources.DisplayMetrics.HeightPixels; //int width = 100; //MyImageView.Height; //App.bitmap = App._file.Path.LoadAndResizeBitmap(width, height); if (App.bitmap != null) { MyImageView.SetImageBitmap(App.bitmap); Bitmap bitmap = App.bitmap; var filePath = App._file.AbsolutePath; var stream = new System.IO.FileStream(filePath, System.IO.FileMode.Create); bitmap.Compress(Bitmap.CompressFormat.Jpeg, 100, stream); stream.Close(); bitmap = null; App.bitmap = null; PictureTaken = true; } // Dispose of the Java side bitmap. GC.Collect(); } }
问题分析与修复方案
1. API 29+ 禁止直接使用Uri.FromFile()传递文件路径
Android 10(API 29)启用Scoped Storage后,直接通过Uri.FromFile()生成的Uri无法被其他应用(如相机)访问,相机因权限不足直接取消操作。
修复步骤:
- 在
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> - 在
Resources/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="my_images" path="Pictures/PhotoManager" /> </paths> - 修改相机点击事件中的Uri生成逻辑:
// 替换Uri.FromFile(App._file) Uri photoUri = FileProvider.GetUriForFile(this, PackageName + ".fileprovider", App._file); intent.PutExtra(MediaStore.ExtraOutput, photoUri); // 添加临时访问权限 intent.AddFlags(ActivityFlags.GrantReadUriPermission); intent.AddFlags(ActivityFlags.GrantWriteUriPermission);
2. API 33 需单独申请相机权限
Android 13(API 33)新增了CAMERA权限的单独申请要求,未申请时相机调用会被系统拦截。
修复步骤:
- 在
AndroidManifest.xml添加权限声明:<uses-permission android:name="android.permission.CAMERA" /> <uses-feature android:name="android.hardware.camera" android:required="false" /> - 调用相机前动态申请权限:
if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.Camera) != Permission.Granted) { ActivityCompat.RequestPermissions(this, new string[] { Manifest.Permission.Camera }, 1001); return; } // 权限通过后再执行相机调用逻辑 - 处理权限申请结果:
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults) { base.OnRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == 1001) { if (grantResults.Length > 0 && grantResults[0] == Permission.Granted) { // 重新触发相机调用 OpenCamera(); // 将原相机点击逻辑封装为OpenCamera方法 } } }
3. 目录创建不符合Scoped Storage规范
原目录创建逻辑使用ExternalStorageDirectory,在API 29+下会导致权限问题,且文件管理器无法访问该目录。
修复步骤:
修改CreateDirectoryForPictures方法,使用应用专属外部存储目录:
private void CreateDirectoryForPictures() { // 应用私有外部存储图片目录,无需额外权限 App._dir = new File(GetExternalFilesDir(Android.OS.Environment.DirectoryPictures), "PhotoManager"); if (!App._dir.Exists()) { App._dir.Mkdirs(); } }
4. OnActivityResult图片获取逻辑错误
指定MediaStore.ExtraOutput后,相机不会在data中返回缩略图,直接从指定路径读取图片即可,无需从data中获取再保存。
修复步骤:
修改OnActivityResult代码:
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) { base.OnActivityResult(requestCode, resultCode, data); if (requestCode == TakePictureRequestCode && resultCode == Result.Ok) { // 从指定路径加载图片,避免内存溢出 BitmapFactory.Options options = new BitmapFactory.Options(); options.InJustDecodeBounds = true; BitmapFactory.DecodeFile(App._file.AbsolutePath, options); options.InSampleSize = CalculateInSampleSize(options, MyImageView.Width, MyImageView.Height); options.InJustDecodeBounds = false; App.bitmap = BitmapFactory.DecodeFile(App._file.AbsolutePath, options); if (App.bitmap != null) { MyImageView.SetImageBitmap(App.bitmap); PictureTaken = true; } GC.Collect(); } } // 计算图片缩放比例,防止内存溢出 private int CalculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { int height = options.OutHeight; int width = options.OutWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { int halfHeight = height / 2; int halfWidth = width / 2; while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) { inSampleSize *= 2; } } return inSampleSize; }
内容的提问来源于stack exchange,提问作者Prescott Chartier
相关产品推荐
相关产品推荐

