kotlin 编程截图Android 13

oknwwptz  于 2022-11-25  发布在  Kotlin
关注(0)|答案(1)|浏览(327)

我有下面的代码,它在Android 12及以下版本上运行得非常好,但出于一个奇怪的原因,Android 13在95%的情况下都会截图为“黑色”。

fun setVirtualDisplay() {
    mImageReader = ImageReader.newInstance(
        deviceScreenUtils.getWidth(),
        deviceScreenUtils.getHeight(),
        PixelFormat.RGBA_8888,
        2
    )

    mImageReader?.let {
        val flags =
            DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY or DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC

        try {
            mVirtualDisplay = mMediaProjection?.createVirtualDisplay(
                "screen-mirror", deviceScreenUtils.getWidth(), deviceScreenUtils.getHeight(),
                deviceScreenUtils.getDensity(), flags, it.surface, null, null
            )
        } catch (e: Throwable) {
            Log.i(TAG, "Media Projection not longer available...")
            mMediaProjectionIntent = null
            mImageReader = null
        }
    }
}

fun takeScreenshot() {
    Log.i(TAG, "Taking screenshot...")
    val handler = Handler(Looper.getMainLooper())
    mImageReader?.setOnImageAvailableListener({ imageReader ->
        val image = imageReader.acquireLatestImage()
        Log.i(TAG, "Acquiring image...")
        if (image != null) {
            val planes = image.planes
            val buffer = planes[0].buffer
            val pixelStride = planes[0].pixelStride
            val rowStride = planes[0].rowStride
            val rowPadding: Int = rowStride - pixelStride * deviceScreenUtils.getWidthPixels()
            val bitmap = Bitmap.createBitmap(
                deviceScreenUtils.getWidth() + (rowPadding.toFloat() / pixelStride.toFloat()).toInt(),
                deviceScreenUtils.getHeight(),
                Bitmap.Config.ARGB_8888
            )
            bitmap.copyPixelsFromBuffer(buffer)
            image.close()

            fileUtil.saveImage(bitmap)?.let {
                lastScreenshot.onNext(it)
            }
        }

        imageReader.setOnImageAvailableListener(null, handler)
        releaseBuffer()
    }, handler)
}

这个特定的功能有什么改变吗?或者我的代码有什么错误,它在以前的版本中神奇地工作着?

kmpatx3s

kmpatx3s1#

在我的测试和观察中,在Android 13上使用ImageReader和媒体投影时存在两个问题
首先,setOnImageAvailableListener的回调结果有时会返回空像素的缓冲区。

imageReader.setOnImageAvailableListener({ imageReader ->
    val image = imageReader.acquireLatestImage()
    // ... get buffer here
    val bitmap = Bitmap.createBitmap(screenWidth + rowPadding / pixelStride, screenHeight, Bitmap.Config.ARGB_8888)
    bitmap.copyPixelsFromBuffer(buffer)

    // IMPORTANT!
    val isEmptyBitmap = bitmap.isEmptyBitmap()
    if (isEmptyBitmap) {
        // don't stop the listener and let the imageReader continue to run so that we can get next round of image buffer
    } else {
        // save bitmap to file
    }

    }, Handler(Looper.getMainLooper()))

isEmptyBitmap()只是位图扩展:

fun Bitmap.isEmptyBitmap(): Boolean {
    val emptyBitmap = Bitmap.createBitmap(width, height, config)
    return this.sameAs(emptyBitmap)
}

第二个问题是,OnImageAvailableListener有时甚至不回调!在本例中,我设置了一个超时来等待结果,当超时时,从同一个MediaProjection示例重新创建VirtualDisplay对象,它就可以正常工作了。
我使用的是Kotlin协程,因此代码片段可能看起来像:

retry(10) {
    val imageVirtualDisplay = createVirtualDisplay(...)
    try {
        withTimeout(100) {
            // awaitImageAvailable will suspend here to get the non-empty bitmap
            val bitmap = imageReader.awaitImageAvailable(screenWidth, screenHeight)
            // save bitmap to file
        }
    } finally {
        Timber.d("screenshot imageVirtualDisplay release")
        imageVirtualDisplay?.release()
    }
}

awaitImageAvailable

suspend fun ImageReader.awaitImageAvailable(screenWidth: Int, screenHeight: Int): Bitmap =
suspendCancellableCoroutine { cont ->
    setOnImageAvailableListener({ imageReader ->

        val image = imageReader.acquireLatestImage() ?: throw Error("get screen image result failed")

        val planes = image.planes
        val buffer = planes[0].buffer

        val pixelStride = planes[0].pixelStride
        val rowStride = planes[0].rowStride
        val rowPadding: Int = rowStride - pixelStride * screenWidth
        var bitmap =
            Bitmap.createBitmap(screenWidth + rowPadding / pixelStride, screenHeight, Bitmap.Config.ARGB_8888)
        bitmap.copyPixelsFromBuffer(buffer)
        bitmap = Bitmap.createBitmap(bitmap, 0, 0, screenWidth, screenHeight)
        image.close()

        if (!bitmap.isEmptyBitmap()) {
            Timber.d("image not empty")
            setOnImageAvailableListener(null, null)
            cont.resume(bitmap)
        } else {
            Timber.w("image empty")
        }
    }, Handler(Looper.getMainLooper()))

    cont.invokeOnCancellation { setOnImageAvailableListener(null, null) }
}

相关问题