Android Multimedia框架总结(二十五)MediaProjection实现手机截屏(无须root)

眼前多少难甘事,自古男儿当自强。这篇文章主要讲述Android Multimedia框架总结(二十五)MediaProjection实现手机截屏(无须root)相关的知识,希望能为你提供帮助。
转载请把头部出处链接和尾部二维码一起转载, 本文出自逆流的鱼yuiop: http://blog.csdn.net/hejjunlin/article/details/53966818
前言: 一年半多以前, 我们曾有个项目, 要做一个截屏功能, 当时负责调研的同事, 答应了产品上这个功能, 但开发一周后, 发现, 无法实现截取手机屏幕图像, 须要root权限, 才能做。因为最近研究MediaProjection, 意外的发现, 竟然无须root, 可以轻松实现次功能。曾经被做不到的, 如今做到了, 很难相信此时的心情。看下今天的Agenda:

  • android源码中使用组合键是如何实现屏幕截图功能的?
  • MediaProjection实现手机截屏效果
  • 简要思路
以我的魅族手机为例, 是同时按电源键+ 音量下键来实现截屏, 苹果手机则是电源键 + HOME键, 小米手机是菜单键+ 音量下键, 而HTC一般是按住电源键再按左下角的“主页”键。那么Android源码中使用组合键是如何实现屏幕截图功能呢?
Android源码中对按键的捕获位于文件PhoneWindowManager.java中


Android Multimedia框架总结(二十五)MediaProjection实现手机截屏(无须root)

文章图片


这个类处理所有的按键事件, 其中函数interceptKeyBeforeQueueing( ) 会对常用的按键做特殊处理。以我自己魅族手机为例, 是同时按电源键和音量下键来截屏, 那么在这个方法中可以看到这么如下代码:
interceptKeyBeforeQueueing

Android Multimedia框架总结(二十五)MediaProjection实现手机截屏(无须root)

文章图片


以上代码总结为: 当按下音量下键时, 且是down时, 会进入到interceptPowerKeyDown方法中, 而按下电源键时, 且是down时, 也会进入interceptPowerKeyDown方法中, 接着进入这个方法中看下:
interceptPowerKeyDown

Android Multimedia框架总结(二十五)MediaProjection实现手机截屏(无须root)

文章图片


以上代码总结为: 如果是interactive为true, 且一些其他条件满足时, 会记录电源键按下的时间, 且进入interceptScreenshotChord方法中:

Android Multimedia框架总结(二十五)MediaProjection实现手机截屏(无须root)

文章图片


以上代码总结为: 用两个布尔变量判断是否同时按了音量下键和电源键后, 再计算两个按键响应Down事件之间的时间差不超过( // Time to volume and power must be pressed within this interval of each other.
private static final long SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS = 150; ) 150毫秒, 也就认为是同时按了这两个键后, 此种case时, 才是真正的屏幕截屏的组合键。
获取到组合键后, 会用一个mHandler 开始post一个runnable,进入这个runnable中:


Android Multimedia框架总结(二十五)MediaProjection实现手机截屏(无须root)

文章图片


以上代码, 会执行方法takeScreenshot方法, 接着进入:

Android Multimedia框架总结(二十五)MediaProjection实现手机截屏(无须root)

文章图片


以上代码总结为: 使用AIDL绑定了service服务到”com.android.systemui.screenshot.TakeScreenshotService”, 注意在service连接成功时, 对message的msg.arg1和msg.arg2两个参数的赋值。其中在mScreenshotTimeout中对服务service做了超时处理。接着我们找到实现这个服务service的类TakeScreenshotService, 该类在(frameworks/base/packages/SystemUI/src/com/android/systemui/screenshot包下):

Android Multimedia框架总结(二十五)MediaProjection实现手机截屏(无须root)

文章图片


对应代码如下:

Android Multimedia框架总结(二十五)MediaProjection实现手机截屏(无须root)

文章图片


以上代码总结为: 引用了GlobalScreenshot类, 调用了takeScreenshot方法, 紧接着进入:

Android Multimedia框架总结(二十五)MediaProjection实现手机截屏(无须root)

文章图片


以上代码总结为: 引用SurfaceControl类, 调用了screenshot方法, 传入了屏幕的宽和高, 这两个参数, 接着进入SurfaceControl类中, 位于frameworks/base/core/java/android/view目录下:

Android Multimedia框架总结(二十五)MediaProjection实现手机截屏(无须root)

文章图片


对应screenshot方法代码如下:

Android Multimedia框架总结(二十五)MediaProjection实现手机截屏(无须root)

文章图片


screenshot

Android Multimedia框架总结(二十五)MediaProjection实现手机截屏(无须root)

文章图片


最终到达native方法中nativeScreenshot

Android Multimedia框架总结(二十五)MediaProjection实现手机截屏(无须root)

文章图片


上面就是java层的部分, 接着到jni层, 在\\frameworks\\base\\core\\jni\\android_view_SurfaceControl.cpp中:

Android Multimedia框架总结(二十五)MediaProjection实现手机截屏(无须root)

文章图片


以上代码总结为: 到jni中, 映射nativeScreenshot方法的是nativeScreenshotBitmap函数, 这个函数代码如下:

Android Multimedia框架总结(二十五)MediaProjection实现手机截屏(无须root)

文章图片

Android Multimedia框架总结(二十五)MediaProjection实现手机截屏(无须root)

文章图片


以上代码总结为: 实例化ScreenshotClient类, 且调用update方法, 最终通过构造一个新的bitmap, 把screenshot得到的像素点, 及相关信息达到bitmap, 通过GraphicsJNI, 重新创建一个bitmap返回给上层。
我们接着看下ScreenshotClient的声明, 位于SurfaceComposerClient.h中。注意这个类是在frameworks\\native\\include\\gui中, 而前面android_view_SurfaceControl是在\\frameworks\\base\\core\\jni\\下

Android Multimedia框架总结(二十五)MediaProjection实现手机截屏(无须root)

文章图片


最后辗转来到c+ + 层, 就是\\frameworks\\native\\libs\\gui下的SurfaceComposerClient.cpp中, 实现ScreenshotClient声明的函数update, 如下:

Android Multimedia框架总结(二十五)MediaProjection实现手机截屏(无须root)

文章图片


以上代码总结为: 通过ISurfaceComposer接口, 调用captureScreen函数, 我们找到其对应的实现类\\frameworks\\native\\libs\\gui\\ISurfaceComposer.cpp, 找到captureScreen函数的实现如下:

Android Multimedia框架总结(二十五)MediaProjection实现手机截屏(无须root)

文章图片


以上代码总结为: 把IGraphicBufferProducer, 作为binder开始写到parcel中, 最后一版看到transact, 就知道当前是client端, 而server端的onTransact()函数,会接收到传过来的参数。如这里的data。接着看下BnSurfaceComposer的onTransact方法, 这个BnSurfaceComposer依然是在ISurfaceComposer.cpp中。

Android Multimedia框架总结(二十五)MediaProjection实现手机截屏(无须root)

文章图片


以上代码总结为: 当进入到CAPTURE_SCREEN中, data会读取IGraphicBufferProducer生成出的图像buffe, 接着调用 reply-> writeInt32(res); 返回给client.然后再回调到java层。以上就是系统截屏的原理。
那对于多媒体这块可以通过MediaProjection+ VirturalDisplay+ MediaProjectionManager来实现截屏, 以下我的实例效果图:
【Android Multimedia框架总结(二十五)MediaProjection实现手机截屏(无须root)】效果图1: 操作过程, 点击浮动小剪刀, 就可以实现截屏, 拖动小剪刀, 可到任意位置:


Android Multimedia框架总结(二十五)MediaProjection实现手机截屏(无须root)

文章图片


效果图2: 截图后过程


Android Multimedia框架总结(二十五)MediaProjection实现手机截屏(无须root)

文章图片


主界面:


Android Multimedia框架总结(二十五)MediaProjection实现手机截屏(无须root)

文章图片


体验apk:
链接: http://pan.baidu.com/s/1kVugxCV 密码: 97z0
实现思路:
  • MediaProjection是一个token, 用户可以授予应用程序捕获屏幕内容和录制系统音频。
  • 屏幕截取, 可以通过MediaProjectionManager的createScreenCaptureIntent, 创建一个intent, 保证有足够能力截取屏幕上的内容, 但是没有系统声音。
  • 调用setUpMediaProjection()方法获取共享数据并对其进行赋值; 若已初始化, 则直接调用virtualDisplay()方法利用之前定义的变量对截屏环境进行初始化。
第一时间获得博客更新提醒, 以及更多android干货, 源码分析, 欢迎关注我的微信公众号, 扫一扫下方二维码或者长按识别二维码, 即可关注。
Android Multimedia框架总结(二十五)MediaProjection实现手机截屏(无须root)

文章图片

如果你觉得好, 随手点赞, 也是对笔者的肯定, 也可以分享此公众号给你更多的人, 原创不易

    推荐阅读