Jetpack Compose中TakePicture Launcher仅首次授权后可用问题
相机拍照后文件内容为空的问题(仅权限已提前授权时出现)
我通过Retrofit Multipart发送图片,实现了相册/相机选择对话框,相册功能正常,但相机存在异常:仅在请求权限后首次可用。具体表现为:
- 相机权限已提前授权时,启动相机后获取的URI对应的文件存在(
.exists()为true),但Content-Length为0; - 只有在弹出权限对话框并授权后,本次拍照才能正常获取到完整的图片内容。
一、相关代码实现
1. Launcher定义
相机Launcher
val cameraLauncher = rememberLauncherForActivityResult(ActivityResultContracts.TakePicture()) { if (it && uri.path?.isNotEmpty() == true) { viewmodel.onImageUpdated(uri) } }
权限请求Launcher
val permissionLauncher = rememberLauncherForActivityResult( ActivityResultContracts.RequestPermission() ) { if (it) { cameraLauncher.launch(uri) } else { Toast.makeText(context, "Permission Denied", Toast.LENGTH_SHORT).show() } }
2. 临时文件与URI创建
val context = LocalContext.current val file = context.createImageFile() val uri = FileProvider.getUriForFile( Objects.requireNonNull(context), BuildConfig.APPLICATION_ID + ".provider", file ) fun Context.createImageFile(): File { val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date()) val imageFileName = "JPEG_" + timeStamp + "_" return File.createTempFile( imageFileName, ".jpg", externalCacheDir ) }
3. Manifest配置
<provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.provider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/path_provider" /> </provider>
4. 路径配置文件(res/xml/path_provider.xml)
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-cache-path name="my_images" path="/"/> </paths>
5. 相机调用逻辑
权限已授权时的调用(此场景下文件内容为空)
val permissionCheckResult = ContextCompat.checkSelfPermission( context, android.Manifest.permission.CAMERA ) if (permissionCheckResult == PackageManager.PERMISSION_GRANTED) { cameraLauncher.launch(uri) } else { permissionLauncher.launch(android.Manifest.permission.CAMERA) }
权限未授权时的调用(此场景下正常工作)
通过permissionLauncher请求权限后,在回调中调用cameraLauncher.launch(uri),此时拍照后文件内容正常。
二、问题分析与解决方案
核心原因
问题出在临时文件与URI的创建时机:当前代码在Composable初始化时就创建了file和uri,如果权限已提前授权,直接调用相机时使用的是初始化阶段生成的旧文件——该文件可能因系统缓存清理、权限过期等原因,导致相机应用无法正常写入内容,最终生成空文件;而权限刚授权时调用相机,使用的是较新创建的文件,写入流程正常。
解决步骤
每次启动相机前重新创建临时文件与URI
将文件和URI的创建逻辑移到相机点击事件内,确保每次启动相机都使用全新的临时文件:// 相机点击事件处理 onCameraClick = { val permissionCheckResult = ContextCompat.checkSelfPermission( context, android.Manifest.permission.CAMERA ) if (permissionCheckResult == PackageManager.PERMISSION_GRANTED) { // 每次启动相机前生成新的临时文件和URI val file = context.createImageFile() val uri = FileProvider.getUriForFile( context, BuildConfig.APPLICATION_ID + ".provider", file ) cameraLauncher.launch(uri) } else { permissionLauncher.launch(android.Manifest.permission.CAMERA) } }同时修改权限请求回调,确保授权后也使用新的文件:
val permissionLauncher = rememberLauncherForActivityResult( ActivityResultContracts.RequestPermission() ) { granted -> if (granted) { val file = context.createImageFile() val uri = FileProvider.getUriForFile( context, BuildConfig.APPLICATION_ID + ".provider", file ) cameraLauncher.launch(uri) } else { Toast.makeText(context, "Permission Denied", Toast.LENGTH_SHORT).show() } }增加文件创建校验(可选)
在createImageFile()中添加日志,确认文件是否正常创建,排查潜在的文件生成问题:fun Context.createImageFile(): File { val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) val imageFileName = "JPEG_" + timeStamp + "_" val file = File.createTempFile( imageFileName, ".jpg", externalCacheDir ) Log.d("ImageFile", "文件路径:${file.absolutePath},是否存在:${file.exists()},初始大小:${file.length()}") return file }
内容的提问来源于stack exchange,提问作者Aris Guimerá
相关产品推荐
相关产品推荐

