关于Android5.0以上屏幕截图探索总结

时间:2022-06-06 15:22:33

前言

做过Android屏幕截图的朋友应该知道在Android5.0之前如果希望截图屏幕,是需要获取系统root权限的。但,在5.0之后Android开放了新的接口android.media.projection,使用该接口,第三方应用程序无需获取系统root权限也可以直接进行屏幕截图操作了。查询其官方api可知,该接口主要用来“屏幕截图”操作和“音频录制”操作,这里只讨论用于屏幕截图的功能。由于使用了媒体的映射技术手段,故截取的屏幕并不是真正的设备屏幕,而是截取的通过映射出来的“虚拟屏幕”。不过,因为截图我们希望的得到的肯定是一张图而已,而“映射”出来的图片与系统屏幕完全一致,所以,对于普通截屏操作,该方法完全可行。

一、使用方法

首先用参数MEDIA_-PROJECTION_SERVICE调 用Context.getSystemService(),得到MediaProjectionManager类别实例;
其次,调用 createScreenCaptureIntent ()得到一个Intent;再次,使用startActivityForResult()启动屏幕捕捉;
最后,将结果返回到 getMediaProjection()上,获取捕捉数据。

二、Demo案例

该demo中使用了service,且该服务需要获取到startActivityForResult中的结果数据,由于直接在service中调用startActivityForResult不现实,故在该demo中还是用了共享类数据模型。由于代码较多,下面只对关键步骤稍作说明。
1、关键步骤及代码
1) 获取MediaProjectionManager类实例:

protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mMediaProjectionManager=MediaProjectionManager)getApplication().getSystemService(Context.MEDIA_PROJECTION_SERVICE);
startIntent();
}

2) 利用MediaProjectionManager类实例的功能函数createScreenCaptureIntent()生成intent,为接下来的的抓取屏幕做准备

startActivityForResult(mMediaProjectionManager.createScreenCaptureIntent(), REQUEST_MEDIA_PROJECTION);

3) 在onActivityResult()中获取resultCode和resultData,以方便下面getMediaProjection()使用。

public void onActivityResult(int requestCode, int resultCode, Intent data) {
......
result = resultCode;
intent = data;
......
}

4) 利用第一步中的MediaProjectionManager类实例,通过getMediaProjection()方法(参数分别为3中的resultCode和resultData)获取当前的屏幕映射,并保存到临时MediaProjection实例中。

public void setUpMediaProjection(){
mResultData = ((ShotApplication)getApplication()).getIntent();
mResultCode = ((ShotApplication)getApplication()).getResult();
mMediaProjectionManager1 = ((ShotApplication)getApplication()).getMediaProjectionManager();
mMediaProjection = mMediaProjectionManager1.getMediaProjection(mResultCode, mResultData);
Log.i(TAG, "mMediaProjection defined");
}

5) 使用ImageReader实例通过getSurface()方法获取屏幕表层,使用4步中的MediaProjection临时实例通过createVirtualDisplay()方法进行虚拟屏幕的显示。

private void virtualDisplay(){
mVirtualDisplay = mMediaProjection.createVirtualDisplay("screen-mirror",
windowWidth, windowHeight, mScreenDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
mImageReader.getSurface(), null, null);
Log.i(TAG, "virtual displayed");
}

6) 转化并保存截取的屏幕数据到文件(命名、后缀自定义,按照一般的图片格式就可以了,如:png等)

private void startCapture(){
strDate = dateFormat.format(new java.util.Date());
nameImage = pathImage+strDate+".png";

Image image = mImageReader.acquireLatestImage();
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();
Log.i(TAG, "image data captured");
.......
}

2、LOG对比

12-02 15:30:22.390 I/art     (19796): Late-enabling -Xcheck:jni
12-02 15:30:22.710 I/View (19796): ssignParent(ViewParent parent) parent is: android.view.ViewRootImpl@1fc4ad78
12-02 15:30:22.900 I/OpenGLRenderer(19796): Initialized EGL, version 1.4
12-02 15:30:24.620 I/MainActivity(19796): user agree the application to capture screen
12-02 15:30:24.770 I/MainActivity(19796): start service Service1
12-02 15:30:24.930 I/View (19796): ssignParent(ViewParent parent) parent is: android.view.ViewRootImpl@25532924
12-02 15:30:24.930 I/Service (19796): created the float sphere view
12-02 15:30:24.940 I/Service (19796): prepared the virtual environment
12-02 15:30:24.980 I/ash ( 4267): view add pid:19796 uid:10161
12-02 15:30:24.980 I/AppsCleanUp( 4267): add top view, pid:19796 count:1
12-02 15:30:25.630 I/View (19796): ssignParent(ViewParent parent) parent is: null
12-02 15:30:25.660 I/View (19796): ssignParent(ViewParent parent) parent is: null
12-02 15:30:30.160 I/Service (19796): start screen capture intent
12-02 15:30:30.160 I/Service (19796): want to build mediaprojection and display virtual
12-02 15:30:30.170 I/Service (19796): mMediaProjection defined
12-02 15:30:30.190 I/Service (19796): virtual displayed
12-02 15:30:31.200 I/Service (19796): image data captured
12-02 15:30:31.200 I/Service (19796): image file created
12-02 15:30:32.020 I/Service (19796): screen image saved
12-02 15:30:40.340 I/PgedBinderListener( 4267): kstate callback type:8 value1=19796 value2=KILLED
12-02 15:30:40.570 I/MediaProcessHandler( 3417): processOp opType: 1, uid: 10161, pid: 19796
12-02 15:30:40.570 W/MediaProcessHandler( 3417): remove target not exist, maybe the UI process: uid: 10161, pid: 19796
12-02 15:30:40.570 I/HwSystemManager( 4203): HoldService:uid:10161 pid:19796 died
12-02 15:30:40.570 I/HwSystemManager( 4203): HoldService:oldVersionKey:10161,19796

3、附件及效果图
【附件】 CaptureScreen.rar
【效果图】
关于Android5.0以上屏幕截图探索总结关于Android5.0以上屏幕截图探索总结
4、操作说明
安装完成并启动该apk后,会弹出一个对话框,点击“立即开始”,整个对话框会直接退出,然后会出现一个火苗洋时代的浮动小球。
需要截屏时,直接单击该小球即可。
默认保存的截图路径在:sdcard/Pictures路径下,
有的手机的图库查看器中设有默认文件夹的选择路径功能,如果在图库查看器中看不到截图,则可以直接通过“文件管理”到“本地/内部存储/Pictures”路径下查看。

三、总结

通过上面实例可以看出,该方法的使用主要工作就在截屏前的准备上。需要说明的是,其中有部分像素微调的代码,该微调操作是为了消除截图的黑边框(毕竟真正截的图是媒体的映射屏~~)。另外,如上文提到,该功能是在Android5.0才开始加入的(或者说开放出来的)新接口,所以,该方法只能用于5.0以上的Android版本(api21以上)。
另外,通过上述说明可以得出,整个阶段分为映射准备阶段和开始截屏并保存阶段。需要注意的是:准备阶段完成后需要等待1s或者更长时间才可以去操作截屏,否则有可能imageReader在进行newInstance时不成功!!(同事在移植该应用时遇到过,在此添加上)