最近研究了一些Android的截屏方法,做一个总结。

图片剪裁方法
使用View.getDrawingCache()得到Bitmap。非常简单但是只能截图本应用的图片,并且没办法控制截图的范围。
对Bitmap进行截屏。可以方便的操作截取大小,但是需要提前截取整个屏幕,然后再处理生成的Bitmap。截取屏幕流程:打开一个新的Window全屏展示,上面包含一个CropView->操作CropView选择区域->根据选择区域截取Bitmap。
代码调用
一般需求用来从相册选择或者从相册发送图片。 
(1).选择图片。打开系统选择图片的Activity并设置相应的参数。

// 打开选择的Activity.
private void selectPic(){
    Intent intent = new Intent("android.intent.action.PICK");
    intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
    startActivityForResult(intent, REQUEST_PICK_IMG);
}
1
2
3
4
5
6
(2).截切图片。选择图片成功(onActivityResult)以后,设置相应参数开始裁剪图片。

// 截取图片
public void cutImage(Uri uri){
    try {
        Intent intent = new Intent("com.android.camera.action.CROP");
        intent.setDataAndType(uri, "image/*");
        intent.putExtra("crop", "true");
        intent.putExtra("aspectX", 1);
        intent.putExtra("aspectY", 1);
        intent.putExtra("outputX", width);
        intent.putExtra("outputY", height);
        // 不返回bitmap,全使用uri来传递,防止图片使用内存太大
        intent.putExtra("return-data", false);
        intent.putExtra("output", outputUri);
        intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
        intent.putExtra("scale", true);
        intent.putExtra("scaleUpIfNeeded", true);
        intent.putExtra("noFaceDetection", true);
        startActivityForResult(intent, REQUEST_CUT_IMG);
    }
    catch (Exception e) {
        Log.e("Test", "com.android.camera.action.CROP error");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(3).onActivityResult处理裁剪图片成功以后回调。

流程分析(基于api25)
系统从相册打开图片,然后截取图片。源码在com.android.gallery3d包下,com.android.gallery3d是一个单独的应用(进程)。 
(1).打开相册com.android.gallery3d/.app.GalleryActivity,选择图片,略过。 
(2).打开截图界面com.android.gallery3d/.filtershow.crop.CropActivity。 
(3).执行BitmapIOTask.doInBackground,对sourceUri进行操作,保存在dstUri中。 
(4).执行CropActivity.getCroppedImage进行截图。

protected static Bitmap getCroppedImage(Bitmap image, RectF cropBounds, RectF photoBounds) {
    // cropBounds:截取大小。photoBounds:图片大小。
    RectF imageBounds = new RectF(0, 0, image.getWidth(), image.getHeight());
    RectF crop = CropMath.getScaledCropBounds(cropBounds, photoBounds, imageBounds);
    if (crop == null) {
        return null;
    }
    Rect intCrop = new Rect();
    crop.roundOut(intCrop);

// 相当于直接对Bitmap 进行截取。
    return Bitmap.createBitmap(image, intCrop.left, intCrop.top, intCrop.width(),
            intCrop.height());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
api21以上使用MediaProjectionManager,MediaProjection,VirtualDisplay,ImageReader。
代码调用
(1).申请权限

private void requestCapturePermission() {
    MediaProjectionManager mediaProjectionManager = (MediaProjectionManager)
            getSystemService(Context.MEDIA_PROJECTION_SERVICE);
    startActivityForResult(mediaProjectionManager.createScreenCaptureIntent(),
            REQUEST_MEDIA_PROJECTION);
}
1
2
3
4
5
6
获取权限以后,获取MediaProjection对象。

MediaProjection mMediaProjection;
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    switch (requestCode) {
        case REQUEST_MEDIA_PROJECTION:
            mMediaProjection = ((MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE))
                .getMediaProjection(Activity.RESULT_OK, data);
    }
}
1
2
3
4
5
6
7
8
(2).获取屏幕内容

// 获取屏幕内容
VirtualDisplay mVirtualDisplay;
private void virtualDisplay() {
    mVirtualDisplay = mMediaProjection.createVirtualDisplay("screen-mirror",
            mScreenWidth, mScreenHeight, mScreenDensity,
            DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
            mImageReader.getSurface(), null, null);
}
1
2
3
4
5
6
7
8
(3).截图,android.media.Image可以拿到相应的Bitmap信息。

ImageReader.OnImageAvailableListener() {
    // 每当生成一个新的Image 的时候都会回调onImageAvailable
    @Override
    public void onImageAvailable(ImageReader reader) {
        if (reader == null) {
            return;
        }
        // 最终会调用native方法
        Image image = reader.acquireLatestImage();
        if (image == null) {
            startScreenShot();
        } else {
            if (mCropView != null) {
                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 + (pixelStride == 0 ? 0 : rowPadding / pixelStride), height, Bitmap.Config.ARGB_8888);
                // 最终生成了Bitmap保存了图片
                bitmap.copyPixelsFromBuffer(buffer);
            }
        }

if (image != null) {
            // 注意需要调用close
            image.close();
        }

// 防止多次回调onImageAvailable
        reader.close();

}
}, mMainHandler);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
利用ddmlib(DDMS的ddmlib.jar)截屏。
代码调用
(1).创建Java工程,并导入ddmlib.jar,ddms.jar和ddmuilib.jar。在Android SDK的tools/lib目录下面。 
(2).直接完整代码了,注意getDevice中的createBridge需要改成自己电脑adb的路径。takeScreenshot中保存的路径需要设置成自己的路径。main中调用的getDevice需要改成自己手机的id。

public class MyClass {

private static void waitDeviceList(AndroidDebugBridge bridge) {
        int count = 0;
        while (bridge.hasInitialDeviceList() == false) {
            try {
                Thread.sleep(100); // 如果没有获得设备列表,则等待
                count++;
            } catch (InterruptedException e) {
            }
            if (count > 300) {
                // 设定时间超过300×100 ms的时候为连接超时
                System.err.print("Time out");
                break;
            }
        }
    }

/**
     * 连接device
     */
    private static IDevice getDevice(String id) {
        AndroidDebugBridge.init(false);  // 需要初始化 false
        AndroidDebugBridge bridge = AndroidDebugBridge
                .createBridge("/Users/Egos/Library/Android/sdk/platform-tools/adb", false);
        waitDeviceList(bridge);
        IDevice devices[] = bridge.getDevices();

for (IDevice onlinedeivce : devices) {
            if (onlinedeivce.getSerialNumber().equals(id))
                return onlinedeivce;
        }

return null;
    }

/**
     * 截取屏幕
     */
    private static void takeScreenshot(IDevice device) {
        try {
            RawImage rawScreen = device.getScreenshot();
            if (rawScreen != null) {
                int width = rawScreen.width;
                int height = rawScreen.height;
                BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
                int index = 0;
                int indexInc = rawScreen.bpp >> 3;
                for (int y = 0; y < rawScreen.height; y++) {
                    for (int x = 0; x < rawScreen.width; x++, index += indexInc) {
                        int value = rawScreen.getARGB(index);
                        image.setRGB(x, y, value);
                    }
                }
                ImageIO.write(image, "PNG", new File("/Users/Egos/Downloads/test.png"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

public static void main(String[] args) {
        IDevice device = getDevice("03f37b9af0c746f4");
        takeScreenshot(device);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
adb shell screencap -p filepath。使用adb截图保存在手机上面,没有具体分析调用流程。
执行adb shell input keyevent 120,相当于调用的系统截屏(手机组合键截图)。
流程分析(基于api25)
(1).按下组合键以后,执行到PhoneWindowManager.interceptKeyBeforeDispatching()

// PhoneWindowManager.java
public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) {
    // 省略代码
    else if (keyCode == KeyEvent.KEYCODE_S && event.isMetaPressed()
            && event.isCtrlPressed()) {
        if (down && repeatCount == 0) {
            int type = event.isShiftPressed() ? TAKE_SCREENSHOT_SELECTED_REGION
                    : TAKE_SCREENSHOT_FULLSCREEN;
            mScreenshotRunnable.setScreenshotType(type);
            mHandler.post(mScreenshotRunnable);
            return -1;
        }
    }

// 省略代码
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(2).执行到PhoneWindowManager.ScreenshotRunnable.run()

// PhoneWindowManager.java
private class ScreenshotRunnable implements Runnable {
    private int mScreenshotType = TAKE_SCREENSHOT_FULLSCREEN;
    public void setScreenshotType(int screenshotType) {
        mScreenshotType = screenshotType;
    }

@Override
    public void run() {
        takeScreenshot(mScreenshotType);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
(3).执行到takeScreenshot

// PhoneWindowManager.java
private void takeScreenshot(final int screenshotType) {
    // SYSUI_SCREENSHOT_SERVICE = "com.android.systemui.screenshot.TakeScreenshotService"
    final ComponentName serviceComponent = new ComponentName(SYSUI_PACKAGE,
            SYSUI_SCREENSHOT_SERVICE); 
    // 省略代码
    final Intent serviceIntent = new Intent();
    serviceIntent.setComponent(serviceComponent);
    ServiceConnection conn = new ServiceConnection() {
        // 省略代码
    }           
    if (mContext.bindServiceAsUser(serviceIntent, conn,
            Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
            UserHandle.CURRENT)) {
        mScreenshotConnection = conn;
        mHandler.postDelayed(mScreenshotTimeout, 10000);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(4).com.android.systemui.screenshot.TakeScreenshotService

private Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        final Messenger callback = msg.replyTo;
        // 省略代码
        if (mScreenshot == null) {
            mScreenshot = new GlobalScreenshot(TakeScreenshotService.this);
        }

switch (msg.what) {
            // 截取全屏
            case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
                mScreenshot.takeScreenshot(finisher, msg.arg1 > 0, msg.arg2 > 0);
                break;
            // 截取指定区域    
            case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION:
                mScreenshot.takeScreenshotPartial(finisher, msg.arg1 > 0, msg.arg2 > 0);
                break;
        }
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
(5).截取全屏或者指定区域最后都是执行到了同一个逻辑。分析全屏截取。执行mScreenshot.takeScreenshot。

// GlobalScreenshot.java
void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible) {
    mDisplay.getRealMetrics(mDisplayMetrics);
    // 全屏就是 (0,0) -> (width,height)
    takeScreenshot(finisher, statusBarVisible, navBarVisible, 0, 0, mDisplayMetrics.widthPixels,
            mDisplayMetrics.heightPixels);
}
1
2
3
4
5
6
7
(6).GlobalScreenshot.takeScreenshot,截取选择区域也是这个方法。

// GlobalScreenshot.java
void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible,
            int x, int y, int width, int height) {

// 省略代码
    // 截取代码获取了Bitmap。
    mScreenBitmap = SurfaceControl.screenshot((int) dims[0], (int) dims[1]);
    // 截屏失败
    if (mScreenBitmap == null) {
        notifyScreenshotError(mContext, mNotificationManager,
                R.string.screenshot_failed_to_capture_text);
        finisher.run();
        return;
    }

// 省略代码
    // Start the post-screenshot animation
    startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
            statusBarVisible, navBarVisible);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(7).SurfaceControl.screenshot,SurfaceControl是hide的。

// SurfaceControl.java
public static Bitmap screenshot(int width, int height) {
    IBinder displayToken = SurfaceControl.getBuiltInDisplay(
            SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN);
    // 调用native代码截图
    return nativeScreenshot(displayToken, new Rect(), width, height, 0, 0, true,
            false, Surface.ROTATION_0);
}
1
2
3
4
5
6
7
8
扩展
截屏功能native层通过framebuffer来实现的(这里可能不是很准确)。帧缓冲(framebuffer)是Linux为显示设备提供的一个接口,把显存抽象后的一种设备,他允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作。Android系统是基于Linux内核的,所以也存在framebuffer这个设备,我们要实现截屏的话只要能获取到framebuffer中的数据,然后把数据转换成图片就可以了,Android中的framebuffer数据是存放在 /dev/graphics/fb0文件中的,所以我们只需要来获取这个文件的数据就可以得到当前屏幕的内容。

总结
在自身应用中截取一个View比较简单。但是去截取任意界面任意大小,就需要比较大的权限,可能需要root手机。直接使用android.view.SurfaceControl反射处理。需要使用到系统的uid``android:sharedUserId="android.uid.systemui"。api21以后系统开放了截图的接口,可以直接使用(自己的使用的时候碰到过问题)。

参考
为什么 Android 截屏需要 root 权限 
截屏方法总结 
ddmlib使用入门
--------------------- 
作者:CoolEgos 
来源:CSDN 
原文:https://blog.csdn.net/A38017032/article/details/70482514 
版权声明:本文为博主原创文章,转载请附上博文链接!

Android截屏方法总结相关推荐

  1. android利用反射调用截屏api,Android利用反射机制调用截屏方法和获取屏幕宽高的方法...

    想要在应用中进行截屏,可以直接调用 View 的 getDrawingCache 方法,但是这个方法截图的话是没有状态栏的,想要整屏截图就要自己来实现了. 还有一个方法可以调用系统隐藏的 screen ...

  2. android如何截屏快捷键是什么手机,安卓截屏快捷键是什么?截屏方法总结 - Android教程 - 安卓中文网...

    现如今,使用安卓手机的机友们是越来越多,但是对于新手们来说,大家对安卓手机又有多了解呢?相信大家在使用手机的过程中一定有过这样的经历,经常聊QQ以及一些其他的聊天工具,有时微博想截个屏与大家分享都无从 ...

  3. android截屏代码实现方法

    最近由于项目需要,在学习android系统.android是一个基于linux的专门针对手机平台的操作系统.当然,现在的android 3似乎也将进入平板电脑的市场.由于至今为止,大部分的智能手机采用 ...

  4. Android截屏截图的几种方法总结

    Android截屏 Android截屏的原理:获取具体需要截屏的区域的Bitmap,然后绘制在画布上,保存为图片后进行分享或者其它用途 一.Activity截屏 1.截Activity界面(包含空白的 ...

  5. android盒子截图,Android截屏截图的几种方法总结

    Android截屏 Android截屏的原理:获取具体需要截屏的区域的Bitmap,然后绘制在画布上,保存为图片后进行分享或者其它用途 一.Activity截屏 1.截Activity界面(包含空白的 ...

  6. android 6 截屏快捷键是什么,一加6怎么截图 一加6截屏方法汇总

    今天是一加6的首销日,通过今天的表现来看一加6异常火爆,非常受欢迎.相信要不了几天大家会陆陆续续收到真机,对于使用过程当中难免会使用到截屏.那么全新一代的一加6怎么截图呢?下面"脚本之家&q ...

  7. Android截屏截图方法汇总(Activity、View、ScrollView、ListView、RecycleView、WebView截屏截图)

    Android截屏 Android截屏的原理:获取具体需要截屏的区域的Bitmap,然后绘制在画布上,保存为图片后进行分享或者其它用途 一.Activity截屏 1.截Activity界面(包含空白的 ...

  8. 三星 android 截屏,三星S7怎么截屏 3种三星S7截图方法

    三星S7是前不久,三星正式上市的一款高端旗舰手机,如今也已经有不少用户开始了预约.三星S7怎么截屏呢?这是后续不少用户会遇到的一个问题,电脑百事网小编下面分享3种三星S7截图方法. 方法一:快捷键截屏 ...

  9. 荣耀android手机怎么截图,华为荣耀畅玩5C怎么截图/截屏方法教程

    华为荣耀畅玩5C怎么截图/截屏方法教程 来源:www.18183.com作者:皮卡时间:2016-04-29 作为荣耀今年的首款开年作品,该机受到了很多用户们的关注,而小编也相信有不少花粉们已经入手了 ...

最新文章

  1. linux服务器的搭建配置与应用,linux服务器的搭建与配置
  2. ASP.NET MVC5+ 路由特性
  3. Android之修改app名字客户需要升级需要注意的问题
  4. Servlet中获取请求头的数据
  5. 同样是面对失败的责任:任春雷比杜一楠更有种
  6. java 面试700问_JAVA面试700问(一)
  7. charles抓包工具_HTTP协议抓包工具:Charles for Mac
  8. 程序员写代码为什么需要 review?
  9. MongoDB(三):创建、更新和删除文档
  10. outlook搜索栏跑到上面去了_南昌搜索引擎seo优化
  11. 夜神模拟器怎么打开开发者选项
  12. lncRNASNP:SNP位点对lncNA结构和lncRNA-miRNA影响的数据库
  13. 2019 码云 最流行的开源项目 TOP 50
  14. c语言程序设计江义火答案,大学就是一个群英会集的(),天下各处各地的学子到这里来,以寻求天下各种各样的知识。...
  15. 循环队列–C语言实现–数据结构
  16. cuda编程与gpu并行计算(六):图稀疏矩阵转为CSR结构并传入gpu
  17. 网络舆情分析关键词怎么获取的系统平台方法
  18. AndroidStudio - - - 点击头像更换头像_菜单选择_相机拍照与相册获取
  19. 【AHOI2009】【BZOJ1800】fly 飞行棋
  20. laravel-admin 省市区三级联动的爬坑问题

热门文章

  1. SQL2012数据库加密方法
  2. 企业家Scott Gerber:小公司应用开发的十条建议
  3. jQuery工作原理解析以及源代码示例
  4. 漂亮的NavMenu导航控件
  5. 基本电子电路系列——MOS管
  6. Linux动态链接库的使用
  7. Linux之特殊权限(SUID/SGID/SBIT)
  8. Ubuntu下使用VSCode的launch.json及tasks.json编写
  9. 【Paper】英文论文写作小技巧
  10. 解决sublime text无法安装插件问题