1, 分屏功能介绍
Android N 支持多窗口模式,或者叫分屏模式,即在屏幕上可以同时显示多个窗口。 在手机模式下,两个应用可以并排或者上下同时显示,如图 1 所示,屏幕上半部分的窗口是系统的 CLOCK 应用,下半部分是系统设置功能。用户可以拖动两个应用之间的分界线改变两个窗口的大小,放大其中一个应用,同时缩小另一个应用。
在分屏模式下,各个窗口的应用都可以正常运行,但是只能有一个窗口获得焦点,而另外的窗口则属于暂停状态。
Android N的的多窗口框架中,总共包含了三种模式。
Split-Screen Mode: 分屏模式。
Freeform Mode 自由模式:类似于Windows的窗口模式。
Picture In Picture Mode:画中画模式(PIP)
2, APP启动分屏功能
android:resizeableActivity="true|false" 通过AndroidManifest中进行配置,来支持或者禁止分屏功能
重写Activity或者FragmentActivity的onMultiWindowModeChanged方法
@Override
public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
super.onMultiWindowModeChanged(isInMultiWindowMode);
}
当然你还可以指定在自由形状模式时 Activity 的默认大小、位置和最小尺寸:
<activity android:name=".MainActivity">
<layout android:defaultHeight="500dp" android:defaultWidth="600dp" android:gravity="top|end" android:minimalSize="450dp" />
如果想禁止Activity销毁重建,则需要对Activity的configChanges进行如下的配置:
android:configChanges="screenLayout|screenSize|smallestScreenSize|orientation"
进入分屏模式后,两个窗口只有一个会处于onResume的状态,所以如果有视频播放的需求,建议在onStart和onPause方法里边做相关的播放和暂停处理
如果分屏之后,想跳转进入的目标Activity展示在另一半的屏幕中,则需要设置以下Flag:
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT);
在目前博郡的系统进入分屏模式的截图如下:
3, 分屏模式源码分析以及博郡系统进入分屏模式的定制修改
3.1 多窗口原理框架
Android原生多窗口是多Stack方案,即存在多个ActivityStack。ActivityStack是一个抽象的栈,每个栈都有自己的屏幕区域bound和id,Activity是以Task方式组织并放在某一个Stack中的。
比如,Launcher、Recents属于id=HOME_STACK的栈中。
AMS和WMS中对Stack分别用ActivityStack和TaskTack描述,通过StackId来映射。对Task分别用TaskRecord、Task描述,通过TaskId来映射。
每个Activity显示在所属ActivityStack的bound区域内,多个Activity显示在各自ActivityStack的bound区域内,这样就可以实现多窗口。但是FreeForm模式下,
Activity的bound由所属Task决定,而非Stack。多窗口不仅仅是控制Activity放入不同ActivityStack中,同时还要改变Activity的生命周期,
即FocusActivity是resume状态,其他可见Activity是Pause状态,并不会进入Stop状态。 整个系统中只会有一个FocusStack,一个FocusActivity。
用户在哪个Activity中操作,FocusActivity便指向该Activity,FocusStack便指向FocusActivity所属的Stack。注意,画中画模式下,浮层Activity无法成为FocusActivity,故浮层属于的Stack并非FocusStack。
Android为了支持多窗口,在运行时创建了多个Stack,Stack就是类似这里虚拟桌面的作用。 每个Stack会有一个唯一的Id,在ActivityManager.java中定义了这些Stack的Id: 由此我们可以知道,系统中可能会包含这么几个Stack:
【Id:0】Home Stack,这个是Launcher所在的Stack。 其实还有一些系统界面也运行在这个Stack上,例如近期任务
【Id:1】FullScren Stack,全屏的Activity所在的Stack。 但其实在分屏模式下,Id为1的Stack只占了半个屏幕。
**
**
【Id:2】Freeform模式的Activity所在Stack
**
**
【Id:3】Docked Stack 下文中我们将看到,在分屏模式下,屏幕有一半运行了一个固定的应用,这个就是这里的Docked Stack
【Id:4】Pinned Stack 这个是画中画Activity所在的Stack
分屏模式下的Activity的状态。整个屏幕被分成了两个Stack,一个DockedStack,一个FullScreenStack。每个Stack里面有多个Task,每个Task里面又有多个Activity。当我们设置了Stack的大小之后,Stack里面的所有的Task的大小以及Task里面所有的Activity的窗口大小都确定了。假设屏幕的大小是1440x2560,整个屏幕的栈边界就是(0,0,1440,2560)。
在普通模式下启动FirstActivity, SecondActivity通过查看activitiy堆栈的命令
adb shell am stack list
Stack id=1 bounds=[0,0][720,1920] displayId=0 userId=0
taskId=230: com.wx.multiwindow/com.wx.multiwindow.FirstActivity bounds=[0,0][720,1920] userId=0 visible=true topActivity=ComponentInfo{com.wx.multiwindow/com.wx.multiwindow.SecondActivity}
taskId=231: com.wx.multiwindow/com.wx.multiwindow.SecondActivity bounds=[0,0][720,1920] userId=0 visible=true topActivity=ComponentInfo{com.wx.multiwindow/com.wx.multiwindow.SecondActivity}
可以从上面的日志看出FirstActivity, SecondActivity 都在FullScren Stack【Id:1】里面。显示区域为 bounds=[0,0][720,1920] 全屏显示。
在分屏模式下启动FirstActivity, SecondActivity通过查看activitiy堆栈的命令
adb shell am stack list
Stack id=1 bounds=[0,990][720,1920] displayId=0 userId=0
taskId=234: com.wx.multiwindow/com.wx.multiwindow.SecondActivity bounds=[0,990][720,1920] userId=0 visible=true topActivity=ComponentInfo{com.wx.multiwindow/com.wx.multiwindow.SecondActivity}
Stack id=3 bounds=[0,0][720,980] displayId=0 userId=0
taskId=232: com.wx.multiwindow/com.wx.multiwindow.FirstActivity bounds=[0,0][720,980] userId=0 visible=true topActivity=ComponentInfo{com.wx.multiwindow/com.wx.multiwindow.FirstActivity}
从日志可以看出FirstActivity在分屏模式下位于【Id:3】Docked Stack里,显示的区域为bounds=[0,0][720,980]。 SecondActivity在分屏模式下位于
【Id:1】FullScren Stack里。显示的区域为bounds=[0,990][720,1920]。
3.2 博郡系统进入分屏模式的定制修改
用户进入分屏模式是通过长按recents按键,但是在车载系统上面一般是没有recents按键的。所以要进入分屏模式需要定制修改。 通过分析进入分屏的模式的源码可以发现
private boolean onLongPressRecents() { if (mRecents == null || !ActivityManager.supportsMultiWindow(getContext()) || !mDivider.getView().getSnapAlgorithm().isSplitScreenFeasible() || Recents.getConfiguration().isLowRamDevice) { return false; } return mStatusBar.toggleSplitScreenMode(MetricsEvent.ACTION_WINDOW_DOCK_LONGPRESS, MetricsEvent.ACTION_WINDOW_UNDOCK_LONGPRESS); }
这段代码位于
framework/base/packages/SystemUI/src/.../NavigationBarFragment.java
可以看到最终是调用了StatusBar的
boolean toggleSplitScreenMode(int metricsDockAction, int metricsUndockAction)方法。
目前简单的实现就是
在StatusBar的onStart方法里面注册自定义广播
mContext.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { toggleSplitScreen(); } }, new IntentFilter( "com.chinatsp.action.switchsplitmode" ));
在app端发送广播进入分屏模式
sendBroadcast(new Intent( "com.chinatsp.action.switchsplitmode" ));
目前是通过广播的简单的实现进入分屏模式,后续项目考虑修改为AIDL方法。