该文主要介绍了 多个虚拟屏同时显示并可交互 的解决方案。主要解决多个 VirtualDisplay 虚拟屏同时显示的问题 和 输入交互的问题。
分析系统级应用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”
创建出来的辅助屏 效果如下:
保证不同的辅助屏上启动不同应用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。
这里实现了一个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[1]) && (event_y positionV2[0]) && (event_x positionV2[1]) && (event_y 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();
}
}
}