Jay*_*yce 1 android android-mediaprojection
我在媒体投影和截图方面遇到错误,但仅限于 android 13,有时它们会显示为黑色,但并非总是如此。我尝试过延迟(最多 5 秒)来看看 Android 系统是否是造成这种情况的原因,但它仍然发生,感谢任何帮助。我确实搜索了该网站,但没有发现有关 android 13 问题的信息。
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
windowManager.getDefaultDisplay().getRealSize(size);
final int width = size.x, height = size.y;
final ImageReader imageReader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 1);
imageReader.setOnImageAvailableListener(reader -> {
//-> Stop our media projection just in case it is running
mediaProjection.stop();
Image image = reader.acquireLatestImage();
if (image != null){
Image.Plane[] planes = image.getPlanes();
ByteBuffer buffer = planes[0].getBuffer();
int pixelStride = planes[0].getPixelStride(), rowStride = planes[0].getRowStride(), rowPadding = rowStride - pixelStride * width;
bitmap = Bitmap.createBitmap(width + rowPadding / pixelStride, height, Bitmap.Config.ARGB_8888);
bitmap.copyPixelsFromBuffer(buffer);
String fileName = "Screenshot_" + System.currentTimeMillis() + ".jpg";
String destinationPath = this.getExternalFilesDir(null) + "/screenshots/" + fileName;
File imageFile = new File(destinationPath);
FileOutputStream outputStream = new FileOutputStream(imageFile);
int quality = 100;
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, outputStream);
outputStream.flush();
outputStream.close();
String mediaPath = Environment.DIRECTORY_PICTURES + File.separator + "Screenshots/myapp" + File.separator;
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
values.put(MediaStore.Images.Media.IS_PENDING, 0);
values.put(MediaStore.Images.Media.RELATIVE_PATH, mediaPath);
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
values.put(MediaStore.Images.Media.SIZE, imageFile.length());
values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);
Uri path = this.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
OutputStream imageOutStream = this.getContentResolver().openOutputStream(path);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, imageOutStream);
if (imageOutStream != null) {
imageOutStream.flush();
imageOutStream.close();
}
if (image != null) { image.close(); }
mediaProjection.stop();
if (reader != null){ reader.close(); }
}
}, null);
Run Code Online (Sandbox Code Playgroud)
根据我的测试和观察,在 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()))
Run Code Online (Sandbox Code Playgroud)
isEmptyBitmap() 只是 Bitmap 的扩展:
fun Bitmap.isEmptyBitmap(): Boolean {
val emptyBitmap = Bitmap.createBitmap(width, height, config)
return this.sameAs(emptyBitmap)
}
Run Code Online (Sandbox Code Playgroud)
第二个问题是,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()
}
}
Run Code Online (Sandbox Code Playgroud)
实施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) }
}
Run Code Online (Sandbox Code Playgroud)