Android MediaProjection学习(一)之和ImageReader实现屏幕截图

MediaProjection是什么?

按照惯例,附上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()去请求系统的屏幕信息权限,但是不会录制声音。

MediaProjection的重要方法

官方文档里提供了四种方法,但是最重要的就是下面这个方法

返回值 说明
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方法,上文中已经对这个方法进行了详细的说明,前面几个不再详细说明,我们看看几个关键参数。

1、DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR

这个参数官方的说明是:Virtual display flag: Allows content to be mirrored on private displays when no content is being shown.大概意思就是屏幕开始展示的时候就会把内容传出来。

2、imageReader.getSurface()

这个参数实际上是通过ImageReader的getSurface()方法来获取一个surface对象。然后把截屏内容传给这个imageReader的实例来处理。

聊聊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,这里面主要说明的有几个方法。

1、Image image = imageReader.acquireLatestImage();

acquireLatestImage()
Acquire the latest Image from the ImageReader’s queue, dropping older images.

获得最新的Image从ImageReader的队列里,丢掉旧的images。

这里斜体字可以不看
之前我通过这个方法判断获取的 Image是否为空,判断完以后再去获取发现还是空,最后看了官方文档才知道会丢掉这张Image所以你判断完的时候就再也获取不到了,所以就要先获取再判断

回归正题,下面就看看另外一句关键的代码

2、后面的Image转换为bitmap的代码,我还不是太懂。求告知。

Final

通过以上的操作就可以实现屏幕截图了,那段转换代码我决定去求助一下别人。主要过程都在博文里说了,大家可以自己多看看,不会也可以问我。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章