Android8.0多窗口调研

时间:2024-04-04 10:37:46

Android8.0多窗口调研

一、概述

Android8.0上面原生的多窗口功能支持四种模式:全屏、分屏、画中画、FreeForm模式。多窗口主要涉及ActivityManagerServiceWindowManagerServiceInput三个模块。以下分析基于Android8.0代码。

二、原理框架

Android原生多窗口是多Stack方案,即存在多个ActivityStackActivityStack是一个抽象的栈,每个栈都有自己的屏幕区域boundidActivity是以Task方式组织并放在某一个Stack中的。比如,LauncherRecents属于id=HOME_STACK的栈中。8.0主要涉及以下:

HOME_STACK_IDFULLSCREEN_WORKSPACE_STACK_IDFREEFORM_WORKSPACE_STACK_IDDOCKED_STACK_IDPINNED_STACK_IDRECENTS_STACK_IDASSISTANT_STACK_ID

AMSWMS中对Stack分别用ActivityStackTaskTack描述,通过StackId来映射。对Task分别用TaskRecordTask描述,通过TaskId来映射。

每个Activity显示在所属ActivityStackbound区域内,多个Activity显示在各自ActivityStackbound区域内,这样就可以实现多窗口。但是FreeForm模式下,Activitybound由所属Task决定,而非Stack。多窗口不仅仅是控制Activity放入不同ActivityStack中,同时还要改变Activity的生命周期,即FocusActivityresume状态,其他可见ActivityPause状态,并不会进入Stop状态。

整个系统中只会有一个FocusStack,一个FocusActivity。用户在哪个Activity中操作,FocusActivity便指向该ActivityFocusStack便指向FocusActivity所属的Stack。注意,画中画模式下,浮层Activity无法成为FocusActivity,故浮层属于的Stack并非FocusStack

进入/退出多窗口按以下逻辑框架顺序进行处理:

step1.调整ActivityStack

step2.调整Task

step3.调整Activity堆栈,调整WMSAPPWindowToken堆栈

step4.调用Activity生命周期函数

step5.添加WindowWMS

step6.过度动画及应用窗口显示

三、关键函数

移栈:

AMS.moveTaskToStack()

AMS.moveTaskToDockedStack()

AMS.moveTopActivityToPinnedStack()

分屏相互切换:

AMS.swapDockedAndFullscreenStack()

真正切换task(重点)

TaskRecord.reparent()//8.0新增分屏task移动都在此函数执行

调整栈大小:

AMS.resizeDockedStack()

AMS.resizePinnedStack()

ActivityStackSupervisor.resizeStackUncheckedLocked()

Activity生命周期:

ActivityStack.resumeTopActivityInnerLocked()

Activity启动:

ActivityStarter.startActivityUnchecked()

四、多窗口变更通知和查询

Activity.isInMultiWindowMode()调用该方法以确认 Activity是否处于多窗口模式。Activity.isInPictureInPictureMode()调用该方法以确认 Activity是否处于画中画模式。Activity.onMultiWindowModeChanged()Activity进入或退出多窗口模式时系统将调用此方法。在Activity进入多窗口模式时,系统向该方法传递true值,在退出多窗口模式时,则传递false值。Activity.onPictureInPictureModeChanged()Activity进入或退出画中画模式时系统将调用此方法。在Activity进入画中画模式时,系统向该方法传递true值,在退出画中画模式时,则传递false值。每个方法还有Fragment版本,例如 Fragment.isInMultiWindowMode()

五、源码分析

1、分屏模式

进入分屏有两种入口:

AMS.startActivityFromRecents()//在任务栏中拖动一个分屏应用到顶部

AMS.moveTaskToDockedStack()//长按底部任务按键

简单分析下AMS.startActivityFromRecents()

进入分屏之前PhoneActivity属于id=1FullScreenStack),任务栏Recents属于id=0HomeStack),FocusStack指向HomeStack。进入分屏相当于启动PhoneActivity,并将其放到上半区域Stack中,上半区域为DockedStackid=3),即将PhoneActivityid=1FullScreenStack)移到id=3DockedStack中,最后将HomeStackboundresize,处在下半屏。注意分屏后FocusStack指向上半区域DockedStack。整个时序如下所示。

Android8.0多窗口调研


AMS.moveTaskToDockedStack()

在支持分屏应用界面长按底部任务按键,进入分屏模式。相当于把PhoneActivityid=1移到id=3DockedStack中,同时resizeHomeStack。类似前者,不做详细分析。

2、画中画模式

画中画即置顶ActivityActivity的窗口永远位于所有窗口之上,所属的Stack自然位于所有Stack的上面,这类特殊的Stack称为PinnedStackid=4)。PinnedStack无法成为FocusStackActivity也无法成为FocusActivity

如何启动画中画,只需在AndroidManifest中声明Activity时添加android:supportsPictureInPicture="true"android:resizeableActivity="true”属性,并调用新方法 Activity.enterPictureInPictureMode()。时序图如下:

Android8.0多窗口调研



ps:reparent没做详细展出

PinnedStack为非FocusablestackActivity为非focusactivityresumeFocusedStackTopActivityLocked()只会将stack堆栈中FocusStackfocusactivity进行resume,其他任何activity均为pausedstoped状态,所以画中画Activity是处于paused状态的.

3FreeForm模式

FreeForm模式下并不存在与之对应的多个Stack,而是只有一个FreeFormStackstackId=2)存放了多个Task。这种模式下决定Activity显示位置的不是Stackbound,而是Taskbound,即每个Task都有自己的bound

FreeForm模式下ActivityRecents中启动,调用的接口为AMS.startActivityFromRecents(),其时序跟从任务栏拖动一个应用进入分屏差不多,只是此处ActivityFreeFormStack中启动,其他基本流程基本一致,不再详细分析。

4、窗口resize/拖动

在分屏模式下,拖动中间bar条改变分屏大小,中间bar条是SystemUI添加到系统一个名为“DockedStackDivider”的浮窗,接收触摸事件,根据MotionEventY值动态调用ActivityManagerService.resizeDockedStack()来改变DockedStackbound大小,完成窗口大小调整。

FreeForm模式下的窗口缩放和拖拽跟分屏模式不一样,没有SystemUI添加的bar浮窗,但是在FreeForm模式下DecorView与子ContentView间插入了一个DecorCaptionView,代码如下所示。

Android8.0多窗口调研

//FreeForm模式下config发生改变,会触发onConfigurationChanged,调用到此处

窗口调整跟拖拽便是在DecorCaptionView中根据触摸事件检测开启的,调用的开启函数便是WindowManagerService.startMovingTask(IWindowwindow, float startX, float startY)。就拿拖拽来说,用户按住FreeFormActivity的窗口边缘然后移动,此拖动过程分为三步:

 step1.DecorCaptionView检测触摸事件,发现是拖拽或窗口调整,那么跨进程调用WindowManagerService.startMovingTask()开启Task移动;

 step2.WMS.startMovingTask()会创建一个TaskPositionerTaskPositioner会注册一个InputChannelInputManager中,此后的触摸事件便会发送到TaskPositioner中来;

  step3.TaskPositioner接收move事件,计算WindowRect然后调用AMS.resizeTask(inttaskId, Rect bounds, int resizeMode)来更新Activity窗口bound

六、区别OS

os的多窗口使用的也是类似多stack方案,通过调整stack大小,同时显示多个应用。

和原生的区别在于,os在切换窗口大小的时候需要加载不同的布局,系统强制发送onConfigurationChanged给应用,应用根据config加载不同布局。原生在config变化会销毁activity,然后根据新config重新启动新页面。os修改系统逻辑不重启应用

七、总结

Android8.0针对多窗口进一步完善,并且将taskRecord相关的切换放到reparent新方法中统一处理。os的方案在8.0上面需要针对新特新重新修改。