本文是多年前在Intel Baytrail 平台上所做过的一个项目的思路总结。当时设备上有同时支持VGA/HDMI显示设备(很Intel吧,跟PC的接口很像吧),需求是在Android 上支持VGA/HDMI两个屏幕同时显示,并且同时需要显示运行两个应用程序在不同的显示屏幕下,简单的就是需要你在HDMI显示屏上看视频的同时,在VGA显示屏幕上操作一个应用。本文简单介绍了实现方式,至于代码不方便放出来,再说Android N都出来了,对这个功能已经接近支持了。本文将按照以下的思路来组织.
在Android 的Framework框架当中,在多display显示方面,已经有比较完整的框架支持,虽然目前可能还不太完善,本文档将简单介绍下android的多display 框架,力争能分析清楚当前框架,找出不足之处,最终能够提出完善当前框架的方案。因此,本文档将分成以下三个部分:
- ◆现有多Display 框架分析。
- ◆现有多Display 框架的不足。
- ◆完善多Display 框架的方案。
现有多Display 框架分析
在android 4.4.4的代码当中,我们已经能够看到android已经有了大量关于多Display支持的代码,并且当前android也已经支持通过WIFI Display将图像投影到一个WIFI设备,也增加了一个Presentation的特殊Dialog允许将该Dialog绘制在另外一个Display设备上。本文将从以下四个方面分析当前android版本对多Display框架的支持:
- ◆JAVA FW应用部分,需要提醒注意的是,这个应用部分并不是指应用程序,而是指android FW与应用程序相连接的那一部分。
- ◆JAVA GUI FW部分,主要是DisplayManagerService 以及WindowManagerService对Display当中窗口的管理。
- ◆ Native GUI FW部分,主要是SurfaceFlinger当中对Display当中窗口的绘制,以及图像输出
- ◆Android Input子系统对多Display的支持。
由于android多Display与android GUI系统紧密耦合,所以在讨论android多Display之前,我们先大致了解下android GUI框架,在Android framework GUI系统当中,最核心最重要的是系统提供的三个系统service,分别是ActivityManagerService与WindowManagerService.这两个Java层面的Service, 顾名思义,这两个service将分别管理activity跟window。以及一个SurfaceFlinger这么一个Native层的Service。这个service主要负责对所有界面的compose操作。下图是android GUI框架的一个简图,简单描述了应用程序,JAVA GUI FW,Native GUI FW 这三者之间的关系:
稍微解释下上面这张图,应用程序端的三个组件是由Framework当中的三个service来分别负责管理的,Activity由AMS负责管理,其在AMS中对应的是一个ActivityRecord对象,AMS将管理其生命周期。Window则虚拟对应WMS中的一个WindowState.说是“虚拟对应”是因为Window跟WMS并没有直接的关系,只存在逻辑上的关系。WMS将管理窗口的Z轴以及每个窗口显示的大小。mDoctorView与SF中的Layer也是虚拟对应的关系,归纳一点就是Layer为mDoctorView提供显示buffer,从而让mDoctorView这棵递归树能将自己所有的子view都画到这个buffer当中,最终SF将compose其管理的所有的Layer,最终显示到屏幕之上。
WindowManagerService与SurfaceFlinger在android GUI框架当中是分工协作的,WindowManagerService管理逻辑关系,包括Z-Order, 显示区域大小等等,SurfaceFlinger则根据WindowManagerService的输入负责具体绘制以及compose。
◆应用部分
在应用程序部分,android最主要是在WindowImpl.java以及ContexImpl.java这两个类
中增加了一个类型为Display的成员变量mDisplay。下面我们分别从Activity创建,以及应用往WindowManagerService添加window这两个流程分析下应用部分针对多Display的改动。
Activity的创建
先补充一点Activity创建的一些背景知识,Activity是android当中应用程序的一个基本
单元,抛开其中的复杂流程而言,由上面所简单介绍到的android GUI 框架,应用程序端与android 负责管理activity的系统服务ActivityManagerService之间大致关系如下图:
简单的描述下:每个应用进程都会存在一个ActivityThread对象作为与AMS通信的接口(ActivityThread的作用不仅仅于此),应用进程当中的所有Activity都是通过它与远端的AMS建立联系,比如Activity A需要创建Activity B, A会通过ActivityThread向AMS发送Binder调用请求,AMS收到请求之后会进行一系列的执行过程,最终AMS会通知Activity B所在的ActivityThread来Create或者Resume Activity B.
有了以上简单简单的背景之后,再回过头来看看应用程序端Activity的创建:
1, ActivityThread handleLauncheActivity 函数,这个函数是响应AMS端往应用程序端的binder调用。
2, performLaunchActivity函数,这个函数被handleLauncheActivity调用,这个函数当中会创建一个新的Activity对象,然后调用createBaseContextForActivity来为该Activity对象创建一个Context上下文对象。
3, createBaseContextForActivity函数需要我们重点关注下
- private Context createBaseContextForActivity(ActivityClientRecord r,
- final Activity activity) {
- ContextImpl appContext = ContextImpl.createActivityContext(this, r.packageInfo, r.token);
- appContext.setOuterContext(activity);
- // For debugging purposes, if the activity's package name contains the value of
- // the "debug.use-second-display" system property as a substring, then show
- // its content on a secondary display if there is one.
- Context baseContext = appContext;
- String pkgName = SystemProperties.get("debug.second-display.pkg");
- if (pkgName != null && !pkgName.isEmpty()
- && r.packageInfo.mPackageName.contains(pkgName)) {
- DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
- for (int displayId : dm.getDisplayIds()) {
- if (displayId != Display.DEFAULT_DISPLAY) {
- Display display = dm.getRealDisplay(displayId, r.token);
- baseContext = appContext.createDisplayContext(display);
- break;
- }
- }
- }
- return baseContext;
- }
这个函数中首先调用ContextImpl.createActivityContext来创建一个ContextImpl对象,追进去看了之后发现ContextImpl中的mDisplay比赋值成null,这个函数下半部分看起来是一个测试多Display的功能,大致作用是将根据” debug.second-display.pkg”这个系统属性定义的包名的activity放到其他的display当中,其调用的createDisplayContext(display)函数,会让ContextImpl中的mDisplay被赋值,这为我们完善多Display提供参考。Activity的创建过程先到这里,至此,我们所提到的WindowImpl.java 以及ContexImpl.java这两个类当中ContexImpl类中的mDisplay在android默认情况下被赋值成NULL.接下来我们继续看下应用往WindowManagerService添加window的过程。
添加Window
Activity创建完成之后,应用程序还只是有一个壳,具体需要显示,绘制,还是要有Window的参与。上面已经有一张图相信能够简单描述了应用程序,WindowManagerService, SurfaceFlinger之间的联系了。下图将描述应用与WindowManagerService之间的联系接口:
1,Activity.javaattach 函数,在这个函数当中,会使用到我们上面所提到创建的ComtextImpl对象调用:
- context.getSystemService(Context.WINDOW_SERVICE),mToken, mComponent.flattenToString(),(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
去创建一个一个WindowManagerImpl对象。
2, 在ContexImpl.java中我们看到:
- registerService(WINDOW_SERVICE, new ServiceFetcher() {
- Display mDefaultDisplay;
- public Object getService(ContextImpl ctx) {
- Display display = ctx.mDisplay;
- if (display == null) {
- if (mDefaultDisplay == null) {
- DisplayManager dm = (DisplayManager)ctx.getOuterContext().
- getSystemService(Context.DISPLAY_SERVICE);
- mDefaultDisplay = dm.getDisplay(Display.DEFAULT_DISPLAY);
- }
- display = mDefaultDisplay;
- }
- return new WindowManagerImpl(display);
- }});
如果ContextImpl对象中的mDisplay为null的话,将使用默认的display为WindowManagerImpl中的mDisplay成员赋值。
3, 添加窗口将使用WindowManagerImpl中的addView函数,该函数使用WindowManagerImpl本身对象的mDisplay成员作为参数,最终将window所在的Display信息带到WindowManagerService当中。
至此,应用部分就分析完成,应用部分最终的目的就在于为每个window指定了一个具体的Display信息。这个信息将为窗口在SurfaceFlinger中绘制提供帮助。
◆ JAVA GUI FW部分
在JAVA GUI FW部分,主要涉及就是JAVAFW当中对Display的管理,以及WindowManagerService对Display中window的管理两个部分。我们先看看对Display的管理。
DisplayManagerService作为JAVA FW层中的一个系统服务,在系统的启动阶段由SystemServer启动,其启动之后的第一件事情,就尝试从SurfaceFlinger当中获取到系统默认的display 与HDMI display,可选择的创建VirturlDisplay, WifiDisplay, OverlayDisplay.从目前看起来,这三个可选的Display的实现方式差不多,在SurfaceFlinger端都是对应一个VirturlDisplay,通过传递一个Surface到SurfaceFlinger端,从而让SurfaceFlinger在这个surface上绘制一份整个系统的图像。
由于我们需要实现的双屏是完整的双屏,所以我们需要在这里做一些改动,需要能从SurfaceFlinger当中拿到第二个屏的信息。并且在DisplayManagerService保存。但是如果第二个屏是HDMI Display的话,这个改动目前可以忽略掉。
WindowManagerService作为JAVA FW层中的一个系统服务,主要管理系统中所有的Window,在WMS中,有一个对应的WindowState对象。
1, addWindow,该函数在WindowManagerService当中,由上文提到的WindowManagerImpl中的addView函数调用到,并且将window所在的Display作为参数带到WMS当中。
2, 在addWindow函数,WMS首先找到窗口所在的display,然后将窗口加到Display中的windowlist当中。
3, WindowState通过设置将自己所在Display的LayerStack值设置成自己的LayerStack值,告知SurfaceFlinger自己所在Display。
mSurfaceControl.setLayerStack(mLayerStack);
其他的窗口管理与非多Display系统差别不大,可能需要改动的是要让多Display系统中,每个Display当中都需要有一个foucs状态的窗口。需要有一个处于Resume状态当中的Activity。
◆ Native GUI FW部分
SurfaceFlinger作为Android在native层比较重要的一个系统服务,主要作用是compose所有的layer,将其绘制输出到显示设备当中,也就是物理Display当中。大致如下图:
上图很简单,SurfaceFlinger需要利用HWC compose 属于每个Display的layer,并且将其输出到具体的DisplayDivice当中。WindowManagerService会在JAVA FW中指定每个Layer属于哪一个Display.
在当前SurfaceFlinger当中,支持默认Display与HDMI Display两个具体的物理Display.如果需要再加一个物理Display,可能再做一些改动。
◆ Android Input子系统对多Display的支持
从目前的代码中看起来,貌似input子系统还不支持多Display系统。
现有多Display 框架的不足
通过以上部分,我们简单分析了现有的android多Display框架,现在我们简单梳理下目前多Display系统稍显不足的地方:
1, 应用端默认都是使用默认的Display来存放窗口,没有接口可以让应用程序*选择。
2, AMS与WMS当中,需要支持每个Display当中都要有一个Resume状态的Activity,一个Focus状态的Window.
3, 在Input子系统中需要增加对多Display的支持,这样能让用户在每个Display上都能够跟系统交互。
4, SurfaceFlinger当中目前只有默认的Display与HDMI Display两种物理Display,如果再加一个I2C接口的显示屏,可能再需要改动。从目前来看HDMI Display能满足我们的需求,这部分可以暂时略过。
完善多Display 框架的方案
根据上文所提到的目前多Display框架的不足,我们需要针对性的做出一定的改进:
1, 在应用端,需要提供接口让应用自己选择自己需要放置窗口的Display 设备或者我们根据一些policy强制将一些应用窗口放置到指定的Display当中。
2, 我们需要改动AMS与WMS,让AMS中针对每一个Display都保留一个处于Resume状态的Activity, 让WMS为每一个Display保留处于Focus状态的Window.
3, Input 子系统根据具体需求,可能需要的工作量会比较大,比如如果需要支持的第二个Display同样具有触摸屏这样一个输入设备,这就需要做比较大的改动。
4, SurfaceFlinger当中需要支持任意多的物理Display,还需要继续做一些调研。目前优先级稍微低一点。
以上只是初步设想的方案,还没有进行过可行性验证,接下来我会进行可行性验证,并且完善出更加详细的方案。
◆ 显示部分,确定显示一个窗口界面在某一个Display中,大致如下图所示:
解释下上图所提到的几个变量:
1, JAVA FW应用程序端指定其应用ContextImpl的Display。
2, JAVA FW WindowManagerService则会获取对应Display的layerstack将其放置在WindowStateAnimator当中,并且将值设置到SurfaceFlinger当中与之对应的Layer中。
3, SurfaceFlinger根据Layer当中的layerStack成员获知需要将该Layer绘制到具体哪一个Display当中。
根据分析代码,1)部分需要改动。2)部分当中,由于HDMIDisplay默认是Mirror模式,所以也需要一点改动。3)部分当中,对HDMI显示设备以及默认支持,暂时不需要任何改动。
针对1)的改动:
private ContextcreateBaseContextForActivity(ActivityClientRecord r,
final Activity activity) {
ContextImpl appContext = ContextImpl.createActivityContext(this,r.packageInfo, r.token);
appContext.setOuterContext(activity);
// For debugging purposes, if theactivity's package name contains the value of
// the "debug.use-second-display" system property as asubstring, then show
// its content on a secondary display if there is one.
Context baseContext = appContext;
String pkgName =SystemProperties.get("debug.second-display.pkg");
++ pkgName = “com.android.gallery3d”
if (pkgName != null && !pkgName.isEmpty()
&&r.packageInfo.mPackageName.contains(pkgName)) {
DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
for (int displayId : dm.getDisplayIds()) {
if (displayId !=Display.DEFAULT_DISPLAY) {
Display display =dm.getRealDisplay(displayId, r.token);
baseContext =appContext.createDisplayContext(display);
break;
}
}
}
return baseContext;
}
该改动只是简单的将指定的package 放置到非默认的显示屏当中。
针对2)的改动:
由于DisplayManagerService当中默认将HDMI的显示屏作为一个Mirror的显示屏,所以其对应的LayerStack值与DefaultDisplay中的LayerStack值相等。
之前提到过,由于目前AMS/WMS分别只有一个处于focus状态的Activity与Window.为了不影响主屏上正常显示,我们必须让主屏跟第二个屏都各自拥有一个处于focus状态的Activity与Window。
1, 目前AMS管理两个ActivityStack,一个为Home Stack,Launch app活在其中,另外一个为App Stack,所有的其他应用的activity活在其中。修改之后再增加一个ActivityStack,专门用来放置需要放置在第二屏上的activity。
1, 修改了AMS中当前默认流程:
当前AMS在启动一个新的全屏的activity之后,会默认认为原来的activity已经处于不可见状态,那么会通知SurfaceFlinger下次绘制的时候不需要再绘制原来的Activity。这样会造成如果启动一个新的Activity到第二屏。那么主屏上的所有界面都不会再被绘制。所以修改掉了默认流程:如果启动的是到第二屏的应用,则原本针对Home Stack与App Stack中activity隐藏的流程不再继续走下去。
◆ Input 子系统 Android Input子系统对多Display的支持
InputReader在派发input事件的时候,已经会带上display的参数,只不过目前使用的是默认的default display。
1) 对touch屏的支持,目前代码中看起来,EventHub在上报event事件时会告知device ID,只需要将touch屏的device ID与Display关联在一起就能够完美支持。
2) 对鼠标的支持,鼠标输入事件支持双屏主要需要修改以下两个方面:
a) 需要告知PointerController主屏与第二屏的尺寸大小。目前PointerController只知道主屏的大小。
b) 需要修改将Event Hub上报的鼠标移动事件转换成android 鼠标事件的计算方式,具体就是原来系统当中,鼠标事件被限制在主屏的大小范围当中,修改之后需要根据相应的阙值将Event hub上报的鼠标事件转换成跟Display相关的android鼠标事件。
|
c) 鼠标图标的绘制,根据上面所提到的,只需要修改鼠标图标的窗口在SurfaceFlinger当中Layer的layerStack变量就能让其绘制在指定的Display当中。
最后的Framework改成的结构是这样的,目前Android N上原生的实现框架已经搭得差不太多了。
本文是多年前在Intel Baytrail 平台上所做过的一个项目的思路总结。当时设备上有同时支持VGA/HDMI显示设备(很Intel吧,跟PC的接口很像吧),需求是在Android 上支持VGA/HDMI两个屏幕同时显示,并且同时需要显示运行两个应用程序在不同的显示屏幕下,简单的就是需要你在HDMI显示屏上看视频的同时,在VGA显示屏幕上操作一个应用。本文简单介绍了实现方式,至于代码不方便放出来,再说Android N都出来了,对这个功能已经接近支持了。本文将按照以下的思路来组织.
在Android 的Framework框架当中,在多display显示方面,已经有比较完整的框架支持,虽然目前可能还不太完善,本文档将简单介绍下android的多display 框架,力争能分析清楚当前框架,找出不足之处,最终能够提出完善当前框架的方案。因此,本文档将分成以下三个部分:
- ◆现有多Display 框架分析。
- ◆现有多Display 框架的不足。
- ◆完善多Display 框架的方案。
现有多Display 框架分析
在android 4.4.4的代码当中,我们已经能够看到android已经有了大量关于多Display支持的代码,并且当前android也已经支持通过WIFI Display将图像投影到一个WIFI设备,也增加了一个Presentation的特殊Dialog允许将该Dialog绘制在另外一个Display设备上。本文将从以下四个方面分析当前android版本对多Display框架的支持:
- ◆JAVA FW应用部分,需要提醒注意的是,这个应用部分并不是指应用程序,而是指android FW与应用程序相连接的那一部分。
- ◆JAVA GUI FW部分,主要是DisplayManagerService 以及WindowManagerService对Display当中窗口的管理。
- ◆ Native GUI FW部分,主要是SurfaceFlinger当中对Display当中窗口的绘制,以及图像输出
- ◆Android Input子系统对多Display的支持。
由于android多Display与android GUI系统紧密耦合,所以在讨论android多Display之前,我们先大致了解下android GUI框架,在Android framework GUI系统当中,最核心最重要的是系统提供的三个系统service,分别是ActivityManagerService与WindowManagerService.这两个Java层面的Service, 顾名思义,这两个service将分别管理activity跟window。以及一个SurfaceFlinger这么一个Native层的Service。这个service主要负责对所有界面的compose操作。下图是android GUI框架的一个简图,简单描述了应用程序,JAVA GUI FW,Native GUI FW 这三者之间的关系:
稍微解释下上面这张图,应用程序端的三个组件是由Framework当中的三个service来分别负责管理的,Activity由AMS负责管理,其在AMS中对应的是一个ActivityRecord对象,AMS将管理其生命周期。Window则虚拟对应WMS中的一个WindowState.说是“虚拟对应”是因为Window跟WMS并没有直接的关系,只存在逻辑上的关系。WMS将管理窗口的Z轴以及每个窗口显示的大小。mDoctorView与SF中的Layer也是虚拟对应的关系,归纳一点就是Layer为mDoctorView提供显示buffer,从而让mDoctorView这棵递归树能将自己所有的子view都画到这个buffer当中,最终SF将compose其管理的所有的Layer,最终显示到屏幕之上。
WindowManagerService与SurfaceFlinger在android GUI框架当中是分工协作的,WindowManagerService管理逻辑关系,包括Z-Order, 显示区域大小等等,SurfaceFlinger则根据WindowManagerService的输入负责具体绘制以及compose。
◆应用部分
在应用程序部分,android最主要是在WindowImpl.java以及ContexImpl.java这两个类
中增加了一个类型为Display的成员变量mDisplay。下面我们分别从Activity创建,以及应用往WindowManagerService添加window这两个流程分析下应用部分针对多Display的改动。
Activity的创建
先补充一点Activity创建的一些背景知识,Activity是android当中应用程序的一个基本
单元,抛开其中的复杂流程而言,由上面所简单介绍到的android GUI 框架,应用程序端与android 负责管理activity的系统服务ActivityManagerService之间大致关系如下图:
简单的描述下:每个应用进程都会存在一个ActivityThread对象作为与AMS通信的接口(ActivityThread的作用不仅仅于此),应用进程当中的所有Activity都是通过它与远端的AMS建立联系,比如Activity A需要创建Activity B, A会通过ActivityThread向AMS发送Binder调用请求,AMS收到请求之后会进行一系列的执行过程,最终AMS会通知Activity B所在的ActivityThread来Create或者Resume Activity B.
有了以上简单简单的背景之后,再回过头来看看应用程序端Activity的创建:
1, ActivityThread handleLauncheActivity 函数,这个函数是响应AMS端往应用程序端的binder调用。
2, performLaunchActivity函数,这个函数被handleLauncheActivity调用,这个函数当中会创建一个新的Activity对象,然后调用createBaseContextForActivity来为该Activity对象创建一个Context上下文对象。
3, createBaseContextForActivity函数需要我们重点关注下
- private Context createBaseContextForActivity(ActivityClientRecord r,
- final Activity activity) {
- ContextImpl appContext = ContextImpl.createActivityContext(this, r.packageInfo, r.token);
- appContext.setOuterContext(activity);
- // For debugging purposes, if the activity's package name contains the value of
- // the "debug.use-second-display" system property as a substring, then show
- // its content on a secondary display if there is one.
- Context baseContext = appContext;
- String pkgName = SystemProperties.get("debug.second-display.pkg");
- if (pkgName != null && !pkgName.isEmpty()
- && r.packageInfo.mPackageName.contains(pkgName)) {
- DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
- for (int displayId : dm.getDisplayIds()) {
- if (displayId != Display.DEFAULT_DISPLAY) {
- Display display = dm.getRealDisplay(displayId, r.token);
- baseContext = appContext.createDisplayContext(display);
- break;
- }
- }
- }
- return baseContext;
- }
这个函数中首先调用ContextImpl.createActivityContext来创建一个ContextImpl对象,追进去看了之后发现ContextImpl中的mDisplay比赋值成null,这个函数下半部分看起来是一个测试多Display的功能,大致作用是将根据” debug.second-display.pkg”这个系统属性定义的包名的activity放到其他的display当中,其调用的createDisplayContext(display)函数,会让ContextImpl中的mDisplay被赋值,这为我们完善多Display提供参考。Activity的创建过程先到这里,至此,我们所提到的WindowImpl.java 以及ContexImpl.java这两个类当中ContexImpl类中的mDisplay在android默认情况下被赋值成NULL.接下来我们继续看下应用往WindowManagerService添加window的过程。
添加Window
Activity创建完成之后,应用程序还只是有一个壳,具体需要显示,绘制,还是要有Window的参与。上面已经有一张图相信能够简单描述了应用程序,WindowManagerService, SurfaceFlinger之间的联系了。下图将描述应用与WindowManagerService之间的联系接口:
1,Activity.javaattach 函数,在这个函数当中,会使用到我们上面所提到创建的ComtextImpl对象调用:
- context.getSystemService(Context.WINDOW_SERVICE),mToken, mComponent.flattenToString(),(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
去创建一个一个WindowManagerImpl对象。
2, 在ContexImpl.java中我们看到:
- registerService(WINDOW_SERVICE, new ServiceFetcher() {
- Display mDefaultDisplay;
- public Object getService(ContextImpl ctx) {
- Display display = ctx.mDisplay;
- if (display == null) {
- if (mDefaultDisplay == null) {
- DisplayManager dm = (DisplayManager)ctx.getOuterContext().
- getSystemService(Context.DISPLAY_SERVICE);
- mDefaultDisplay = dm.getDisplay(Display.DEFAULT_DISPLAY);
- }
- display = mDefaultDisplay;
- }
- return new WindowManagerImpl(display);
- }});
如果ContextImpl对象中的mDisplay为null的话,将使用默认的display为WindowManagerImpl中的mDisplay成员赋值。
3, 添加窗口将使用WindowManagerImpl中的addView函数,该函数使用WindowManagerImpl本身对象的mDisplay成员作为参数,最终将window所在的Display信息带到WindowManagerService当中。
至此,应用部分就分析完成,应用部分最终的目的就在于为每个window指定了一个具体的Display信息。这个信息将为窗口在SurfaceFlinger中绘制提供帮助。
◆ JAVA GUI FW部分
在JAVA GUI FW部分,主要涉及就是JAVAFW当中对Display的管理,以及WindowManagerService对Display中window的管理两个部分。我们先看看对Display的管理。
DisplayManagerService作为JAVA FW层中的一个系统服务,在系统的启动阶段由SystemServer启动,其启动之后的第一件事情,就尝试从SurfaceFlinger当中获取到系统默认的display 与HDMI display,可选择的创建VirturlDisplay, WifiDisplay, OverlayDisplay.从目前看起来,这三个可选的Display的实现方式差不多,在SurfaceFlinger端都是对应一个VirturlDisplay,通过传递一个Surface到SurfaceFlinger端,从而让SurfaceFlinger在这个surface上绘制一份整个系统的图像。
由于我们需要实现的双屏是完整的双屏,所以我们需要在这里做一些改动,需要能从SurfaceFlinger当中拿到第二个屏的信息。并且在DisplayManagerService保存。但是如果第二个屏是HDMI Display的话,这个改动目前可以忽略掉。
WindowManagerService作为JAVA FW层中的一个系统服务,主要管理系统中所有的Window,在WMS中,有一个对应的WindowState对象。
1, addWindow,该函数在WindowManagerService当中,由上文提到的WindowManagerImpl中的addView函数调用到,并且将window所在的Display作为参数带到WMS当中。
2, 在addWindow函数,WMS首先找到窗口所在的display,然后将窗口加到Display中的windowlist当中。
3, WindowState通过设置将自己所在Display的LayerStack值设置成自己的LayerStack值,告知SurfaceFlinger自己所在Display。
mSurfaceControl.setLayerStack(mLayerStack);
其他的窗口管理与非多Display系统差别不大,可能需要改动的是要让多Display系统中,每个Display当中都需要有一个foucs状态的窗口。需要有一个处于Resume状态当中的Activity。
◆ Native GUI FW部分
SurfaceFlinger作为Android在native层比较重要的一个系统服务,主要作用是compose所有的layer,将其绘制输出到显示设备当中,也就是物理Display当中。大致如下图:
上图很简单,SurfaceFlinger需要利用HWC compose 属于每个Display的layer,并且将其输出到具体的DisplayDivice当中。WindowManagerService会在JAVA FW中指定每个Layer属于哪一个Display.
在当前SurfaceFlinger当中,支持默认Display与HDMI Display两个具体的物理Display.如果需要再加一个物理Display,可能再做一些改动。
◆ Android Input子系统对多Display的支持
从目前的代码中看起来,貌似input子系统还不支持多Display系统。
现有多Display 框架的不足
通过以上部分,我们简单分析了现有的android多Display框架,现在我们简单梳理下目前多Display系统稍显不足的地方:
1, 应用端默认都是使用默认的Display来存放窗口,没有接口可以让应用程序*选择。
2, AMS与WMS当中,需要支持每个Display当中都要有一个Resume状态的Activity,一个Focus状态的Window.
3, 在Input子系统中需要增加对多Display的支持,这样能让用户在每个Display上都能够跟系统交互。
4, SurfaceFlinger当中目前只有默认的Display与HDMI Display两种物理Display,如果再加一个I2C接口的显示屏,可能再需要改动。从目前来看HDMI Display能满足我们的需求,这部分可以暂时略过。
完善多Display 框架的方案
根据上文所提到的目前多Display框架的不足,我们需要针对性的做出一定的改进:
1, 在应用端,需要提供接口让应用自己选择自己需要放置窗口的Display 设备或者我们根据一些policy强制将一些应用窗口放置到指定的Display当中。
2, 我们需要改动AMS与WMS,让AMS中针对每一个Display都保留一个处于Resume状态的Activity, 让WMS为每一个Display保留处于Focus状态的Window.
3, Input 子系统根据具体需求,可能需要的工作量会比较大,比如如果需要支持的第二个Display同样具有触摸屏这样一个输入设备,这就需要做比较大的改动。
4, SurfaceFlinger当中需要支持任意多的物理Display,还需要继续做一些调研。目前优先级稍微低一点。
以上只是初步设想的方案,还没有进行过可行性验证,接下来我会进行可行性验证,并且完善出更加详细的方案。
◆ 显示部分,确定显示一个窗口界面在某一个Display中,大致如下图所示:
解释下上图所提到的几个变量:
1, JAVA FW应用程序端指定其应用ContextImpl的Display。
2, JAVA FW WindowManagerService则会获取对应Display的layerstack将其放置在WindowStateAnimator当中,并且将值设置到SurfaceFlinger当中与之对应的Layer中。
3, SurfaceFlinger根据Layer当中的layerStack成员获知需要将该Layer绘制到具体哪一个Display当中。
根据分析代码,1)部分需要改动。2)部分当中,由于HDMIDisplay默认是Mirror模式,所以也需要一点改动。3)部分当中,对HDMI显示设备以及默认支持,暂时不需要任何改动。
针对1)的改动:
private ContextcreateBaseContextForActivity(ActivityClientRecord r,
final Activity activity) {
ContextImpl appContext = ContextImpl.createActivityContext(this,r.packageInfo, r.token);
appContext.setOuterContext(activity);
// For debugging purposes, if theactivity's package name contains the value of
// the "debug.use-second-display" system property as asubstring, then show
// its content on a secondary display if there is one.
Context baseContext = appContext;
String pkgName =SystemProperties.get("debug.second-display.pkg");
++ pkgName = “com.android.gallery3d”
if (pkgName != null && !pkgName.isEmpty()
&&r.packageInfo.mPackageName.contains(pkgName)) {
DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
for (int displayId : dm.getDisplayIds()) {
if (displayId !=Display.DEFAULT_DISPLAY) {
Display display =dm.getRealDisplay(displayId, r.token);
baseContext =appContext.createDisplayContext(display);
break;
}
}
}
return baseContext;
}
该改动只是简单的将指定的package 放置到非默认的显示屏当中。
针对2)的改动:
由于DisplayManagerService当中默认将HDMI的显示屏作为一个Mirror的显示屏,所以其对应的LayerStack值与DefaultDisplay中的LayerStack值相等。
之前提到过,由于目前AMS/WMS分别只有一个处于focus状态的Activity与Window.为了不影响主屏上正常显示,我们必须让主屏跟第二个屏都各自拥有一个处于focus状态的Activity与Window。
1, 目前AMS管理两个ActivityStack,一个为Home Stack,Launch app活在其中,另外一个为App Stack,所有的其他应用的activity活在其中。修改之后再增加一个ActivityStack,专门用来放置需要放置在第二屏上的activity。
1, 修改了AMS中当前默认流程:
当前AMS在启动一个新的全屏的activity之后,会默认认为原来的activity已经处于不可见状态,那么会通知SurfaceFlinger下次绘制的时候不需要再绘制原来的Activity。这样会造成如果启动一个新的Activity到第二屏。那么主屏上的所有界面都不会再被绘制。所以修改掉了默认流程:如果启动的是到第二屏的应用,则原本针对Home Stack与App Stack中activity隐藏的流程不再继续走下去。
◆ Input 子系统 Android Input子系统对多Display的支持
InputReader在派发input事件的时候,已经会带上display的参数,只不过目前使用的是默认的default display。
1) 对touch屏的支持,目前代码中看起来,EventHub在上报event事件时会告知device ID,只需要将touch屏的device ID与Display关联在一起就能够完美支持。
2) 对鼠标的支持,鼠标输入事件支持双屏主要需要修改以下两个方面:
a) 需要告知PointerController主屏与第二屏的尺寸大小。目前PointerController只知道主屏的大小。
b) 需要修改将Event Hub上报的鼠标移动事件转换成android 鼠标事件的计算方式,具体就是原来系统当中,鼠标事件被限制在主屏的大小范围当中,修改之后需要根据相应的阙值将Event hub上报的鼠标事件转换成跟Display相关的android鼠标事件。
|
c) 鼠标图标的绘制,根据上面所提到的,只需要修改鼠标图标的窗口在SurfaceFlinger当中Layer的layerStack变量就能让其绘制在指定的Display当中。
最后的Framework改成的结构是这样的,目前Android N上原生的实现框架已经搭得差不太多了。