Android 截图和录屏

一、Android 截图

Android 截图在这里分为三类:

  • 截取除状态栏的屏幕
  • 截取某个控件或区域
  • 使用 MediaProjection 截图

1.截取除状态栏的屏幕

该方式是使用 View 的 Cache 机制生成 View 的图像缓存保存为 Bitmap。
主要的 API 如下:

  • void setDrawingCacheEnabled(boolean flag) 开启或关闭 View 的 Cache,设置为 false 后,系统也会自动把原来的 Cache 销毁。
  • void buildDrawingCache() 创建 Cache,可不调用
  • Bitmap getDrawingCache() 获取 View 的 Cache 图片
  • void destroyDrawingCache()销毁 Cache 。若想更新 Cache,必须要调用该方法把旧的 Cache 销毁,才能建立新的。

示例代码

        View dView = getWindow().getDecorView();
        dView.setDrawingCacheEnabled(true);
        dView.buildDrawingCache();
        Bitmap bitmap = Bitmap.createBitmap(dView.getDrawingCache());
        if(bitmap != null){
            imageView.setImageBitmap(bitmap);
        }
        dView.setDrawingCacheEnabled(false);

2.截取某个控件或区域

该方式的原理和上面一样,都是利用 View 的 Cache 机制,不同点在于,这里的 View 不是 DecorView
示例代码:

        View view = ivSrc;
        view.setDrawingCacheEnabled(true);
        view.buildDrawingCache();
        Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache());
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
        }
        view.setDrawingCacheEnabled(false);

还有一种方式是将 View 绘制到 Canvas

        View view = ivSrc;
        //根据 View 的宽高创建 Bitmap 对象
        Bitmap bitmap = Bitmap.createBitmap(view.getWidth(),view.getHeight(), Bitmap.Config.ARGB_8888);
        //将以上创建的 Bitmap 指定为要绘制的 Bitmap 作为参数创建画布
        Canvas canvas = new Canvas(bitmap);
        //将 View 绘制在画布上
        view.draw(canvas);
        imageView.setImageBitmap(bitmap);

ListView、ScrollView、WebView、RecyclerView 截长图都可以用使用此方法

3、使用 MediaProjection

​ Android 在5.0 之后支持了实时录屏的功能。通过实时录屏我们可以拿到截屏的图像。
大体步骤如下:

  1. 初始化一个 MediaProjectionManager 对象
MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
  1. 创建并启动 Intent
           Intent captureIntent = mediaProjectionManager.createScreenCaptureIntent();
           startActivityForResult(captureIntent, RECORD_REQUEST_CODE);
  1. 在 Activity 的 onActivityResult 方法中获取 'MediaProjection' 对象
MediaProjection mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data);
  1. 创建 ImageReader 对象
//参数1:默认图像的宽度像素
//参数2:默认图像的高度像素
//参数3:图像的像素格式
//参数4:用户想要读图像的最大数量
ImageReader imageReader = ImageReader.newInstance(
metrics.widthPixels, metrics.heightPixels,PixelFormat.RGBA_8888, 1);

ImageReader 类允许应用程序直接访问呈现表面的图像数据创建 ImageReader 对象
主要操作:

  • getSurface()//得到一个表面,可用于生产这个 ImageReader 的图像
  • acquireLatestImage() //从ImageReader的队列获得最新的图像,放弃旧的图像。
  • acquireNextImage() //从ImageReader的队列获取下一个图像
  • getMaxImages()//最大数量的图像
  • getWidth() //每个图像的宽度,以像素为单位。
  • getHeight() //每个图像的高度,以像素为单位。
  • getImageFormat()//图像格式
  • close() //释放与此ImageReader相关的所有资源。用完记得关
  • setOnImageAvailableListener(OnImageAvailableListener listener, Handler handler)//注册一个监听器,当ImageReader有一个新的Image变得可用时候调用。
  1. 通过 MediaProjection 创建 VirtualDisplay 对象,把内容渲染给 ImageRaederSurface 控件
mediaProjection.createVirtualDisplay("Capture", 
metrics.widthPixels, metrics.heightPixels, 2,
     DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                    imageReader.getSurface(), null, null);

VirtualDisplay 类代表一个虚拟显示器,调用 createVirtualDisplay() 方法,将虚拟显示器的内容渲染在一个 Surface 控件上,当进程终止时虚拟显示器会被自动的释放,并且所有的 Window 都会被强制移除。当不再使用他时,你应该调用 release() 方法来释放资源。

  1. 通过 ImageReader 获取 Image 生成 Bitmap
new Thread(){
                @Override
                public void run() {
                    while (true) {
                        Image image = imageReader.acquireNextImage();
                        //8、
                        if (image != null) {
                            Image.Plane[] plane = image.getPlanes();
                            ByteBuffer buffers = plane[0].getBuffer();
                            int pixelStride = plane[0].getPixelStride();
                            int rowStride = plane[0].getRowStride();
                            int rowPadding = rowStride - pixelStride * metrics.widthPixels;

                            Bitmap bitmap = Bitmap.createBitmap(metrics.widthPixels + rowPadding
                                            / pixelStride, metrics.heightPixels,
                                    Bitmap.Config.ARGB_8888);
                            bitmap.copyPixelsFromBuffer(buffers);
                            FileUtils.saveBitmap(bitmap);
                            image.close();
                        }
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }.start();

Image 为图片数据,Plane 为 Image 的抽象内部类,
image.getPlanes() 获取该图片的像素矩阵,返回值为一个 Plane[] 矩阵
plane.getBuffer() 获取图像数据,getPixelStride()getRowStride() 为获取 Iamge 的一些跨距,经过一系列转换得到图像的尺寸,创建 Bitmap 对象,然后从 Image 的 ByteBuffer 中拷贝像素数据生成 Bitmap

二、MediaProjection 录屏

录屏的实现需要使用 MediaRecoder 类
前面几步同截图类似

1. 初始化一个 MediaProjectionManager 对象
2. 创建并启动 Intent
3. 在 Activity 的 onActivityResult 方法中获取 MediaProjection 对象
4. 初始化 MediaRecorder并准备录制
private void initRecorder() {
        mediaRecorder = new MediaRecorder();
        width = displayMetrics.widthPixels;
        height = displayMetrics.heightPixels;
        dpi = displayMetrics.densityDpi;
      // 视频最大的尺寸 720 * 1280 ,其他视频尺寸使用屏幕大小
        if (dpi > DisplayMetrics.DENSITY_XHIGH) {
            width = (orientation == Configuration.ORIENTATION_LANDSCAPE ? 1280 : 720);
            height = (orientation == Configuration.ORIENTATION_LANDSCAPE ? 720 : 1280);
        }
        //如果是横屏,视频输出时旋转90度
        mediaRecorder.setOrientationHint(orientation != Configuration.ORIENTATION_LANDSCAPE ? 0 : 90);
         //  音频源,这里需要 android.permission.RECORD_AUDIO 权限
        mediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT); 
        //  视频来源  
        mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); 
        //  视频输出格式        
        mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
        // 录制输出文件
         currentVideoFilePath = getRecorderDir();
        mediaRecorder.setOutputFile(currentVideoFilePath);
        //视频编码格式
        mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
        //音频编码格式
        mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
        // 设置最大时长5分钟
        mediaRecorder.setMaxDuration(1 * 60 * 1000);        
        //  设置视频文件的比特率,经过测试该属性对于视频大小影响最大  
        mediaRecorder.setVideoEncodingBitRate(1 * 1024 * 1024);  
        //设置视频分辨率
        mediaRecorder.setVideoSize(width, height);
        //设置视频帧频率
        mediaRecorder.setVideoFrameRate(30);

        // 录制发生错误的监听
        mediaRecorder.setOnErrorListener(new MediaRecorder.OnErrorListener() {
            @Override
            public void onError(MediaRecorder mr, int what, int extra) {

            }
        }); 
       //记录录制时出现的信息事件
        mediaRecorder.setOnInfoListener(new MediaRecorder.OnInfoListener() {
            @Override
            public void onInfo(MediaRecorder mr, int what, int extra) {

            }
        });
        try {
        //准备录制
            mediaRecorder.prepare();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
5. 创建 VirtualDisplay 以进行录屏
virtualDisplay = mediaProjection.createVirtualDisplay("MainScreen", width, height, dpi,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, mediaRecorder.getSurface(), null, null);

VirtualDisplay 的渲染目标 Surface 设置为 MediaRecordergetSurface,后面我就可以通过 MediaRecorder 将屏幕内容录制下来

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

推荐阅读更多精彩内容