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 <b>must</b> 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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容

  • 本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 这篇文章,会带你学习如何使用MediaProj...
    陈添阅读 24,786评论 19 79
  • 本篇文章是基于谷歌有关Graphic的一篇概览文章的翻译:http://source.android.com/de...
    lee_3do阅读 7,109评论 2 21
  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    阳明先生_X自主阅读 15,969评论 3 119
  • 很久以前有过听匠人精神这个词,是以工匠的心态去做事情,当时以为工匠精神就是简单的做事情,专业,敬业,严谨,追求完美...
    侯兵阅读 404评论 0 0
  • 第一章 去爬山 “原来你是我最想留住的幸运,原来我们和爱情曾经靠的那么近,那为我对抗世界的决定,那陪我淋的雨,一...
    薄荷糖的夏天1213阅读 226评论 1 0