基于Android R版本分析
分屏
和PIP模块一样,Split Screen也属于多窗口机制中的一种场景,在SystemUI模块中的Divider管理着所有关于分屏的对象;
- DividerView:分屏分割线,分屏显示界面;
- SplitScreenTaskOrganizer:分屏Task组织者,分屏逻辑;
在Android原生系统中,分屏功能是通过Recents(近期任务)功能进入:
Stack & Task 层次接口
bash
nobo@nobo-System-Product-Name:~/dupz1019/SystemUI/log$ adb shell am stack list
Stack id=4 bounds=[0,1498][1440,2960] displayId=0 userId=0
configuration={1.0 ?mcc?mnc [zh_CN_#Hans,en_US] ldltr sw369dp w411dp h369dp 560dpi smll hdr widecg land finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 1498 - 1440, 2960) mAppBounds=Rect(0, 1498 - 1440, 2792) mWindowingMode=split-screen-secondary mDisplayWindowingMode=fullscreen mActivityType=undefined mAlwaysOnTop=undefined mRotation=ROTATION_0} s.10}
taskId=1085: com.android.gallery3d/com.android.gallery3d.app.GalleryActivity bounds=[0,1498][1440,2960] userId=0 visible=true topActivity=ComponentInfo{com.android.messaging/com.android.messaging.ui.conversationlist.ConversationListActivity}
taskId=1083: com.android.launcher3/com.android.launcher3.uioverrides.QuickstepLauncher bounds=[0,485][1440,2960] userId=0 visible=true topActivity=ComponentInfo{com.android.messaging/com.android.messaging.ui.conversationlist.ConversationListActivity}
taskId=1084: com.android.messaging/com.android.messaging.ui.conversationlist.ConversationListActivity bounds=[0,1498][1440,2960] userId=0 visible=true topActivity=ComponentInfo{com.android.messaging/com.android.messaging.ui.conversationlist.ConversationListActivity}
Stack id=3 bounds=[0,0][1440,1464] displayId=0 userId=0
configuration={1.0 ?mcc?mnc [zh_CN_#Hans,en_US] ldltr sw369dp w411dp h369dp 560dpi smll hdr widecg land finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1440, 1464) mAppBounds=Rect(0, 171 - 1440, 1464) mWindowingMode=split-screen-primary mDisplayWindowingMode=fullscreen mActivityType=undefined mAlwaysOnTop=undefined mRotation=ROTATION_0} s.10}
taskId=1086: com.android.dialer/com.android.dialer.main.impl.MainActivity bounds=[0,0][1440,1464] userId=0 visible=true topActivity=ComponentInfo{com.android.dialer/com.android.dialer.main.impl.MainActivity}
ConfigurationContainer
scala
public abstract class ConfigurationContainer<E extends ConfigurationContainer> {}
ConfigurationContainer包含了具有覆盖configuration并以层次结构组织的类的通用逻辑;
ConfigurationContainer是一个Container,类似WindowContainer容器,ConfigurationContainer容器用于存放Configuration对象;
ConfigurationContainer有两个子类:
- WindowContainer
- WindowProcessController:保存一些进程相关的信息,是WMS和AMS沟通的媒介;
ConfigurationContainer虽然没有参与层次结构的构建,但是却借助了WindowContainer构建起来的层次结构,从上到下进行了Configuration的分发;
adb shell dumpsys activity a:打印全屏状态下的ActivityRecord的Configuration信息;
Configuration
kotlin
public final class Configuration implements Parcelable, Comparable<Configuration> {}
这个类描述了所有可能影响应用程序检索的资源的设备配置信息,包括用于指定的配置选项(语言区域列表和缩放)以及设备的配置(如输入模式、屏幕大小和屏幕方向);
Configuration中包含了一个变量:WindowConfiguration,WindowConfiguration持有了窗口状态相关的配置信息,作为Configuration的一部分,跟随Configuration一起进行分发;
WindowConfiguration
kotlin
public class WindowConfiguration implements Parcelable, Comparable<WindowConfiguration> {}
这个类用于描述窗口配置或者状态信息的类,用于那些直接或间接包含窗口的容器;
WindowConfiguration Param
yaml
configuration={1.0 ?mcc?mnc [zh_CN_#Hans,en_US] ldltr sw369dp w411dp h369dp 560dpi smll hdr widecg land finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 1498 - 1440, 2960) mAppBounds=Rect(0, 1498 - 1440, 2792) mWindowingMode=split-screen-secondary mDisplayWindowingMode=fullscreen mActivityType=undefined mAlwaysOnTop=undefined mRotation=ROTATION_0} s.10}
mBounds
java
private Rect mBounds = new Rect(); // mBounds=Rect(0, 1498 - 1440, 2960)
代表了Container的边界。例如Task,调用WindowConfiguration的getBounds()方法,返回的就是这个bounds,这个bounds代表的就是Task的边界;
mAppBounds
yaml
private Rect mAppBounds; // mAppBounds=Rect(0, 1498 - 1440, 2792)
代表的是App的可用边界,对比mBounds,区别在于mAppBounds的计算考虑到了insets,需要考虑StatusBar和NavigationBar的高度,而mBounds的边界不考虑insets;
同时,mAppBounds一个比较重要的作用就是,计算Configuration的screenWidthDp和screenHeightDp;
- screenWidthDp = w 411dp
- screenHeightDp = h 369dp
- densityDpi = 560dpi
mWindowingMode & mDisplayWindowingMode
arduino
/** The current windowing mode of the configuration. */
private @WindowingMode int mWindowingMode; // split-screen-secondary
/** The display windowing mode of the configuration */
private @WindowingMode int mDisplayWindowingMode; // fullscreen
- WindowingMode:代表了当前container处于哪一种多窗口模式;
- DisplayWindowingMode:代表了当前container所在的Display处于哪一种多窗口模式里;
mActivityType
arduino
/** The current activity type of the configuration. */
private @ActivityType int mActivityType; // undefined
代表了当前container的Activity类型;
Activity Type | Desc | Value |
---|---|---|
ACTIVITY_TYPE_UNDEFINED | 一般container刚刚创建时使用的模式,在后续使用中需要选择一种非ACTIVITY_TYPE_UNDEFINED的Activity类型 | 0 |
ACTIVITY_TYPE_STANDARD | 标准模式,大部分App都是类型 | 1 |
ACTIVITY_TYPE_HOME | Launcher类型的App,如google原生Launcher中的ActivityRecord都是这种类型 | 2 |
ACTIVITY_TYPE_RECENTS | Recent或者Overview类型,系统中只有一个Activity是这个类型:RecentsActivity - Android Q 是通过判断packageName = com.android.systemui.recents来进行赋值的 - Android R 是通过判断对应Component是否为Recent组件且是否与Recent组件共享相同的uid | 3 |
ACTIVITY_TYPE_ASSISTANT | Assistant类型,目前接触的不多,多和语音助手服务有关 | 4 |
ACTIVITY_TYPE_DREAM | Dream类型,壁纸类型相关的Activity | 5 |
mAlwaysOnTop
arduino
/** The current always on top status of the configuration. */
private @AlwaysOnTop int mAlwaysOnTop; // undefined
用来声明当前container是否总是处于顶层;
ALWAYS Type | Desc | Value |
---|---|---|
ALWAYS_ON_TOP_UNDEFINED | 当前没有定义Always on top | 0 |
ALWAYS_ON_TOP_ON | 当前在此配置中处于开启状态 | 1 |
ALWAYS_ON_TOP_OFF | 当前该配置为关闭状态 | 2 |
我们看一下ALWAYS_ON_TOP的判断条件:
kotlin
/**
* Returns true if the container associated with this window configuration is always-on-top of
* its siblings.
* @hide
*/
public boolean isAlwaysOnTop() {
if (mWindowingMode == WINDOWING_MODE_PINNED) return true; // PIP模式
if (mActivityType == ACTIVITY_TYPE_DREAM) return true; // 壁纸场景
if (mAlwaysOnTop != ALWAYS_ON_TOP_ON) return false; // 没有开启置顶开关
return mWindowingMode == WINDOWING_MODE_FREEFORM
|| mWindowingMode == WINDOWING_MODE_MULTI_WINDOW;
}
这几种场景下,需要有优先级的排序,PIP优先级最高,其次Dream。针对除这两种以外的场景,需要满足两个条件:
- mAlwaysOnTop = ALWAYS_ON_TOP_ON
- mWindowingMode = WINDOWING_MODE_FREEFORM || WINDOWING_MODE_MULTI_WINDOW
mRotation
arduino
private int mRotation = ROTATION_UNDEFINED; // ROTATION_0
当前container的旋转角度,只和当前container所在的display相关,和container所在层级结构无关;
一般情况下,各级的container都是直接继承Display的rotation,但是ActivityRecord中针对尺寸兼容模式有额外的逻辑;
流程分析
Divider init & start
- 创建SplitScreenTaskOrganizer实例,SplitScreenTaskOrganizer继承自TaskOrganizer,TaskOrganizer:接收ActivityTaskManager/WindowManager委派任务控制的接口,SplitScreenTaskOrganizer扩展了TaskOrganizer,实现分屏功能的任务控制逻辑;
- 创建DisplayController实例,DisplayController用于处理WMS的显示变化,通过调用WMS的registerDisplayWindowListener()方法向底层注册WMS 监听窗口变化的事件;
- 创建SystemWindows实例和WindowManagerProxy实例,WindowManagerProxy中创建了SyncTransactionQueue实例,SyncTransactionQueue用于序列化同步WindowContainerTransaction和相应Callback的助手类;
- 创建DividerWindowManager实例,用于管理Docked Stack;
- 调用addDisplayWindowListener()方法,监听Window的变化,这个listener是SystemUI内部的Listener,IDisplayWindowListener接收到回调之后,会调用该Listener向SystemUI上报;
在init过程中,通过DisplayController的registerDisplayWindowListener()方法向底层注册了IDisplayWindowListener监听器, 注册完成之后,就会直接进行遍历Listener集合,遍历RootWindowContainer下的所有child,将对应的child的displayId通知给listener;
- SystemUI应用通过IDisplayWindowListener的onDisplayAdded()回调方法,获取到的对应的displayId值,然后根据对应的DisplayId获取一些对应的参数值(context、DisplayLayout等),然后通过OnDisplaysChangedListener类型的监听器向SystemUI上报;
- 在OnDisplaysChangedListener的onDisplayAdded()方法中,创建SplitDisplayLayout实例,SplitDisplayLayout用于处理分屏相关的内部显示布局;
- 然后判断该版本是否支持分屏功能,不支持则直接退出;
- 然后调用SplitScreenTaskOrganizer的init()方法,初始化SplitScreenTaskOrganizer实例;
- 然后通过SplitDisplayLayout的resizeSplits()方法设置了Primary Screen和Secondary Screen的Bound和Smallest(最小的) ScreenWidthDp属性值,通过WindowContainerTransaction的applyTransaction方式进行处理这些操作;
- 注册任务堆栈侦听器,该侦听器在任务堆栈更改时获取回调;
registerOrganizer
我们知道,SplitScreenTaskOrganizer的过程分为两部分:创建和初始化。在初始化过程中,向底层注册了TaskOrganizer,即SplitScreenTaskOrganizer,SplitScreenTaskOrganizer绑定了两种WindowingMode:
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
然后通过TaskOrganizerController机制,通过onTaskAppeared()方式为Primary Stack和Secondary Stack创建对应的SurfaceControl实例,用于显示图像内容;不过onTaskAppeared()方法是在Task创建成功之后,在addTask()的时候才会被回调;
createRootTask
在执行完registerOrganizer之后,会等待Task的创建,只有在创建Task完成之后,才会创建对应的SurfaceControl实例;
相同的,调用两次createRootTask()方法分别创建Primary类型和Secondary类型的Task;
- 根据默认的DisplayId来获取对应的DisplayContent,然后再根据获取到的DisplayContent实例获取默认的TaskDisplayArea实例,DefaultTaskDisplayArea指在专用于应用程序窗口的显示器上获取默认显示区域;
- 调用DefaultTaskDisplayArea的createStack()方法,在该方法中首先会针对ActivityType以及WindowingMode的有效性进行判断;
- 调用getNextStackId()获取新建Stack的Id值,然后调用createStackUnchecked()继续执行;
- 然后创建ActivityStack实例,用于承载后续分屏过程中的Task;
- 调用ActivityStack的setWindowingMode()方法,将创建好的ActivityStack和WindowingMode进行了绑定,然后触发onConfigurationChanged机制;
- 最后通过getTaskInfo()方式从ActivityStack中获取到RunningTaskInfo实例,然后将RunningTaskInfo实例和ActivityStack实例以键值对的方式保存到mLastSentTaskInfos集合中进行维护;
SplitScreenTaskOrganizer # onTaskAppeared
当Primary Stack 和 Secondary Stack创建成功之后,就会调用TaskOrganizer的onTaskAppeared()回调方法,上报ActivityStack创建完成的结果,而TaskOrganizer对应的就是SplitScreenTaskOrganizer;
这个过程比较简单,为创建好的Primary和Secondary类型的RunningTaskInfo实例创建对应的SurfaceControl,然后调用SurfaceControl.Transaction中的apply()清除应用事务状态,并使其可作为新事务使用;
stack list
scss
Stack id=3 bounds=[0,0][1440,1464] displayId=0 userId=0
configuration={1.0 ?mcc?mnc [zh_CN_#Hans,en_US] ldltr sw369dp w411dp h369dp 560dpi smll hdr widecg land finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1440, 1464) mAppBounds=Rect(0, 171 - 1440, 1464) mWindowingMode=split-screen-primary mDisplayWindowingMode=fullscreen mActivityType=undefined mAlwaysOnTop=undefined mRotation=ROTATION_0} s.10}
taskId=3: unknown bounds=[0,0][1440,1464] userId=0 visible=false
Stack id=4 bounds=[0,1498][1440,2960] displayId=0 userId=0
configuration={1.0 ?mcc?mnc [zh_CN_#Hans,en_US] ldltr sw369dp w411dp h369dp 560dpi smll hdr widecg land finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 1498 - 1440, 2960) mAppBounds=Rect(0, 1498 - 1440, 2792) mWindowingMode=split-screen-secondary mDisplayWindowingMode=fullscreen mActivityType=undefined mAlwaysOnTop=undefined mRotation=ROTATION_0} s.10}
taskId=4: unknown bounds=[0,1498][1440,2960] userId=0 visible=false
这个就是Divider init&start完成之后,stack list的情况,其中包括了两个StackId = 3 和StackId = 4的ActivityStack,这个就是通过createRootStack创建的;
start SplitScreen
Divider和SplitScreenTaskOrganizer创建和初始化完成之后,就等待Android设备通过Recents来开启分屏功能;
start Primary SplitScreen
在点击分屏之后,会将当前对应的应用进程Task移动到Stack = 3(Primary Screen)中;
-
TaskShortcutFactory响应分屏功能的点击事件;
-
首先先创建ActivityOptions实例,ActivityOptions是用于Activity转场动画,在其中配置了WindowingMode属性和SplitScreenCreateMode属性;
- WindowingMode = WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
- SplitScreenCreateMode = SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
-
然后就是调用ActivityManagerWrapper的startActivityFromRecents()方法进行分屏开启;
在开启主分屏时,主屏一般是已经存在的Activity的Task,不需要重新启动,可以通过RootWindowContainer的anyTaskForId()方法找到对应的task;
- 通过开启分屏的应用的taskId获取到对应的Task实例,同时也通过getLaunchStack()方法寻找对应的ActivityStack,而这个Stack的类型应该为SPLIT_SCREEN_PRIMARY类型的Stack;
- 通过Task的reparent()方法重置应用Task所属的Stack,其中传入的Stack就是通过getLaunchStack()获取到的ActivityStack;
-
针对Primary Stack,会调用ActivityStartController的postStartActivityProcessingForLastStarter()方法记录的最后一个起始者,重新评估是否可以移除;
-
最终继续执行ActivityTaskManagerService的continueWindowLayout()方法;
continueWindowLayout
当上面的所有工作执行完成之后,调用ATMS的continueWindowLayout()继续窗口系统的绘制和显示逻辑;
我们知道,在上述过程中,主副屏task的状态变化和子Task操作(reparent、addChild)都会回调通知SplitScreenTaskOrganizer,回调SplitScreenTaskOrganizer的onTaskInfoChanged()方法;
handleTaskInfoChanged
这个过程也比较简单,主要涉及两组变量:
-
Changed before
- primaryWasEmpty:用于描述changed之前primary screen topActivity的情况
- secondaryWasEmpty:用于描述changed之前secondary screen topActivity的情况
-
Changed after
- primaryIsEmpty:用于描述changed之后primary screen topActivity的情况
- secondaryIsEmpty:用于描述changed之后secondary screen topActivity的情况
然后通过这些状态信息判断分屏状态;