Android 系统副屏截屏功能

收到一个客户需求,要求对双屏设备的副屏进行截图。查询资料后发现,系统截图有两种方法,一种是通过SurfaceControl.screenshot提供的接口调用,还有一种是通过screencap 命令获取,这两种方式默认都需要使得系统签名才能使用。

  • 方法一:SurfaceControl.screenshot

    android 原生的音量减+电源键截屏功能最终会调用到在SysmteUI进程中。
frameworks/base/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
    void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible, int x, int y, int width, int height) {
        ......
        // Take the screenshot
        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;
        }
        ......

从这个地方可以看到SurfaceControl.screenshot是执行方法,这里会返回当前桌面所在的截图,进入到screenshot方法

    public static Bitmap screenshot(int width, int height) {
        // TODO: should take the display as a parameter
        IBinder displayToken = SurfaceControl.getBuiltInDisplay(
                SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN);
        return nativeScreenshot(displayToken, new Rect(), width, height, 0, 0, true,
                false, Surface.ROTATION_0);
    }

这里可以看到会调用一个native方法nativeScreenshot,第一个参数就是使用哪个屏幕,默认是BUILT_IN_DISPLAY_ID_MAIN主屏,后面几个参数是大小和方向,
由此可知道,当传递displayToken为副屏的display id时即可截取副屏。
基于此理论,我们在应用中写出相关代码测试

try {
            @SuppressLint("PrivateApi") Class<?> mClassType = Class.forName("android.view.SurfaceControl");
            Method nativeScreenshotMethod;
            nativeScreenshotMethod = mClassType.getDeclaredMethod("nativeScreenshot", IBinder.class, Rect.class, int.class, int.class, int.class, int.class, boolean.class, boolean.class, int.class);
            nativeScreenshotMethod.setAccessible(true);
            Method getBuiltInDisplayMethod = mClassType.getMethod("getBuiltInDisplay", int.class);
            IBinder displayToken = (IBinder)getBuiltInDisplayMethod.invoke(mClassType, 1);
//            Log.d("MainActivity", "zly --> nativeScreenshotMethod before");
            Bitmap sBitmap = (Bitmap)nativeScreenshotMethod.invoke(mClassType, displayToken, new Rect(), 1920, 1080, 0, 0, true, false, Surface.ROTATION_0);
//            Log.d("MainActivity", "zly --> nativeScreenshotMethod after sBitmap=" + (sBitmap != null));
        } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
            Log.d("MainActivity", "zly --> e:" + e.toString());
        }

其中SurfaceControl是hide的类,所以必须要用反射,由于nativeScreenshot方法是private static 修饰的所以必须使用nativeScreenshotMethod.setAccessible(true);设置为可修改,否则会抛出异常。
在使用nativeScreenshot方法时,发现一直返回的NULL,无法获取返回的Bitmap,
网上查询资料后发现需求添加权限
<uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
但是添加完后,依然返回为空,于是把应用进行系统签名后可正常截图。这里可以确认到缺少对应权限导致,
于是查看log,发现一个权限报错的地方
Permission Denial:can't read framebuffer pid
找到对应代码位置

frameworks/native/services/surfaceflinger/SurfaceFlinger_hwc1.cpp
status_t SurfaceFlinger::onTransact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
    switch (code) {
        case CREATE_CONNECTION:
        case CREATE_DISPLAY:
        case SET_TRANSACTION_STATE:
        case BOOT_FINISHED:
        case CLEAR_ANIMATION_FRAME_STATS:
        case GET_ANIMATION_FRAME_STATS:
        case SET_POWER_MODE:
        case GET_HDR_CAPABILITIES:
        {
            // codes that require permission check
            IPCThreadState* ipc = IPCThreadState::self();
            const int pid = ipc->getCallingPid();
            const int uid = ipc->getCallingUid();
            if ((uid != AID_GRAPHICS && uid != AID_SYSTEM) &&
                    !PermissionCache::checkPermission(sAccessSurfaceFlinger, pid, uid)) {
                ALOGE("Permission Denial: "
                        "can't access SurfaceFlinger pid=%d, uid=%d", pid, uid);
                return PERMISSION_DENIED;
            }
            break;
        }
        case CAPTURE_SCREEN:
        {
            // codes that require permission check
            IPCThreadState* ipc = IPCThreadState::self();
            const int pid = ipc->getCallingPid();
            const int uid = ipc->getCallingUid();
            if (false/* (uid != AID_GRAPHICS) &&
                    !PermissionCache::checkPermission(sReadFramebuffer, pid, uid)*/) {
                ALOGE("Permission Denial: "
                        "can't read framebuffer pid=%d, uid=%d", pid, uid);
                return PERMISSION_DENIED;
            }
            break;
        }
    }

uid != AID_GRAPHICS这个地方会导致第三方应用被拦截掉,屏蔽掉即可。
if (false/* (uid != AID_GRAPHICS) &&
!PermissionCache::checkPermission(sReadFramebuffer, pid, uid)*/) {

  • 方法二:screencap命令方式

screencap命令参数如下

screencap [-hp] [-d display-id] [FILENAME]
-h: this message
-p: save the file as a png
-d: specify the display id to capture, default 0

T2:/ $ screencap -d 1 -p /sdcard/ff.png
使用这个命令即可截取副屏,-d后面对应的是屏幕id,0为主屏,1为副屏,
使用Runtime.getRuntime().exec命令即可
注:方法二没有进行尝试,应该需要系统签名才可以

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 官网 中文版本 好的网站 Content-type: text/htmlBASH Section: User ...
    不排版阅读 4,465评论 0 5
  • Android 调试桥 Android 调试桥 (adb) 是一个通用命令行工具,其允许您与模拟器实例或连接的 A...
    guanjm阅读 1,518评论 0 1
  • 专业考题类型管理运行工作负责人一般作业考题内容选项A选项B选项C选项D选项E选项F正确答案 变电单选GYSZ本规程...
    小白兔去钓鱼阅读 9,058评论 0 13
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,535评论 0 17
  • 在使用Mac的时候,很多场景下我们都需要使用到截屏。不得不说,在MacOS中进行截屏是一件相当轻松的事情,因为Ma...
    Mac高级玩家阅读 6,345评论 1 7