Multi Window 是Android N中引入的一个重要的新功能。 本文介绍跟Multi Window 相关的系统实现及应用编程。
一. Multi Window 简介
Android N 允许多个APP在屏幕上同时显示。 如下图所示, 两个app同时显示在屏幕上:
在不同的设备上,Multi window 的功能也略有不同:
1. 在手机上,Android N 提供split-screen mode. 也就是屏幕上只能同时显示两个APP , 左右或者上下, 如上图所示.用户可以拖拽中间的的分割线划分窗口的大小。
如何进入split screen mode :
a. 用户在full screen mode 下,长按resent key , 会进入split-screen mode.
b. 正常界面下,按resent key, 按住一个activity,按提示拖拽至屏幕上方, 进入split-screen mode。
2. 在android TV 上,Android N 提供picture- in- picture mode,即用户可以变看video边浏览其他应用。用户可以将播放Video的窗口放在屏幕一角,然后去浏览其他app。PIP 窗口的大小为240x135 dp。可以显示在屏幕四个角的任意一角并且在所有窗口中之上。如下图。
3. 大屏幕设备例如平板可以选择使用freeform mode。也就是用户可以任意改变窗口大小。这种模式下,既可以使用split –screen mode, 也可以使用freeform mode。
二. Multi window 生命周期
Multi window 模式下activity 的生命周期和正常模式下是相同的。 同时显示的几个activity中,最上层的是刚刚和用户有过交互的activity。 这个activity 是在resume 状态。其他的activity 尽管也完全显示在屏幕上,但是是处于pause 状态。只有用户在屏幕上点击某个pause 状态的activity 时, 这个activity 才会切换到resume 状态,原来在resume状态的activity转入pause状态。
要注意的一点是,在multi window 模式下,video 播放器app 可能完全显示在屏幕上,但是状态是pause。 这种情况下,app最好在pause 状态也能继续播放video , 进入stop状态后再停止播放。同样,在 onStart 时恢复video播放, 而不是onResume。
一个app在进入 multi window mode, 或者app 尺寸发生变化,或者从multi window mode 返回全屏的时候, 系统会通知app configuration change 。 相应的处理跟以前configuration change 一样。
如果APP想处理configuration change, 在manifest文件声明的时候, 最好使用以下声明, 注意加上screenLayout, 因为在屏幕由全屏转为multiwindow时, 可能会导致screenlayout变化,比如从normal到small.
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
如果窗口尺寸变大, app 不能及时地绘制新的图形,那么变化的部分会临时用窗口背景色(windowBackground)填充。
三. App 配置是否支持multi window
App 可以在AndroidManifest.xml 文件中配置是否支持multi window. 注意的一点是,如果task中的root activity 支持multi window, 那么这个task 中的所有activity 都会支持multi window。
固定方向的app(只能横屏或者竖屏显示) 不支持multiwindow。
具体在AndroidManifest.xml中的设置如下:
1. 在application 或者activity下,disable 或者enable multi window, 默认值为true:
android:resizeableActivity=["true" | "false"]
2. 设置layout. 可以在activity的layout 元素下设置以下值。
• android:defaultWidth
在freeform mode下,默认的宽度。
• android:defaultHeight
在freeform mode下,默认的高度。
• android:gravity
在freeform mode下, activity初次启动时在屏幕上的位置。比如水平居中,垂直居中等。具体值可以参见:
https://developer.android.com/reference/android/view/Gravity.html?hl=mr
• android:minHeight, android:minWidth
在split-screen 和 freeform mode 下, activity的最小宽度和高度.如果用户在split-screen mode 下拖拽分割符,导致一个activity小于要求的最小尺寸,系统会自动剪切activity。
下面是一个设置的例子 :
<activity android:name=".MyActivity"> <layout android:defaultHeight="500dp" android:defaultWidth="600dp" android:gravity="top|end" android:minHeight="450dp" android:minWidth="300dp" /> </activity>
四. Android N 提供了一些新的activity API:
• Activity.isInMultiWindowMode()
是否在multi window mode 下
• Activity.isInPictureInPictureMode()
是否在picture-in-picture mode下。如果isInPictureInPictureMode返回true, 那么isInMultiWindowMode也会返回true.
• Activity.onMultiWindowModeChanged()
只要activity 进入或者退出multi window, 系统都会调用该方法。传入参数为true, 则是进入multi window mode. False, 退出multi window mode.
• Activity.onPictureInPictureModeChanged()
只要activity 进入或者退出picture-in-picture mode系统都会调用该方法。传入参数为true, 则是进入。 False, 退出picture-in-picture mode。
Fragment也提供了以上同样的API。
• Activity.enterPictureInPictureMode()
调用该函数,activity 会进入picture-in-picture 模式。
五. 如何以multi window Mode 启动一个activity
1. 当启动一个activity 时,在intent中加入以下flag:
intent.FLAG_ACTIVITY_LAUNCH_TO_ADJACENT
使用该flag 表示你希望新启动的activity 在屏幕上挨着当前的activity。那么如果当前设备是在split-screen模式下,这个新的activity 会与现在的activity 共享屏幕,即新的activity显示在屏幕的另一部分上。但是需要注意的是,系统并不保证一定能够实现这种效果。
如果当前屏幕并不在split-screen模式下,这个flag会被忽略,没有任何效果。
2. 新启动的activiity的尺寸和在屏幕上位置: ActivityOptions.setLaunchBounds()。
如果是在freeform模式下,在启动新的activit时, 可以调用函数ActivityOptions.setLaunchBounds()指定新启动的activiity的尺寸和在屏幕上位置。如果当前设备没在freeform模式下,该函数没有任何效果。
注意,如果一个activity是被启动在与当前activity相同的task中 ,那么 这个新启动的activity会覆盖当前activity所在屏幕上的位置, 并且继承所有的前一个activity的mulit-window 属性。如果想要启动一个activity在不同的window 上,必须在一个新的task中启动activity.
六. 对拖拽的支持(drag and drop)
如果两个activity同时显示在屏幕上,用户可以从一个activity拖拽数据到另一个activity中。
Android N的SDK中拓展了android.view包的内容,支持跨app的数据拖拽。主要有以下一些:
• android.view.DropPermissions
Token 对象负责指定这个权限给接受拖拽数据的app。
• View.startDragAndDrop()
替代View.startDrag()的新方法,View.startDrag()建议不再使用。 这个函数enable 跨activity的拖拽。传送一个新的flag View.DRAG_FLAG_GLOBAL. 如果需要给出URI permission 到接受方,传送另一个新的flag: View.DRAG_FLAG_GLOBAL_URI_READ 或者 View.DRAG_FLAG_GLOBAL_URI_WRITE
• View.cancelDragAndDrop()
Cancels 当前进行中的拖操作。只用被发起拖拽操作的app调用。
• View.updateDragShadow()
替换当前进行的拖拽操作的拖拽阴影(不知道是不是这么翻译, 就是拖拽时的一个缩略图)。只能由发起拖拽的app 调用。
• Activity.requestDragAndDropPermissions()
请求一个permission , 允许传输content URIs。 这个URI 包含在DragEven的ClipData 中。
七. Framework 实现
简单介绍一下framework的实现。Android L在ActivityManagerService 和WindowManagerService中引入了Stack的概念, 这个stack在多窗口模式下扮演了重要的角色,一个窗口对应一个stack.
Multi window必然涉及到activity 显示尺寸的变化,以前每个窗口都对应一个Configuration信息记录当前系统的方向,屏幕尺寸,屏幕密度等情况,系统根据这个configuration信息去配置相应的显示资源。Android N上除了以前的configuration 信息,系统还为每个窗口都新增加了一个参数mOverrideConfig, 这个记录了当前状态下窗口的configuration信息。例如当前屏幕为竖屏并在split-screen模式下,mConfig记录的是全屏的尺寸,如方向为纵向,而mOverrideConfig会记录当前实际显示的屏幕信息, 例如屏幕size会缩小一半,方向也会变为横向。系统会根据 mOverrideConfig中的信息配置显示资源。
参考文档:
https://developer.android.com/preview/features/multi-window.html?hl=mr
https://developer.android.com/preview/features/picture-in-picture.html