Android Framework 常见解决方案(16)android多VirtualDisplay交互方法

112 篇文章 86 订阅

1 原理

该文主要介绍了 多个虚拟屏同时显示并可交互 的解决方案。主要解决多个 VirtualDisplay 虚拟屏同时显示的问题 和 输入交互的问题。

2 修改方案(Android Q)

2.1 多辅助显示

2.1.1 Setting中的辅助屏

分析系统级应用Settings中的选项:Simulate secondary displays 选项,点击这里可以看到有overlay的 辅助屏,一般来讲用这个来模拟 显示器用于调试。

进入到开发者模式->DRAWING->Simulate secondary displays。该点击事件的核心逻辑是:向Settings数据库写入value值,然后会触发 OverlayDisplayAdapter的操作:将Overlay显示设备添加到系统的OverlayDevice列表中 。接下来就可以自动创建 辅助屏了,操作方式直接进入adb的命令行,执行如下逻辑,如下所示:

#创建一块辅助屏显示器,如下所示:
$settings put global overlay_display_devices "1920x1080/320,secure"
#创建两块辅助屏显示器,如下所示:
$settings put global overlay_display_devices "1920x1080/320,secure;1920x1080/320,secure"
#创建三块辅助屏显示器也是类似,以此类推,如下所示:
$settings put global overlay_display_devices "1920x1080/320,secure;1920x1080/320,secure;1920x1080/320,secure"
#关闭辅助屏显示器 如下所示:
$settings put global overlay_display_devices “null”

创建出来的辅助屏 效果如下:

2.1.2 辅助屏上启动Activity

保证不同的辅助屏上启动不同应用Activity,代码如下所示:

ActivityOptions options = ActivityOptions.makeBasic();
//... 
//设置标识位,关键代码1 
options.setLaunchDisplayId(apps[index].mVirtualDisplay.getDisplay().getDisplayId());
Intent intent = new Intent();
//设置标识位,关键代码2
intent.setFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
//这里根据需要设置Component
intent.setComponent(new ComponentName(getItem(position).activityInfo.packageName, getItem(position).activityInfo.name));

startActivity(intent, options.toBundle());

也就是在执行startActivity时候 设置option中的displayId即可。这样可以根据需要在不同的虚拟屏上 启动多个activity。

2.2 实现input事件到各辅助屏上的解决方案

这里实现了一个inputUtil的类,使用反射机制将输入事件分配给各个不同的displayId对应的虚拟屏幕上,参考代码如下所示(注意参考代码只是2个displayId对应的虚拟屏幕的展示,如果需要多个请看懂后 自行调整,难度不大,同时关于算法部分根据需求 将实际屏幕的坐标转换到 辅助屏幕上即可,该部分隐藏)

public class InputUtil {
    private static final String TAG = "InputUtil";

    public static void processEvent(@NonNull Context context, @NonNull MotionEvent event, @NonNull ImageView imageView0ne, @NonNull ImageView imageViewTwo, int displayId) {
        final float THRESHOLD = (float) 0.0001f;
        final float virtual_screen_width = 1920.0f;
        final float virtual_screen_height = 1080.0f;
        float map_x = 0.0f;
        float map_y = 0.0f;
        float event_x = event.getRawX();
        float event_y = event.getRawY();

        int[] positionV1 = new int[2];
        imageView0ne.getLocationOnScreen(positionV1);
        Rect rectV1 = new Rect();
        imageView0ne.getLocalVisibleRect(rectV1);
        //LogUtil.e(TAG, "getLocationOnScreen:" + positionV1[0] + "," + positionV1[1]+"H:"+rectV1.height()+"W:"+rectV1.width());

        int[] positionV2 = new int[2];
        imageViewTwo.getLocationOnScreen(positionV2);
        Rect rectV2 = new Rect();
        imageViewTwo.getLocalVisibleRect(rectV2);
        //LogUtil.e(TAG, "getLocationOnScreen:" + positionV2[0] + "," + positionV2[1]+"H:"+rectV2.height()+"W:"+rectV2.width());
        LogUtil.e(TAG,"StatusBar--H:"+getStatusBarHeight(context)+"W:"+getStatusBarWidth(context));
        //V1 virtual display view rect
        if((event_x > positionV1[0]) && (event_x <positionV1[0]+rectV1.width())){
            if((event_y > positionV1[1]) && (event_y <positionV1[1]+rectV1.height())){
                map_x = {坐标转换算法得x};
                map_y = {坐标转换算法得y};
                LogUtil.e(TAG,"new event x:"+map_x+"y:"+map_y);
                imageView0ne.requestFocus();
            }
        }

        //V2 virtual display view rect
        if((event_x > positionV2[0]) && (event_x <positionV2[0]+rectV2.width())){
            if((event_y > positionV2[1]) && (event_y <positionV2[1]+rectV2.height())){
                map_x = {坐标转换算法得x};
                map_y = {坐标转换算法得y};
                LogUtil.e(TAG,"new event x:"+map_x+"y:"+map_y);
                imageViewTwo.requestFocus();
            }
        }
        event.setLocation(map_x,map_y);
        injectMotionEventProxy(event, displayId);
        LogUtil.e(TAG,"ev2:"+event);
    }
    public static void injectMotionEventProxy(@NonNull MotionEvent event, int displayId){
        eventSetDisplayIdReflection(event,displayId);
        inputManagerInjectMotionEventReflection(event);
    }

    private static int getStatusBarHeight(Context context){
        Resources resources = context.getResources();
        int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android");
        if (resourceId > 0) {
            return resources.getDimensionPixelSize(resourceId);
        }
        return 0;
    }

    private static int getStatusBarWidth(Context context){
        Resources resources = context.getResources();
        int resourceId = resources.getIdentifier("navigation_bar_width", "dimen", "android");
        if (resourceId > 0) {
            return resources.getDimensionPixelSize(resourceId);
        }
        return 0;
    }

    private static void inputManagerInjectMotionEventReflection(MotionEvent event) {
        try {
            Class ownerClass = Class.forName("android.hardware.input.InputManager");
            Field field = ownerClass.getField("INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH");
            Method[] methods = ownerClass.getDeclaredMethods();
            Method method = null;
            for(Method m:methods) {
                if (m.getName().equalsIgnoreCase("getInstance")) {
                    method = ownerClass.getMethod("getInstance");
                    break;
                }
            }
            InputManager inputManager = (InputManager)method.invoke(ownerClass);
            LogUtil.e(TAG,"method invoke:="+inputManager);
            for(Method m:methods) {
                if (m.getName().equalsIgnoreCase("injectInputEvent")) {
                    method = ownerClass.getMethod("injectInputEvent", android.view.InputEvent.class,int.class);
                    break;
                }
            }
            if(!method.isAccessible()){
                method.setAccessible(true);
            }
            method.invoke(inputManager,event,(Integer)field.get(ownerClass));
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }

    private static void eventSetDisplayIdReflection(MotionEvent event,int displayId){
        try {
            Class ownerClass = Class.forName("android.view.MotionEvent");
            Method[] methods = ownerClass.getDeclaredMethods();
            Method method = null;
            for(Method m:methods) {
                if (m.getName().equalsIgnoreCase("setDisplayId")) {
                    method = ownerClass.getMethod("setDisplayId", int.class);
                    break;
                }
            }
            if(!method.isAccessible()){
                method.setAccessible(true);
            }
            Object a = method.invoke(event,displayId);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

  • 7
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 29
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 29
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

图王大胜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值