美文网首页
Android 系统副屏截屏功能

Android 系统副屏截屏功能

作者: 阿狸_191d | 来源:发表于2019-10-10 20:22 被阅读0次

    收到一个客户需求,要求对双屏设备的副屏进行截图。查询资料后发现,系统截图有两种方法,一种是通过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命令即可
    注:方法二没有进行尝试,应该需要系统签名才可以

    相关文章

      网友评论

          本文标题:Android 系统副屏截屏功能

          本文链接:https://www.haomeiwen.com/subject/guwapctx.html