Android截屏技术总结
一、概述
Android截屏的几种场景
- 场景一:截取应用内的固定Activity界面
- 针对普通View
- 针对WebView
- 场景二:截取全局屏幕界面
- 基于Root方案
- 针对Android5.0之后版本的方案
- 场景三:通过PC对Android进行截屏
- 使用ddmlib.jar
- 使用ADB工具
二、截取应用内固定Activity的普通View
原理:利用View.getDrawingCache()方法获取View的绘制缓存,保存为图片
Sample:
1 | /** |
这种截图方式仅能截取特点View的画面,如果设计合理,能够满足大部分应用需求了。另外,这种方式可以延伸一下,通过截取Activity而获取图片。代码如下:
1 | public static Bitmap captureScreen(Activity activity) { |
备注:以上方案针对WebView会出现白屏或黑屏现象。
三、截取应用内固定Activity的WebView
原理:针对View进行截屏适用于WebView会出现白屏或黑屏的问题,可以通过WebView的capturePicture()方法获取图片,解决该问题
Sample
1 | /** |
备注:capturePicture()方法在API Level 19后被标记为deprecated,但是目前还可以使用
四、截取全局屏幕:Root方案
以下方案都Root之后才能成功
原理:在一般的linux文件系统中,通过/dev/fb0设备文件来提供给应用程序对framebuffer进行读写的访问。这里,如果有多个显示设备,就将依次出现fb1,fb2,…等文件。而在我们所说的android系统中,这个设备文件被放在了/dev/graphics/fb0,而且往往只有这一个。这种方法使用的最为普遍,linux系统中经常使用这种方法实现截屏,一般步骤是:
- 读取framebuffer
- Framebuffer转换为bitmap
- bitmap生成图像文件
Sample:
1 | /** |
在AndroidManifest.xml加上两行权限:
1 | <uses-permission android:name=”android.permission.WRITE_EXTERNAL_STORAGE” /> |
这方案有个变种,直接利用screencap命令进行截屏,本质上该命令读取系统的framebuffer,需要获得系统权限(而直接通过ADB调用screencap命令是不需要ROOT的)
1 | /** |
五、截取全局屏幕:Android5.0之后版本的方案
Google从Android 5.0 开始,给出了截屏案例ScreenCapture,在同版本的examples的Media类别中可以找到。给需要开发手机或平板截屏应用的小伙伴提供了非常有意义的参考资料,由于以前版本的API是隐藏的,要想开发一个截屏应用需要费一番心思且有局限性。当然了,这里说的截屏不是应用程序本身,而是包括状态栏在内的整个屏幕,不管当前运行的是什么程序,效果同按下手机自带截屏快捷键一样
Sample:
1 | public class Service1 extends Service |
从onCreate()方法开始,其调用了两个方法:createFloatView()和createVirtualEnvironment();createFloatView()方法负责浮动小球的生成、拖动及其点击事件的响应;createVirtualEnvironment()方法定义截屏所需的变量(包括屏幕信息、图像格式、保存格式等等)。另外,各个方法利用Log日志类输出了运行过程中的状态信息,便于观察代码执行过程。
关键之处就在于对浮动小球点击事件的响应实现,而拖动只是附带的一个辅助功能而已。浮动小球点击事件的响应代码也完成了4件事情,下面一一进行分析。
1、隐藏小球
1 | mFloatView.setVisibility(View.INVISIBLE); |
2、初始化截屏环境
1 | public void startVirtual(){ |
可以看出,这个过程先是对MediaProjection类实例mMediaProjection的值进行了判断,若之前没有初始化(即值为null),则调用setUpMediaProjection()方法获取共享数据并对其进行赋值;若已初始化,则直接调用virtualDisplay()方法利用之前定义的变量对截屏环境进行初始化,而真正执行最终操作的方法为createVirtualDisplay()。
3、屏幕截取
截屏环境初始化完成之后,便可以开始获取屏幕信息了,所以接下来调用的是startCapture()方法。该方法的实现和之前不同,也是容易出错的地方在于以下两句代码:
1 | int rowPadding = rowStride - pixelStride * width; |
值得注意的是调用方法createBitmap()创建Bitmap对象时所用的第1、3个参数,分别对应于图像的宽度、格式。实现过程中发现,只有将格式设置为ARGB_8888才能获取想要的图像质量;而对于宽度,后面会解释为什么要为其设置偏移值。
4、显示小球
1 | mFloatView.setVisibility(View.VISIBLE); |
备注:该截屏方式仅适用于Android5.0以上系统
六、其他
1、在PC端使用Android ddmlib进行截屏
在DDMS的Devices工具栏上我们可以看到有一个Screen Capture功能按钮,我们可以获取当前屏幕信息。使用高效的ddms截图方式,于是就需要使用ddmlib.jar这个库。ddmlib.jar包在 tools/lib文件夹下
1 | public class ScreenShot { |
使用的话很简单(注意这是计算机上的截取Android屏幕的工具,并不是Android上截屏的工具),创建一个Java项目就行了
1 | public class PCScreenShot { |
2、在PC端使用ADB进行截屏
adb shell /system/bin/screencap -p /sdcard/screenshot.png(保存到SDCard)
adb pull /sdcard/screenshot.png d:/screenshot.png(保存到电脑)
备注:以上两种截屏方式不需要ROOT权限。原因是:ddms 通过 adb 发送信号给设备上的 adbd 守护进程,adbd 里面的 framebuffer service 负责整个截屏过程。所以,实际上是 adbd 守护进程启动了 screencap。adbd 是以 shell 用户执行的, 而系统为 shell 用户分配 graphics 组,所以 shell 用户是有权限调用 surfaceflinger 的接口的。