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

Pixel Copy崩溃排查:窗口无后备表面问题求助

问题原因与修复方案

崩溃核心原因

Window doesn't have a backing surface! 异常本质是:调用PixelCopy.request时,目标窗口的底层Surface已被释放、未完成初始化,或窗口处于不可见/非活跃状态。

你用decorView.rootView.post仅能保证View树绘制完成,但无法确保窗口Surface状态稳定——比如:

  • Activity进入onDestroy流程,Surface已被销毁
  • 窗口被切换到后台(如按Home键),Surface临时释放
  • 屏幕旋转等配置变化过程中,旧窗口Surface失效、新窗口Surface未创建

具体修复方案

1. 增加窗口有效性检查+异常捕获

在调用PixelCopy前先校验窗口状态,并捕获Surface异常避免崩溃:

@RequiresApi(Build.VERSION_CODES.O)
object PixelCopyUtils {

    fun getViewBitmap(window: Window, event: (PixelCopyEvents) -> Unit) {
        val decorView = window.decorView
        // 先判断窗口是否处于活跃可见状态
        if (decorView.visibility != View.VISIBLE || !window.isActive) {
            event(PixelCopyEvents.OnCopyError)
            return
        }

        val rootView = decorView.rootView
        val bitmap = Bitmap.createBitmap(rootView.width, rootView.height, Bitmap.Config.ARGB_8888)
        val handlerThread = HandlerThread(PixelCopyUtils::class.java.simpleName)
        handlerThread.start()

        try {
            PixelCopy.request(window, bitmap, { copyResult ->
                if (copyResult == PixelCopy.SUCCESS) {
                    event(PixelCopyEvents.OnCopySuccess(bitmap))
                } else {
                    event(PixelCopyEvents.OnCopyError)
                }
                handlerThread.quitSafely()
            }, Handler(handlerThread.looper))
        } catch (e: IllegalArgumentException) {
            // 捕获Surface无效的异常,避免崩溃
            event(PixelCopyEvents.OnCopyError)
            handlerThread.quitSafely()
        } catch (e: IllegalStateException) {
            // 处理其他窗口状态异常
            event(PixelCopyEvents.OnCopyError)
            handlerThread.quitSafely()
        }
    }

    sealed interface PixelCopyEvents {
        object OnCopyError : PixelCopyEvents
        data class OnCopySuccess(val bitmap: Bitmap) : PixelCopyEvents
    }
}

2. 优化调用时机的前置校验

修改扩展函数,提前过滤无效窗口状态:

@RequiresApi(Build.VERSION_CODES.O)
fun Window.onViewDraw(isReady: (Bitmap?) -> Unit) {
    val decorView = this.decorView
    if (decorView.visibility != View.VISIBLE || !this.isActive) {
        logging(TAG, "Window is not active or visible, can't take screenshot.")
        isReady(null)
        return
    }

    decorView.rootView.post {
        PixelCopyUtils.getViewBitmap(this) { event ->
            when (event) {
                PixelCopyUtils.PixelCopyEvents.OnCopyError -> {
                    logging(TAG, "Error while getting the screenshot through PixelCopy API.")
                    isReady(null)
                }
                is PixelCopyUtils.PixelCopyEvents.OnCopySuccess -> {
                    isReady(event.bitmap)
                }
            }
        }
    }
}

3. 可选:增加偶发异常的重试机制

如果是Surface状态波动导致的偶发失败,可增加一次短延迟重试:

@RequiresApi(Build.VERSION_CODES.O)
object PixelCopyUtils {
    private const val MAX_RETRY = 1
    private var retryCount = 0

    fun getViewBitmap(window: Window, event: (PixelCopyEvents) -> Unit) {
        val decorView = window.decorView
        if (decorView.visibility != View.VISIBLE || !window.isActive) {
            resetRetryCount()
            event(PixelCopyEvents.OnCopyError)
            return
        }

        val rootView = decorView.rootView
        val bitmap = Bitmap.createBitmap(rootView.width, rootView.height, Bitmap.Config.ARGB_8888)
        val handlerThread = HandlerThread(PixelCopyUtils::class.java.simpleName)
        handlerThread.start()

        try {
            PixelCopy.request(window, bitmap, { copyResult ->
                if (copyResult == PixelCopy.SUCCESS) {
                    resetRetryCount()
                    event(PixelCopyEvents.OnCopySuccess(bitmap))
                } else {
                    handleRetry(window, event, handlerThread)
                }
                handlerThread.quitSafely()
            }, Handler(handlerThread.looper))
        } catch (e: IllegalArgumentException) {
            handleRetry(window, event, handlerThread)
        }
    }

    private fun handleRetry(window: Window, event: (PixelCopyEvents) -> Unit, handlerThread: HandlerThread) {
        if (retryCount < MAX_RETRY) {
            retryCount++
            Handler(handlerThread.looper).postDelayed({
                getViewBitmap(window, event)
            }, 100)
        } else {
            resetRetryCount()
            event(PixelCopyEvents.OnCopyError)
            handlerThread.quitSafely()
        }
    }

    private fun resetRetryCount() {
        retryCount = 0
    }

    sealed interface PixelCopyEvents {
        object OnCopyError : PixelCopyEvents
        data class OnCopySuccess(val bitmap: Bitmap) : PixelCopyEvents
    }
}

额外注意事项

  • 避免在onPause、onDestroy或配置变化回调中触发截图
  • 使用完截图Bitmap后,记得调用bitmap.recycle()回收内存
  • 多窗口模式下需额外校验窗口的显示层级

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 11:13:15