我有下面的代码,它在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)
}这个特定的功能有什么变化吗?或者,我的代码中有什么问题,而且它一直在神奇地处理以前的版本?
发布于 2022-11-24 02:16:05
在我的测试和观察中,当在Android13上使用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有时甚至不回调!在本例中,我设置了一个等待结果的超时,当超时时,从同一个VirtualDisplay实例重新创建MediaProjection对象,它就可以工作了。
我使用的是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) }
}https://stackoverflow.com/questions/74105274
复制相似问题