#.简介
Android5.0以后提供了MediaProjectionManager系统服务来获取手机屏幕画面。
需要获取相应服务的权限,然后
创建虚拟显示器,物理屏幕画面会不断被投影到虚拟现实器,输出到创建虚拟显示器时设置的Surface上。使用过程一般会结合ImageReader或OpenGL来进行。
(在更低的版本,如Android4.4,获取屏幕画面需要通过ADB指令来进行。但目前市面上基本已经见不到比Android5.0版本还低的安卓手机)

#.基本使用流程如下

1.获取MediaProjectionManager服务实例
mProjectionManager = (MediaProjectionManager)activity.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
2.通过MediaProjectionManager创建请求屏幕捕捉的隐式Intent,发送到目标Activity。
这时会显示一个弹窗,“xxx将开始截取您屏幕上显示的所有内容”,申请用户同意。

Intent captureIntent = mProjectionManager.createScreenCaptureIntent();
activity.startActivityForResult(captureIntent, SCREEN_CAPTURE_REQUEST_CODE);
3.在发送方Activity的onActivityResult(int requestCode, int resultCode, Intent data)处理请求结果,若用户同意了请求,就可以通过返回的结果获取MediaProjection对象执行后面的流程进行屏幕画面捕捉
MediaProjection mediaProjection = mProjectionManager.getMediaProjection(resultCode, data);
4.通过MediaProjection创建创建虚拟显示器对象,创建后物理屏幕画面会不断地投影到虚拟显示器VirtualDisplay上,输出到虚拟现实器创建时设定的输出Surface上。一般显示屏的刷新频率为60Hz,即会以60帧/s的频率来刷新Surface上的内容。
VirtualDisplay virtualDisplay = mediaProjection.createVirtualDisplay(//VirtualDisplay名称,必须非空VIRTUAL_DISPLAY_NAME,//VirtualDisplay的尺寸宽高,必须大于0mDisplayWidth, mDisplayHeight,//像素密度dpi,必须大于0,一般取1VIRTUAL_DISPLAY_DPI,//设置标识位为公共显示器,一般都是使用该标志位DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,//VirtualDisplay的图像绘制目标Surface,//例如,可以是SurfaceView的Surface,用于显示;//     或者是MediaCodec的输入Surface,用于编码;//     或者是ImageReader的输入Surface,用于其它处理;//这里用SurfaceTexture中的Surface,直接将源画面转化成纹理displaySurface,//注册状态回调接口,当状态改变时触发//第二个参数为Handler,指明了回调方法的执行线程。// 回调方法会在该Handler所对应的Looper消息队列中被执行,即在其所在线程执行;// 若传入null则会在当前线程中执行。
//                mDisplayCallback, null );null, null );
4.1所以虚拟现实器创建时设定的输出Surface很重要:
如果要截屏,那么可以使用ImageReader的输入Surface,然后通过ImageReader将Surface中的画面以Image形式读取出来;
如果做录屏直播,
可以不断取出上面ImageReader的画面,然后通过OpenGL绘制到MediaCodec的输入Surface去编码;
也可以为画面捕捉的线程初始化EGL环境,直接创建一个SurfaceTexture,获取对应的Surface,设置给虚拟现实器,此时虚拟显示器的输出画面会被自动转化为OpenGL外部纹理。经过OpenGL中间处理后,最终将这个纹理绘制到MediaCodec的输入Surface去编码。
4.2另外,注意:MediaProjection捕获的屏幕画面是整个屏幕范围的画面,包括导航条Navigaiton Bar。
所以设置虚拟现实器尺寸时,要使用getRealMetrics来获取尺寸,如果使用getMetrics方法,获取高度不包含导航条。
若实际捕获画面尺寸大于虚拟现实器尺寸,会将捕获画面等比例放缩到虚拟现实器能够容纳的程度,然后空余位置会显示黑边,效果类似“centerInside”。
5.在使用完毕后,结束屏幕画面捕捉并释放相应资源
mediaProjection.stop();
virtualDisplay.release();

#.ImageReader介绍

ImageReader内部关联一个Surface,可以将该Surface交给Camera或VirtualDisplay等作为画面输出目标。
ImageReader可以直接从Surface读取图像数据,其内部有图像数据缓存队列,画面生产方不断把画面绘制到ImageReader的Surface上,并被存入缓存队列;而画面使用方,不断通过ImageReader从其缓存队列上取出Image对象。
maxImages设定了ImageReader最多缓存多少Image,当缓存数量达到maxImages后,若老的Image若一直不close()为队列释放空间,将不会有新的Image放入队列,那么Surface上的画面可能还未被存入队列就被新的画面刷新,结果就是所谓"丢帧"。
1.构造方法
//输出图像的宽、高,图像颜色格式、缓存队列最多缓存的Image数量
mImageReader = ImageReader.newInstance(width, height, format, maxImages);
2.取出Image
//从ImageReader队列中获取最新的一帧Image(队列末尾),并且将前面老的Image都close()掉,如果没有新的可用的Image则返回null。
//这是Android推荐用的方式,因为能获取到尽可能新的画面。
//   不过稍加思索可知,当maxImages=1时,其实与下面.acquireNextImage()的效果没有差异。
//注意用完Image,及时Image.close()
image = mImageReader.acquireLatestImage();//从ImageReader的队列中获取下一帧Image,如果没有新的则返回null。
//注意用完Image,及时Image.close()
image = mImageReader.acquireNextImage();
3.回调接口
//有新的可用图像时的通知回调
//第二个参数为Handler,指明了回调方法的执行线程。
// 回调方法会在该Handler所对应的Looper消息队列中被执行,即在其所在线程执行;
// 若传入null则会在当前线程中执行。
mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {@Overridepublic void onImageAvailable(ImageReader reader) {}
}, null);
4.关键代码:将ImageReader中读取出的Image转换成对应Bitmap
/*将Image对象的ByteBuffer中字节数据写进Bitmap里,因为Bitmap接收的是像素格式的数据,所以需要做一些处理*/
//获取Image的图片像素宽、高
int width = image.getWidth();
int height = image.getHeight();
//获取Image中的ByteBuffer
Image.Plane[] planes = image.getPlanes();
ByteBuffer buffer = planes[0].getBuffer();
//获取Image中每个像素的Byte数(像素间距),这里因为是RGBA4个通道,所以每个像素的间距是4
int pixelStride = planes[0].getPixelStride();
//获取Buffer中每行像素的字节宽度rowStride
int rowStride = planes[0].getRowStride();
//因为内存对齐的缘故,所以buffer的行宽度与上面获取到的width*pixelStride会有差异
//内存对齐的padding字节数 = Buffer行宽 - Image中图片宽度×像素间距
int rowPadding = rowStride - pixelStride * width;
//接收ByteBuffer数据的Bitmap需要的像素宽度 = Image中图片宽度 + 内存对齐宽度/像素间距
//每行的对应位置会填充一些无效数据
//(其实直接写成rowStride/pixelStride也行,按步骤写只是为了让逻辑清晰)
Bitmap tempBmp = Bitmap.createBitmap(width + rowPadding / pixelStride, height, Bitmap.Config.ARGB_8888);
tempBmp.copyPixelsFromBuffer(buffer);
//释放Image,以便继续从输入Surface接收新的画面
image.close();
//将tempBmp中每行像素中的无效数据过滤掉
mLastBitmap = Bitmap.createBitmap(tempBmp, 0, 0, width, height);
//释放tempBmp
tempBmp.recycle();
return mLastBitmap;

#.当使用SurfaceTexture作为虚拟显示器输出目标时,非常关键易出错的一步:

//这一步非常关键,它设置了SurfaceTexture中Surface的大小,否则默认输出的纹理大小为1*1像素
//注意:实际运行代码发现,设置的大小,应该与屏幕捕获的画面大小一致,否则:
//   1.正常情况下,画面从Surface的左上角开始绘制,超出的部分会被裁减掉;
//     Surface上多余的部分会保留底色,而这部分底色一般显示出来会是黑色(要看显示的控件怎么处理底色)
//   2.若这里设置的宽、高与捕获画面的宽、高正好相反,则画面会居中缩放到Surface能容纳的大小,
//     不会发生裁剪,但是Surface上多余部分会保留底色。
//     利用这个特点,如果需要在横竖屏切换时裁掉多余黑色部分,可以调整OpenGL裁剪矩阵,将多余黑色部分裁减掉
mSurfaceTexture.setDefaultBufferSize(mDisplayWidth, mDisplayHeight);

Android 使用MediaProjection+ImageReader捕捉屏幕画面相关推荐

  1. Android 12 新APP启动画面(SplashScreen API)简介源码分析

    以往的启动画面 默认情况下刚启动APP时会显示一会白色背景 如果把这个启动背景设置为null,则一闪而过的白色会变成黑色 如果把启动Activity设置为背景透明[< item name=&qu ...

  2. Android Camera2相机预览画面放大缩小(数码变焦DigitalZoom)功能实现

    一.前言 Android自定义相机开发中,常常会有通过手势放大或缩小相机预览画面的需求,即数码变焦DigitalZoom. 二.接口说明 1. 获取最大的放大倍数 float maxZoom = mC ...

  3. android手机 无电池开机画面,华为手机开不了机停在开机画面怎么办【详解】

    手机对于我们来说都是不陌生的,因为手机的发展太迅速了,同时也加快了手机的普及率.而智能手机的更新速度也是非常的快的.功能和性能也变得越来越好了,尤其是华为手机这几年变化真的是非常的大的,但是很多华为手 ...

  4. Android上电到现实充电画面,android 电池(二):android关机充电流程、充电画面显示(一)...

    上一篇我们讲了锂电池的充放电的流程和电池的一些特性,这一节我们重点说一下android关机充电是怎么.充电画面显示是怎么实现的,这个在工作中也比较有用,我们开始做这一块的时候也走了不少的弯路.我记得我 ...

  5. 电视android界面卡,电视盒子画面卡顿怎么办?这三个方法完美解决困扰

    进入 2020 年,不知道有多少家里还在用几年前购买的电视盒子,其实电视盒子就和我们的手机一样,虽然使用频率相比较手机而言没有那么频繁,但随着近些年的发展,智能电视软件越来越大,对电视盒子的配置需要也 ...

  6. android打开小屏登录画面,Android炫酷登录界面

    来看一波图片吧 CoverEyeLogin.gif 动画效果介绍 1.当用户输入用户名时,小猫头鹰的眼睛是没有被捂住的 2.当用户输入密码时,小猫头鹰会用手捂住眼睛 3.如果用户名和密码都已经输入完毕 ...

  7. android 如何快速检测到画面变化_电瓶修复—如何快速检测电池的好坏2

    本公众号会不断更新详细的电瓶修复技术及分享开店经验 (连载2)2.全新电池安装上去后,没几天或者一个多月客户反映跑的不远. 这种基本可以排除是电池故障引起的.尤其如果你安装的天能.超威之类的大厂电池. ...

  8. android手机 无电池开机画面,安卓手机无法开机的6种解决方法

    现在安卓智能机十分普遍,但是也容易出现多重问题.关于手机不能开机是很多朋友都碰到的问题.那么手机不能开机怎么办?不用担心,下面学习啦小编就为大家介绍安卓手机突然无法开机的6种解决方法,希望可以帮到大家 ...

  9. Teamviewer11现在无法捕捉屏幕画面。这可能是由于快速的用户切换或远程桌面会话断开/最小化。...

    如果你用"远程桌面"连过去开启Teamviewer的话,当你退出"远程桌面"后,外网用Teamviewer连接就会出现这个问题. 解决方法: 不用远程连接过去开 ...

最新文章

  1. kotlin中的异常处理_如何使用assertFailsWith在Kotlin中测试异常
  2. Centos 7.4 中http-2.4 的基本实现和 https 的实现
  3. 131. 直方图中最大的矩形【单调栈】
  4. CentOS 安装过程中格式化 SATA 硬盘巨慢的问题
  5. android 弹出菜单 toast,Android学习第二天:Toast(提醒)、Menu(菜单)、Intent的显式和隐式(包括打开、适配网站,调用拨号界面等)...
  6. 【QQ输入法】QQ输入法-剪切板 释放内存
  7. 三星s10能升级android11,三星 S10+手机已在测试 Android 11 系统
  8. Fatal error: Allowed memory size of 8388608 bytes exhausted (tried to allocate 359404 bytes) in
  9. 本机Android应用程序教程:WhatsApp克隆
  10. 【kafka】WARN Attempting to send response via channel for which there is no open connection
  11. 腾讯推出基于区块链存证的“点亮莫高窟”活动
  12. 暴力猴插件的安装及使用
  13. for循环的经典例题
  14. 《疯狂动物城》 —浪潮分布式存储让动画渲染更高效
  15. 20145213《Java程序设计》第五周学习总结
  16. 远控软件GHOST源码免杀
  17. java读取文件的字节数据
  18. 【179期】这些最常用的Linux命令都不会,你怎么敢去面试?
  19. windows下服务器的数据的迁移解决方案
  20. mysql5.1为什么programdata文件夹里只有frm文件

热门文章

  1. 计算机非全日制硕士 选校,非全日制硕士研究生,到底值不值得报考?看完这篇就懂了!...
  2. 迭代重建算法中投影矩阵的计算
  3. 抓取前程无忧招聘信息
  4. uptown funk 火星哥霸占BILLBOARD排行榜8周的嗨爆神曲
  5. HTML5小游戏-简单抽奖小游戏
  6. 抖音同城怎么引流?抖音如何引流到线下实体店?
  7. 英文参考文献按照首字母排序使用matlab实现
  8. 【物联网】老程序教你一招,10行代码让超声波模块秒变声控开关
  9. Python程序设计实战:输出古诗实战
  10. 2022软件测试技能 APP自动化测试 Python+Appium+Uiautomator2 实战教程