安卓嵌套Fragment调用相机拍照方法及旋转丢失图片问题
安卓Fragment调用相机拍摄图片的完整解决方案
我来帮你搞定这两个问题,尤其是嵌套Fragment弹窗调用相机时出现的旋转丢图痛点,咱们一步步拆解:
一、基础:Fragment中调用相机的正确姿势
在Fragment里调用相机,核心要注意权限申请、安全Uri适配、正确的回调传递这几点,基础流程如下:
- 申请相机和存储权限(Android 6.0+需动态申请)
- 创建唯一的临时图片文件存储拍摄结果
- 使用Fragment自身的
startActivityForResult启动相机(避免嵌套场景下回调丢失) - 在
onActivityResult中处理拍摄结果(别在启动相机后立即读取文件,此时相机还未完成拍摄)
二、解决嵌套Fragment弹窗调用相机时旋转丢图的问题
先分析你现有代码的几个核心问题:
- 直接调用
((Activity) context).startActivityForResult:嵌套Fragment的回调会被Activity拦截,导致你的Fragment拿不到拍摄结果 - 屏幕旋转时Fragment重建,临时文件路径未保存:重建后
tempImagePath丢失,自然找不到图片 - 使用
Uri.fromFile(f):Android 7.0+会触发FileUriExposedException,违反系统安全限制 - 启动相机后立即读取文件:此时相机还未完成拍摄,文件为空,逻辑错误
下面是修正后的完整实现:
1. 配置Manifest权限和FileProvider
首先在AndroidManifest.xml中添加权限和注册FileProvider(适配7.0+的安全Uri):
<!-- 相机和存储权限 --> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <uses-feature android:name="android.hardware.camera" android:required="true" /> <application> <!-- 注册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> </application>
然后在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="camera_images" path="Pictures" /> </paths>
2. Fragment核心代码实现
public class YourNestedFragment extends Fragment { private static final int REQUEST_PERMISSIONS = 100; private static final int REQUEST_IMAGE_CAPTURE = 101; private String tempImagePath; // 保存临时图片路径,旋转时恢复 private View addImage; private View image; private ImageView imagePr; @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.your_fragment_layout, container, false); addImage = view.findViewById(R.id.add_image); image = view.findViewById(R.id.image); imagePr = view.findViewById(R.id.image_pr); // 恢复旋转前的临时图片路径 if (savedInstanceState != null) { tempImagePath = savedInstanceState.getString("temp_image_path"); } // 点击拍照按钮触发逻辑 addImage.setOnClickListener(v -> { if (checkPermissions()) { launchCamera(); } }); return view; } // 检查并申请权限 private boolean checkPermissions() { if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { requestPermissions(new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_PERMISSIONS); return false; } return true; } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == REQUEST_PERMISSIONS) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED) { launchCamera(); } else { Toast.makeText(requireContext(), "需要相机和存储权限才能拍照", Toast.LENGTH_SHORT).show(); } } } // 创建临时图片文件 private File createImageFile() throws IOException { String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date()); String imageFileName = "JPEG_" + timeStamp + "_"; File storageDir = requireContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES); File imageFile = File.createTempFile(imageFileName, ".jpg", storageDir); tempImagePath = imageFile.getAbsolutePath(); return imageFile; } // 启动相机 private void launchCamera() { Intent picIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); // 检查是否有相机应用可以处理 if (picIntent.resolveActivity(requireContext().getPackageManager()) == null) { Toast.makeText(requireContext(), "未找到相机应用", Toast.LENGTH_SHORT).show(); return; } File photoFile = null; try { photoFile = createImageFile(); } catch (IOException e) { Toast.makeText(requireContext(), "创建图片文件失败", Toast.LENGTH_SHORT).show(); return; } if (photoFile != null) { // 用FileProvider生成安全Uri Uri photoUri = FileProvider.getUriForFile(requireContext(), requireContext().getPackageName() + ".fileprovider", photoFile); picIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri); // 用Fragment自己的startActivityForResult,确保回调能传到当前Fragment startActivityForResult(picIntent, REQUEST_IMAGE_CAPTURE); } } // 处理相机回调 @Override public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_IMAGE_CAPTURE) { if (resultCode == Activity.RESULT_OK) { // 拍摄成功,加载图片 if (tempImagePath != null && !tempImagePath.isEmpty()) { addImage.setVisibility(View.GONE); image.setVisibility(View.VISIBLE); // 建议添加图片压缩逻辑,避免OOM Bitmap bitmap = BitmapFactory.decodeFile(tempImagePath); imagePr.setImageBitmap(bitmap); } } else if (resultCode == Activity.RESULT_CANCELED) { // 用户取消拍摄,删除临时文件 if (tempImagePath != null) { File tempFile = new File(tempImagePath); if (tempFile.exists()) { tempFile.delete(); } } } } } // 保存临时路径,防止旋转丢失 @Override public void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); outState.putString("temp_image_path", tempImagePath); } }
3. 额外注意事项
- 图片压缩:直接用
BitmapFactory.decodeFile加载大尺寸图片容易触发OOM,建议用BitmapFactory.Options设置inSampleSize来压缩图片 - 嵌套Fragment回调传递:如果你的Fragment嵌套在父Fragment中,一定要确保父Fragment的
onActivityResult调用了super.onActivityResult,否则子Fragment收不到回调 - 屏幕旋转替代方案:如果不想处理状态保存,也可以给Activity添加
android:configChanges="orientation|screenSize",禁止旋转时重建Activity,但这种方式会跳过系统生命周期重建,不推荐作为首选
内容的提问来源于stack exchange,提问作者Nour eldien
相关产品推荐
相关产品推荐

