You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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,如果权限已提前授权,直接调用相机时使用的是初始化阶段生成的旧文件——该文件可能因系统缓存清理、权限过期等原因,导致相机应用无法正常写入内容,最终生成空文件;而权限刚授权时调用相机,使用的是较新创建的文件,写入流程正常。

解决步骤

  1. 每次启动相机前重新创建临时文件与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()
        }
    }
    
  2. 增加文件创建校验(可选)
    在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á

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 18:35:12