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

Android CameraX PreviewView截图黑屏,求完整Activity截图方案

解决CameraX PreviewView截图黑屏问题

问题根源

CameraX的PreviewView通过SurfaceTexture实现硬件加速的相机预览渲染,而View.draw(Canvas)仅能捕获View树中软件绘制的内容,无法读取硬件层渲染的相机画面,因此截图时预览区域会显示黑屏。


解决方案一:结合ImageCapture与界面截图合成

通过CameraX的ImageCapture捕获当前相机帧,再截取Activity界面,最后将两者合成为完整截图,适合静态场景。

1. 配置CameraX添加ImageCapture用例

private lateinit var imageCapture: ImageCapture

private fun startCamera() {
    val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
    cameraProviderFuture.addListener({
        val cameraProvider = cameraProviderFuture.get()
        val preview = Preview.Builder()
            .build()
            .also { it.setSurfaceProvider(binding.previewView.surfaceProvider) }

        // 初始化ImageCapture
        imageCapture = ImageCapture.Builder()
            .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
            .build()

        val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
        try {
            cameraProvider.unbindAll()
            cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture)
        } catch(exc: Exception) {
            Log.e("CameraX", "绑定用例失败", exc)
        }
    }, ContextCompat.getMainExecutor(this))
}

2. 实现截图合成逻辑

private fun captureFullScreenWithPreview() {
    // 1. 捕获相机帧
    val photoFile = File(externalMediaDirs.first(), "${System.currentTimeMillis()}.jpg")
    val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()
    
    imageCapture.takePicture(
        outputOptions,
        ContextCompat.getMainExecutor(this),
        object : ImageCapture.OnImageSavedCallback {
            override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
                // 2. 截取Activity界面(不含预览内容)
                val activityBitmap = captureActivityScreenshot()
                // 3. 读取相机帧Bitmap
                val cameraBitmap = BitmapFactory.decodeFile(photoFile.absolutePath)
                // 4. 合成完整截图
                val combinedBitmap = combineBitmaps(activityBitmap, cameraBitmap, binding.previewView)
                // 保存合成后的图片
                saveBitmap(combinedBitmap)
            }

            override fun onError(exception: ImageCaptureException) {
                Log.e("Capture", "捕获相机帧失败", exception)
            }
        }
    )
}

// 截取Activity界面
private fun captureActivityScreenshot(): Bitmap {
    val rootView = window.decorView.rootView
    rootView.isDrawingCacheEnabled = true
    val bitmap = Bitmap.createBitmap(rootView.drawingCache)
    rootView.isDrawingCacheEnabled = false
    return bitmap
}

// 合成相机帧与界面截图
private fun combineBitmaps(activityBitmap: Bitmap, cameraBitmap: Bitmap, previewView: PreviewView): Bitmap {
    val combinedBitmap = Bitmap.createBitmap(activityBitmap.width, activityBitmap.height, activityBitmap.config)
    val canvas = Canvas(combinedBitmap)
    
    // 先绘制界面截图
    canvas.drawBitmap(activityBitmap, 0f, 0f, null)
    
    // 计算PreviewView在屏幕上的位置与尺寸
    val location = IntArray(2)
    previewView.getLocationOnScreen(location)
    val left = location[0]
    val top = location[1]
    val width = previewView.width
    val height = previewView.height
    
    // 将相机帧缩放到预览区域尺寸后绘制
    val scaledCameraBitmap = Bitmap.createScaledBitmap(cameraBitmap, width, height, true)
    canvas.drawBitmap(scaledCameraBitmap, left.toFloat(), top.toFloat(), null)
    scaledCameraBitmap.recycle()
    
    return combinedBitmap
}

// 保存Bitmap到本地
private fun saveBitmap(bitmap: Bitmap) {
    val saveFile = File(externalMediaDirs.first(), "full_screenshot_${System.currentTimeMillis()}.jpg")
    val outputStream = FileOutputStream(saveFile)
    bitmap.compress(Bitmap.CompressFormat.JPEG, 90, outputStream)
    outputStream.flush()
    outputStream.close()
    bitmap.recycle()
}

解决方案二:使用MediaProjection API

通过系统MediaProjection捕获整个屏幕内容(包含硬件层渲染的相机预览),适合需要实时或精准全屏截图的场景,但需要用户授权屏幕捕获权限。

1. 请求屏幕捕获权限

private val REQUEST_CODE_SCREEN_CAPTURE = 1001

private fun startScreenCapture() {
    val mediaProjectionManager = getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
    val intent = mediaProjectionManager.createScreenCaptureIntent()
    startActivityForResult(intent, REQUEST_CODE_SCREEN_CAPTURE)
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (requestCode == REQUEST_CODE_SCREEN_CAPTURE && resultCode == RESULT_OK) {
        val mediaProjection = (getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager)
            .getMediaProjection(resultCode, data!!)
        captureScreenWithMediaProjection(mediaProjection)
    }
}

2. 捕获完整屏幕内容

private fun captureScreenWithMediaProjection(mediaProjection: MediaProjection) {
    val displayMetrics = resources.displayMetrics
    val width = displayMetrics.widthPixels
    val height = displayMetrics.heightPixels
    
    val imageReader = ImageReader.newInstance(width, height, ImageFormat.JPEG, 1)
    val virtualDisplay = mediaProjection.createVirtualDisplay(
        "ScreenCapture",
        width, height, displayMetrics.densityDpi,
        DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
        imageReader.surface, null, null
    )
    
    imageReader.setOnImageAvailableListener({ reader ->
        val image = reader.acquireLatestImage()
        val buffer = image.planes[0].buffer
        val bytes = ByteArray(buffer.remaining())
        buffer.get(bytes)
        val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
        
        // 保存或使用截图
        saveBitmap(bitmap)
        
        // 释放资源
        image.close()
        reader.close()
        virtualDisplay.release()
        mediaProjection.stop()
    }, ContextCompat.getMainExecutor(this))
}

注意事项

  • 方案一需处理相机帧与界面截图的同步问题,可能存在微小延迟,适合静态场景。
  • 方案二需要用户授权屏幕捕获权限,Android 13+需额外申请READ_MEDIA_IMAGES权限用于保存图片。
  • 所有Bitmap操作完成后需调用recycle()回收内存,避免内存泄漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 09:55:20