Android双屏异显

2,485 阅读3分钟

背景: 项目需求机实现双屏效果,一个面向观众结果的展示,一个面向工作人员进行二次确认的操作。

讨论的方案

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的设备。

综合以上的问题,最终选择的是方案二,通过串口进行两个设备的通信。