[Android] MediaProjection

Android 5.0 Support MediaProjection For Screenshot

Posted by xiuyuantech on 2020-05-04

Android 5.0以上提供了MediaProjection,方便截屏录屏等功能。在工作过程中一般系统提供的API即可以满足我们的需求,但是特殊的情况下无法满足本文将通过MediaProjection实现截屏功能以及解决遇到的问题
版权声明: 本站所有博文内容均为原创,如若出现不严谨的地方欢迎指出。转载请务必注明作者与原文链接,且不得篡改原文内容

General Screenshot

1
2
3
4
5
6
7
8
9
10
11
12

public static Bitmap snapShotWithStatusBar(Activity activity) {
View view = activity.getWindow().getDecorView();
view.setDrawingCacheEnabled(true);
view.buildDrawingCache();
Bitmap bmp = view.getDrawingCache();
int width = getScreenWidth(activity);
int height = getScreenHeight(activity);
Bitmap bp = Bitmap.createBitmap(bmp, 0, 0, width, height);
view.destroyDrawingCache();
return bp;
}

像这种基本上就可以满足日常截屏功能,同时也不需要特别的权限。不过当布局中包含WebView 或者 其他特殊的自定义View通过硬件加速处理的情况下可能会出现如下问题:

正常截图:

问题截图:

通过上面的截图我们可以看出曲线图部分并没有被截下来。经过查阅资料以及官方文档,从Android 5.0 开始官方提供了系统级的 MediaProjection API。

MediaProjection 不仅可以进行系统级的截屏还支持屏幕录制,本文暂时只讲解截屏相关问题。

MediaProjection Screenshot

其中,捕捉屏幕是需要用户确认权限才可以,这权限对话框是由createScreenCaptureIntent方法创建的,在用户点击允许之后,在onActivityResult得到确认码,才可以拿到MediaProjection对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
projectionManager = (MediaProjectionManager)getSystemService(Context.MEDIA_PROJECTION_SERVICE);

startActivityForResult(projectionManager.createScreenCaptureIntent(), REQUEST_CODE);
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (RESULT_OK == resultCode && REQUEST_CODE == requestCode)
{
mediaProjection = projectionManager.getMediaProjection(resultCode, data);
......
}
}

mediaProjection.stop();

官方及网上代码类似如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47


imageReader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 2);
virtualDisplay = mediaProjection.createVirtualDisplay( "ScreenShot", width, height, mDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY , imageReader.getSurface(), null, handler);
imageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
Image image = null;
FileOutputStream fos = null;
Bitmap bitmap = null;
try {
image = reader.acquireLatestImage();
if (image != null) {
Image.Plane[] planes = image.getPlanes();
ByteBuffer buffer = planes[0].getBuffer();
int pixelStride = planes[0].getPixelStride();
int rowStride = planes[0].getRowStride();
int rowPadding = rowStride - pixelStride * width;
bitmap = Bitmap.createBitmap(width + rowPadding / pixelStride, height, Bitmap.Config.ARGB_8888);
bitmap.copyPixelsFromBuffer(buffer);
Date currentDate = new Date();
SimpleDateFormat date = new SimpleDateFormat("yyyyMMddhhmmss");
String fileName = DOWNLOAD_DIR + "/screenshot_" + date.format(currentDate) + ".png";
fos = new FileOutputStream(fileName);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
mediaProjection.stop();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bitmap != null) {
bitmap.recycle();
}
if (image != null) {
image.close();
}
}
}
}, handler);
}

Warn

关键代码: windowManager.defaultDisplay.getRealMetrics(metrics)

上面的代码可以实现截屏功能,但是生成的图片会有黑边的问题。注意,用Display获取屏幕尺寸要用真实的尺寸,使用getRealMetrics方法。如果使用getMetrics方法,得到的高度是缺少虚拟导航栏 Navigaiton Bar的高度。得到的尺寸和屏幕不一致,最终得到的图像会是等比例缩放到屏幕大小的图像,然后空白的地方会显示黑边。