android5.0区域截图

需求总是推动着我进步/(ㄒoㄒ)/~~,最近有了个新的需求:实现一个对桌面局部区域截屏的效果。还记得以前在4.4的版本上实现截屏分享,使用的是View的cacheDrawable,缺点是截不到状态栏、Activity的上层Dialog等,也无法在后台服务中截屏。一直听说5.0之后Google开放了截屏录屏的API,刚好手上有5.1版本的源码,走一波~

相关类

MediaProjection:可以用来捕获屏幕内容或系统声音,可以通过MediaProjectionManager的createScreenCaptureIntent方法创建的Intent来启动。
MediaProjectionManager:MediaProjection的管理类。通过Context.getSystemService()方法获取实例,参数传Context.MEDIA_PROJECTION_SERVICE。
VirtualDisplay:VirtualDisplay将屏幕内容渲染在一个Surface上,当进程终止时会被自动的释放。当不再使用他时,你也应该调用release() 方法来释放资源。通过调用DisplayManager 类的 createVirtualDisplay()方法来创建。

基本流程

  • 请求用户授予捕获屏幕内容的权限
  • 获取MediaProjection实例
  • 获取VirtualDisplay实例
  • 通过ImageReader获取截图
  • 对图片进行区域裁剪

请求权限

manager = (MediaProjectionManager) application.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
startActivityForResult(manager.createScreenCaptureIntent(), 1);

首先获取MediaProjectionManager对象,然后通过createScreenCaptureIntent方法获取请求权限的Intent,并通过startActivityForResult启动。

我们简单看一下createScreenCaptureIntent方法:

/**
     * Returns an Intent that must passed to startActivityForResult()
     * in order to start screen capture. The activity will prompt
     * the user whether to allow screen capture.  The result of this
     * activity should be passed to getMediaProjection.
     */
    public Intent createScreenCaptureIntent() {
        Intent i = new Intent();
        i.setClassName("com.android.systemui",
                "com.android.systemui.media.MediaProjectionPermissionActivity");
        return i;
    }

其实就是启动MediaProjectionPermissionActivity,该Activity会询问用户是否要开启屏幕捕获,成功开启后可以通过getMediaProjection获取MediaProjection实例。

获取MediaProjection实例

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (requestCode == 1 && resultCode == Activity.RESULT_OK) {
            Log.d(TAG, "onActivityResult: permission request success..");
            mediaProjection = manager.getMediaProjection(resultCode, data);
            getVirtualDisplay(activity);
        } else {
            Log.d(TAG, "onActivityResult: permission request defined..");
        }
    }

请求授权成功后我们可以通过MediaProjectionManager的getMediaProjection方法来获取MediaProjection实例。
再看一下getMediaProjection方法:

/**
     * Retrieve the MediaProjection obtained from a succesful screen
     * capture request. Will be null if the result from the
     * startActivityForResult() is anything other than RESULT_OK.
     *
     * @param resultCode The result code from {@link android.app.Activity#onActivityResult(int,
     * int, android.content.Intent)}
     * @param resultData The resulting data from {@link android.app.Activity#onActivityResult(int,
     * int, android.content.Intent)}
     */
    public MediaProjection getMediaProjection(int resultCode, @NonNull Intent resultData) {
        if (resultCode != Activity.RESULT_OK || resultData == null) {
            return null;
        }
        IBinder projection = resultData.getIBinderExtra(EXTRA_MEDIA_PROJECTION);
        if (projection == null) {
            return null;
        }
        return new MediaProjection(mContext, IMediaProjection.Stub.asInterface(projection));
    }

两个参数分别对应onActivityResult返回的参数,resultCode如果不是-1(RESULT_OK),该方法将返回null。

获取VirtualDisplay实例

private static void getVirtualDisplay(Activity activity) {
        int width;
        int height;
        if (ScreenUtil.isLandspace(activity)) {
            width = Capture.screenW + Capture.nativeH;
            height = Capture.screenH;
        } else {
            width = Capture.screenW;
            height = Capture.screenH + Capture.nativeH;
        }
        virtualDisplay = mediaProjection.createVirtualDisplay("demo", width,
                height, Capture.screenDensity,
                DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, imageReader.getSurface(),
                null, null);
}

通过MediaProjection的createVirtualDisplay方法来获取VirtualDisplay实例,该方法内部调用的是DisplayManager 类的 createVirtualDisplay()方法,有以下几个参数(按顺序):

  • name:virtual display 的名字,不能为null。
  • width:virtual display的宽(像素),必须大于0。
  • height:virtual display的高(像素),必须大于0。
  • dpi:virtual display的密度,必须大于0。
  • flags:DisplayManager类中的几个常量,标识virtual display类型。
  • surface:virtual display内容需要被提供到的那个surface,截图的话我们这里传ImageReader的surface。
  • callback:virtual display状态发生改变时的回调。有暂停、恢复、停止三个回调。
  • handler:回调时所使用的handler,如果是在主线程回调,可以传null。

需要注意的一点是,如果手机有虚拟键,创建virtualDiaplay实例的时候宽高尺寸需要考虑虚拟导航栏的高度。否则得到的图片会变形。

读取图片

    imageReader = ImageReader.newInstance(screenW, screenH, PixelFormat.RGBA_8888, 1);

    public static Bitmap screenShot() {
        Image image = imageReader.acquireNextImage();
        if (image != null) {
            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);
            image.close();
            return bitmap;
        } 
        return null;
    }

注意有个问题就是在获取VirtualDisplay实例后直接获取图片会报错,需要等半秒左右(初始化?),或者为ImageReader设置一个setOnImageAvailableListener监听。

        imageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
            @Override
            public void onImageAvailable(ImageReader reader) {
                Log.d(TAG, "onImageAvailable");
                Bitmap bitmap = screenShot();
                stopVirtual();
            }
        }, null);

区域裁剪

Bitmap cutBitmap = Bitmap.createBitmap(bitmap, mRect.left, mRect.top,  mRect.width(),  mRect.height());

Demo传送门

https://github.com/lunxinfeng/MediaProjection

你可能感兴趣的:(android5.0区域截图)