按照惯例,附上Google官方文档链接: 官方文档
A token granting applications the ability to capture screen contents and/or record system audio. The exact capabilities granted depend on the type of MediaProjection.
A screen capture session can be started through MediaProjectionManager.createScreenCaptureIntent(). This grants the ability to capture screen contents, but not system audio
根据文档介绍。MediaProjection是手环用户获取屏幕内容或者记录系统的界面视频。你需要什么权限取决于MediaProjection的类型。通过MediaProjectionManager.createScreenCaptureIntent()去请求系统的屏幕信息权限,但是不会录制声音。
官方文档里提供了四种方法,但是最重要的就是下面这个方法
返回值 | 说明 |
---|---|
VirtualDisplay | createVirtualDisplay(String name, int width, int height, int dpi, int flags, Surface surface, VirtualDisplay.Callback callback, Handler handler)Creates a VirtualDisplay to capture the contents of the screen. |
参数 | 说明 |
name | String: The name of the virtual display, must be non-empty.This value must never be null. |
- | 这个值不能为空,用途还没有搞明白 |
width | int: The width of the virtual display in pixels. Must be greater than 0. |
- | 用px表示的确切的值,必须大于0,我们截图的话传进去屏幕宽度就好 |
height | int: The height of the virtual display in pixels. Must be greater than 0. |
- | 同上,传入屏幕高度 |
dpi | int: The density of the virtual display in dpi. Must be greater than 0. |
- | 传入屏幕的dpi值 |
flags | int: A combination of virtual display flags. See DisplayManager for the full list of flags. |
- | virtual displays的标识组合 |
surface | Surface: The surface to which the content of the virtual display should be rendered, or null if there is none initially. |
- | 这个是特别重要的一个参数,是我们的屏幕绘制的内容,会放在这个参数中回调 |
callback | VirtualDisplay.Callback: Callback to call when the virtual display’s state changes, or null if none. |
- | VirtualDisplay.Callback的实例对象作为参数,当展示的状态发生变化时回调 |
handler | Handler: The Handler on which the callback should be invoked, or null if the callback should be invoked on the calling thread’s main Looper. |
- | callback回调时要做的操作放在Handler里 |
说再多的概念也没有实际写一个Demo来的实在,那就写一个。
开始我们已经了解到 要通过MediaProjectionManager.createScreenCaptureIntent()方法获取一个intent来获取权限。
Intent intent = mMediaProjectionManager.createScreenCaptureIntent();
startActivityForResult(intent, RESULT_CODE);
其中mMediaProjectionManager是MediaProjectionManager的一个实例,RESULT_CODE是一个int的变量,我设置的是1。经过以上操作之后运行app就会有一个弹窗如图所示:
点击开始就可以获取获取屏幕内容的权限了。我们使用了startActivityForResult方法来启动这个intent,就是为了获取返回值。
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case RESULT_CODE:
if (resultCode == Activity.RESULT_OK) {
mMediaProjection = mMediaProjectionManager.getMediaProjection(resultCode, data);
mMediaProjection.createVirtualDisplay("shot_image", windowManager.getDefaultDisplay().getWidth(),
windowManager.getDefaultDisplay().getHeight(),
displayMetrics.densityDpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, imageReader
.getSurface(), null, null);
}
break;
}
}
这里在onActivityResult方法里用MediaProjectionManager类的getMediaProjection方法传入回传的数据实例化了MediaProjection的对象。
然后重头戏来了,我们调用了createVirtualDisplay方法,上文中已经对这个方法进行了详细的说明,前面几个不再详细说明,我们看看几个关键参数。
这个参数官方的说明是:Virtual display flag: Allows content to be mirrored on private displays when no content is being shown.大概意思就是屏幕开始展示的时候就会把内容传出来。
这个参数实际上是通过ImageReader的getSurface()方法来获取一个surface对象。然后把截屏内容传给这个imageReader的实例来处理。
怎么画风一转开始聊ImageReader呢?其实到上一步,MediaProjection的任务已经完成了。接下来要把数据解析成bitmap然后再使用就好,可以保存到本地或者加载到ImageView控件上,这里我们直接加载到Imageview的控件上。
要转化就一步一步来,这个surface是什么?
A Surface is generally created by or from a consumer of image buffers (such as a SurfaceTexture, MediaRecorder, or Allocation), and is handed to some kind of producer (such as OpenGL, MediaPlayer, or CameraDevice) to draw into.
官方文档里说他就是一个Image的缓冲区,交给一些可以绘制Image的工具去绘制后存在这个缓冲区里。surface我们了解这么多就够了。
接下来看看ImageReader
The ImageReader class allows direct application access to image data rendered into a Surface
这是官方的解释,我们结合代码来看看ImageReader:
private void startCapture() {
mImageName = System.currentTimeMillis() + ".png";
Image image = imageReader.acquireLatestImage();
if (image == null)
return;
int width = image.getWidth();
int height = image.getHeight();
final Image.Plane[] planes = image.getPlanes();
final ByteBuffer buffer = planes[0].getBuffer();
int pixelStride = planes[0].getPixelStride();
int rowStride = planes[0].getRowStride();
int rowPadding = rowStride - pixelStride * width;
Bitmap bitmap = Bitmap.createBitmap(width + rowPadding / pixelStride, height, Bitmap.Config.ARGB_8888);
bitmap.copyPixelsFromBuffer(buffer);
bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height);
if (bitmap != null)
iv_screen.setImageBitmap(bitmap);
image.close();
}
经过上面几行代码之后我们就把拿到的数据转换成了bitmap,这里面主要说明的有几个方法。
acquireLatestImage()
Acquire the latest Image from the ImageReader’s queue, dropping older images.
获得最新的Image从ImageReader的队列里,丢掉旧的images。
这里斜体字可以不看
之前我通过这个方法判断获取的 Image是否为空,判断完以后再去获取发现还是空,最后看了官方文档才知道会丢掉这张Image所以你判断完的时候就再也获取不到了,所以就要先获取再判断
回归正题,下面就看看另外一句关键的代码
通过以上的操作就可以实现屏幕截图了,那段转换代码我决定去求助一下别人。主要过程都在博文里说了,大家可以自己多看看,不会也可以问我。