背景: 项目需求机实现双屏效果,一个面向观众结果的展示,一个面向工作人员进行二次确认的操作。
讨论的方案
1、两台单独的设备使用Socket长连接方式进行轻量级的数据通信(由于网络无法保证,故放弃)
2、两台单独的设备通过串口相连,使用串口进行通信(经测试可行)
3、双屏异显
双屏异显的实现方式
1、Presentation 此方式比较简单,是系统自带的方法,是个特殊的Dialog,代码如下所示
/**
*display 需要显示的屏幕
*outerContext 不是activity的context是与display绑定的context,用于加载资源使用
**/
class NormalPresentation(var outerContext: Context,display: Display) :
Presentation(outerContext, display), AnkoLogger {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.dialog_normal)//绑定副屏显示的布局
}
override fun onStart() {
super.onStart()
}
//消失时调用
override fun onStop() {
super.onStop()
}
}
这样一个自定义的副屏显示界面就实现了,那么如何在Activity中实现呢?下面继续。 先看下下面代码用的类和结果MediaRouter和MediaRouter.Callback。 MediaRouter.Callback是用来监听屏幕是否有变化的,例如有新屏幕进来或者屏幕的移除等操作(物理上的连接)。
从上面的代码我们知道参数有一个display,就是屏幕,所以我们需要获取外接的屏幕。
mDisplayManager =
this@NormalActivity.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
displays = mDisplayManager.displays //得到显示器数组
使用DisplayManager就获取到了设备连接的所有的屏幕,返回的是个数组,0是主屏幕。 既然获取到了屏幕下面直接显示就可以了。
val route = mMediaRouter!!.getSelectedRoute(MediaRouter.ROUTE_TYPE_LIVE_VIDEO) //获取首选的设备和Display
// val route = mMediaRouter!!.getSelectedRoute(MediaRouter.ROUTE_TYPE_LIVE_AUDIO)
val presentationDisplay = route?.presentationDisplay
// val presentationDisplay = displays[1] //0是主屏
mPresentation = NormalPresentation(this, presentationDisplay!!)
这样就创建完了,至于显示和去除功能,直接调用show()和dismiss()方法就可以了。
2、使用WindowManager加载副屏 查看WindowManager的addview()的具体实现
WindowManagerGlobal#addView实现具体的逻辑
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow)
{ ....
try {
// root是ViewRootImpl的实例 root.setView(view, wparams, panelParentView); } catch (
RuntimeException e) { .... } }
里面是有参数Display的,所以只需要将display传进去就可以xias显示,当然这里涉及到了窗口的显示次序即layoutParams.type 使用WindowManager需要系统级别的弹窗layoutParams.type 23以下是2003以上是2038,并且需要在代码中进行判断
if (!Settings.canDrawOverlays(this)) {
var intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivityForResult(intent, 1);
}
如果没有权限手动打开,代码实现。
public class HelpHandPresentation {
private WindowManager.LayoutParams layoutParams;
private Display secondDisplay;
private Context secondDisplayContext;
private View view;
private WindowManager windowManager;
//获取屏幕和屏幕的context
public void addPresentation(Context paramContext) {
Display display = ((MediaRouter) paramContext.getSystemService(Context.MEDIA_ROUTER_SERVICE)).
getSelectedRoute(MediaRouter.ROUTE_TYPE_LIVE_VIDEO).getPresentationDisplay();
this.secondDisplay = display;
if (display != null) {
/****
* Return a new Context object for the current Context but whose resources are adjusted to match the metrics of the given Display.
* 为当前Context返回一个新的Context对象,但其资源被调整以匹配给定Display的指标
*/
this.windowManager = (WindowManager) paramContext.createDisplayContext(display).getSystemService(Context.WINDOW_SERVICE);
this.secondDisplayContext = paramContext.createDisplayContext(this.secondDisplay);
return;
}
}
//副屏显示
@RequiresApi(api = Build.VERSION_CODES.O)
@SuppressLint("WrongConstant")
public View addView(int paramInt) {
this.view = View.inflate(this.secondDisplayContext, paramInt, null);
this.layoutParams = new WindowManager.LayoutParams(2003, 3, 3);
if (Build.VERSION.SDK_INT >= 23) {
this.layoutParams.type = 2038;
} else {
this.layoutParams.type = 2003;
}
this.windowManager.addView(this.view, this.layoutParams);
return this.view;
}
//移除副屏显示
public void removeLayoutView() {
this.windowManager.removeView(this.view);
}
}
具体实现方式
helpHandPresentation = HelpHandPresentation()
helpHandPresentation.addPresentation(this)
//可以使用view进行副屏界面的显示和变化
view = helpHandPresentation.addView(R.layout.dialog_normal)
使用WindowManager实现需要自己控制副屏的显示和移除,不如Presentation。
3、RK DualScreen(资料较少,只是针对特定的机型做的开发)
这个资料较少且有版本限制,故暂时不做讨论。
双屏异显的的缺点
1、副屏无法触发点击事件,故只可以做展示使用。
2、系统对副屏的分辨率可能有特殊需求,例如迈冲科技只支持1080P的设备。
综合以上的问题,最终选择的是方案二,通过串口进行两个设备的通信。