如何在Jetpack Compose中调用系统原生相机应用拍摄并存储照片
首先纠正一个常见误区:废弃的是旧版Camera框架的内置相机实现API,跨应用调用系统相机的Intent协议从来没有被废弃,也和你用View还是Compose没有关系。Camera2、CameraX是用来在应用内搭建自定义相机界面的,如果你只是想拉起系统自带相机拍全分辨率照片、存到指定路径,完全不需要引入这两个库,用Compose适配的Activity Result API就能实现,全程不需要写View相关代码。
1. 前置配置
首先在AndroidManifest.xml里加必要配置:
- 权限声明:
- Android 13及以上:如果需要读取拍摄的照片展示,声明
android.permission.READ_MEDIA_IMAGES - Android 12及以下:声明
android.permission.READ_EXTERNAL_STORAGE - Android 9及以下:额外声明
android.permission.WRITE_EXTERNAL_STORAGE
- Android 13及以上:如果需要读取拍摄的照片展示,声明
- 配置FileProvider:Android 7.0以上禁止直接向外部应用传递
file://格式的文件路径,必须通过FileProvider生成可访问的content://Uri,配置如下:
<application> <!-- 其他应用配置 --> <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配置允许FileProvider访问的路径:
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <!-- 配置应用专属图片目录为可访问路径 --> <external-files-path name="my_app_photos" path="Pictures/" /> </paths>
2. Compose中实现拍照逻辑
直接用AndroidX提供的rememberLauncherForActivityResult注册拍照回调,不需要依赖旧版View体系的onActivityResult:
@Composable fun CameraCaptureScreen() { val context = LocalContext.current // 持有拍摄照片的目标Uri var capturedPhotoUri by remember { mutableStateOf<Uri?>(null) } // 注册系统相机拍照结果接收器 val takePictureLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.TakePicture() ) { isSuccess -> if (isSuccess) { // 拍照成功,capturedPhotoUri就是全分辨率照片的存储地址,可直接加载、上传、处理 capturedPhotoUri?.let { uri -> // 直接调用图片加载库加载即可,比如AsyncImage(model = uri, ...) } } // 失败/用户取消的场景按需处理即可 } Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Button(onClick = { // 生成照片存储文件 val photoFile = File( context.getExternalFilesDir(Environment.DIRECTORY_PICTURES), "CAPTURE_${System.currentTimeMillis()}.jpg" ) // 生成FileProvider可用的content Uri capturedPhotoUri = FileProvider.getUriForFile( context, "${context.packageName}.fileprovider", photoFile ) // 拉起系统相机 takePictureLauncher.launch(capturedPhotoUri!!) }) { Text("调用系统相机拍照") } // 拍照成功后展示照片 capturedPhotoUri?.let { uri -> AsyncImage( model = uri, contentDescription = "拍摄的照片", modifier = Modifier.size(300.dp) ) } } }
3. 可选:保存照片到系统公共相册
如果需要让拍摄的照片在系统相册全局可见,不需要存在应用专属目录,可以直接把输出Uri指定为MediaStore公共图片集合的地址,不需要额外配置FileProvider:
// 创建公共目录图片Uri的方法 fun createPublicPhotoUri(context: Context): Uri { val contentValues = ContentValues().apply { put(MediaStore.Images.Media.DISPLAY_NAME, "IMG_${System.currentTimeMillis()}.jpg") put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/MyApp") } } return context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)!! }
把这个方法生成的Uri直接传给takePictureLauncher.launch()即可,系统相机会直接把照片写入公共相册目录,Android 10及以上不需要申请存储权限。
注意:很多旧教程提到调用相机后只返回缩略图,是因为没有给Intent传入输出Uri参数导致的,只要传入了指定的输出Uri,系统相机默认会写入全分辨率原图,不会返回缩略图,这个行为在所有主流Android版本上保持一致。
内容的提问来源于stack exchange,提问作者Raj Narayanan

