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

Android中CaptureVideo()回调始终返回false问题求助

解决方案:通过ContentObserver监听视频写入完成

针对Android 10及以上版本中ActivityResultContracts.CaptureVideo()回调提前触发(视频已保存但返回false)的问题,推荐通过注册ContentObserver监听目标Uri的内容变化来准确捕获视频写入完成的时机,替代依赖回调返回的result值。

具体实现步骤

  1. 定义ContentObserver子类
    重写onChange方法,通过多次检查文件大小是否稳定来判定视频是否写入完成,确认后执行后续逻辑并注销观察者:
private class VideoCaptureObserver(
    private val contentResolver: ContentResolver,
    private val targetUri: Uri,
    private val onVideoSaved: () -> Unit
) : ContentObserver(Handler(Looper.getMainLooper())) {
    private var lastFileSize = 0L
    private var stableCount = 0
    // 最大检查次数,避免无限等待
    private val maxCheckCount = 5

    override fun onChange(selfChange: Boolean, uri: Uri?) {
        if (uri != targetUri) return

        val currentSize = getFileSize()
        when {
            // 连续两次文件大小一致,且达到检查阈值,判定写入完成
            currentSize == lastFileSize && stableCount >= 2 -> {
                contentResolver.unregisterContentObserver(this)
                onVideoSaved.invoke()
            }
            // 文件大小变化,重置计数
            currentSize != lastFileSize -> {
                lastFileSize = currentSize
                stableCount = 0
                postDelayedCheck()
            }
            // 文件大小稳定但未达阈值,继续检查
            stableCount < maxCheckCount -> {
                stableCount++
                postDelayedCheck()
            }
            // 超时处理
            else -> {
                contentResolver.unregisterContentObserver(this)
                // 可添加超时错误逻辑
            }
        }
    }

    private fun getFileSize(): Long {
        return try {
            contentResolver.openFileDescriptor(targetUri, "r")?.use { it.statSize } ?: 0L
        } catch (e: Exception) {
            0L
        }
    }

    private fun postDelayedCheck() {
        Handler(Looper.getMainLooper()).postDelayed({
            onChange(false, targetUri)
        }, 500)
    }
}
  1. 注册观察者并启动拍摄
    忽略原回调的result值,在拍摄流程结束后启动观察者监听:
private val getVideo = registerForActivityResult(ActivityResultContracts.CaptureVideo()) {
    // 仅作为拍摄流程结束的信号,开始监听文件写入状态
    val observer = VideoCaptureObserver(contentResolver, targetUri) {
        // 视频写入完成后的业务逻辑
        Toast.makeText(this, "视频保存完成", Toast.LENGTH_SHORT).show()
    }
    contentResolver.registerContentObserver(targetUri, true, observer)
}

// 启动视频拍摄的方法
fun startVideoCapture() {
    targetUri = createVideoUri()
    getVideo.launch(targetUri)
}

// 生成存储视频的Uri(基于MediaStore)
private fun createVideoUri(): Uri {
    val values = ContentValues().apply {
        put(MediaStore.Video.Media.DISPLAY_NAME, "captured_${System.currentTimeMillis()}.mp4")
        put(MediaStore.Video.Media.MIME_TYPE, "video/mp4")
        put(MediaStore.Video.Media.RELATIVE_PATH, Environment.DIRECTORY_MOVIES)
    }
    return contentResolver.insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values)!!
}
  1. 额外注意事项
  • Android 10及以上无需申请WRITE_EXTERNAL_STORAGE权限;Android 13+若使用Toast等通知,需申请POST_NOTIFICATIONS权限。
  • 可根据需求调整检查间隔和最大检查次数,适配不同设备的视频写入速度。

替代方案:自定义ActivityResultContract

若偏好统一的合约调用方式,可自定义合约在后台线程等待文件写入完成(需配合协程避免阻塞主线程):

class CaptureVideoWithCompletion : ActivityResultContract<Uri, Boolean>() {
    override fun createIntent(context: Context, input: Uri): Intent {
        return Intent(MediaStore.ACTION_VIDEO_CAPTURE).putExtra(MediaStore.EXTRA_OUTPUT, input)
    }

    override suspend fun parseResult(resultCode: Int, intent: Intent?): Boolean {
        if (resultCode != Activity.RESULT_OK) return false
        val uri = intent?.getParcelableExtra<Uri>(MediaStore.EXTRA_OUTPUT) ?: return false

        var lastSize = 0L
        var stableCount = 0
        repeat(5) {
            val currentSize = getFileSize(uri, context.contentResolver)
            if (currentSize == lastSize) stableCount++ else stableCount = 0
            lastSize = currentSize
            delay(500)
            if (stableCount >=2) return@repeat
        }
        return stableCount >=2
    }

    private fun getFileSize(uri: Uri, resolver: ContentResolver): Long {
        return try {
            resolver.openFileDescriptor(uri, "r")?.use { it.statSize } ?:0L
        } catch (e: Exception) {0L}
    }
}

使用时替换原合约即可:

private val getVideo = registerForActivityResult(CaptureVideoWithCompletion()) { success ->
    if (success) {
        // 视频已写入完成
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 05:43:08