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

首次安装/清数据后相机拍照文件为空,二次操作正常问题排查

问题描述

我为用户提供两种头像上传方式:相册选图(PhotoPicker)和相机拍照。首次启动应用或清除应用数据后,相册选图功能无论首次还是二次操作均正常;但相机拍照时,首次生成的文件长度为0,无法在Android Studio文件管理器中打开,导致无法上传至服务器,二次拍照则完全正常。

相机拍照的Composable代码

val file = remember { context.createImageFile() }
val uri = FileProvider.getUriForFile(Objects.requireNonNull(context), BuildConfig.APPLICATION_ID + ".provider", file)

val cameraLauncher =
            rememberLauncherForActivityResult(ActivityResultContracts.TakePicture()) { imageWasSaved ->
                 if (imageWasSaved) handleFileResult(context, uri, uploadPhoto)
            }

...

cameraLauncher.launch(uri)

相册选图代码

val singlePhotoPickerLauncher = rememberLauncherForActivityResult(
           contract = ActivityResultContracts.PickVisualMedia(),
           onResult = { uri ->
                handleFileResult(context, uri, uploadPhoto)
           }
)

handleFileResult函数代码

fun handleFileResult(
    context: Context,
    uri: Uri?,
    onResult: (File) -> Unit,
) {
    uri?.run {
        val parcelFileDescriptor = context.contentResolver.openFileDescriptor(this, "r", null)
        parcelFileDescriptor?.let {
            val inputStream = FileInputStream(it.fileDescriptor)
            val file = File(context.cacheDir, context.contentResolver.getFileName(this))
            val outputStream = FileOutputStream(file)
            val inChannel = inputStream.channel
            val outChannel = outputStream.channel

            try {
                inChannel.transferTo(0, inChannel.size(), outChannel)
                inputStream.close()
                outputStream.close()
                it.close()
                onResult(file)
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }
    }
}
问题原因与修复方案

核心问题:remember 缓存导致的文件复用异常

你用 remember { context.createImageFile() } 缓存拍照文件,首次启动应用或清数据后,Composable初始化时会创建一个空文件并被remember保留。当第一次调用相机拍照时,系统尝试写入内容到该文件,但由于remember的缓存特性,后续即使重组,文件实例还是最初的空文件——相机应用可能因文件状态异常(比如已存在但无内容、权限感知问题)导致写入失败,最终生成0字节文件;而二次拍照时,文件已被正确初始化,所以恢复正常。

修复步骤

  1. 移除remember,每次拍照前创建新文件
    不要缓存拍照用的文件实例,每次触发拍照时才创建全新文件,确保相机写入的是未被使用过的文件:

    val cameraLauncher = rememberLauncherForActivityResult(ActivityResultContracts.TakePicture()) { imageWasSaved ->
        if (imageWasSaved) {
            handleFileResult(context, currentUri, uploadPhoto)
        }
    }
    
    // 临时存储当前拍照的Uri
    var currentUri by remember { mutableStateOf<Uri?>(null) }
    Button(onClick = {
        val file = context.createImageFile()
        val uri = FileProvider.getUriForFile(Objects.requireNonNull(context), BuildConfig.APPLICATION_ID + ".provider", file)
        currentUri = uri
        cameraLauncher.launch(uri)
    }) {
        Text("拍照")
    }
    
  2. 确保createImageFile()生成唯一文件名
    检查你的createImageFile方法,必须保证每次生成的文件名唯一,避免文件覆盖冲突:

    fun Context.createImageFile(): File {
        val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
        val imageFileName = "AVATAR_${timeStamp}_"
        val storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
        return File.createTempFile(
            imageFileName,
            ".jpg",
            storageDir
        )
    }
    
  3. 优化handleFileResult的资源管理
    原代码中流关闭逻辑存在异常泄漏风险,改用Kotlin的use函数自动管理资源,确保流在任何情况下都会被关闭:

    fun handleFileResult(
        context: Context,
        uri: Uri?,
        onResult: (File) -> Unit,
    ) {
        uri?.run {
            context.contentResolver.openFileDescriptor(this, "r")?.use { pfd ->
                FileInputStream(pfd.fileDescriptor).use { inputStream ->
                    val fileName = context.contentResolver.getFileName(this) ?: "temp_avatar.jpg"
                    val targetFile = File(context.cacheDir, fileName)
                    FileOutputStream(targetFile).use { outputStream ->
                        inputStream.channel.transferTo(0, inputStream.channel.size(), outputStream.channel)
                        onResult(targetFile)
                    }
                }
            }
        }
    }
    

额外验证项

  • 确认FileProvider的paths.xml配置正确,允许访问外部文件目录
  • 首次拍照后,直接检查createImageFile()生成的文件路径,确认是否有内容写入
  • 打印imageWasSaved的返回值,验证相机是否真的成功保存图片

内容的提问来源于stack exchange,提问作者tasjapr

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 21:55:14