让我告诉你Multi-Window是什么鬼
在Android N以前,Android是无法像IOS或者传统PC那样舒畅地同时运行多个App的,谷歌曾经在 Android M预览版中提供分屏多任务的功能,但是在正式版中被砍掉了。不过很多厂商在自己的定制ROM中添加了这一功能,如三星、LG在Android 4 的年代就有了这些功能。一些视频应用也支持小窗口播放视频,这主要是利用WindowManager来添加悬浮界面,此外还有一些开源的解决方案,但是也只是在特定的系统版本和高性能的机器上才能支持。但是Android N之后,Google放大招了,Android用户也开始体验一下,同时做多件事情的功能了。在Google3月份放出的Android N预览版中,我们惊喜的发现Android N增加了对同时在一个屏幕上展示多个App的支持。用户可以将屏幕切分成多块,同时进行不同的操作。在不久的将来,我们在日常的手持设备上也可以和PC一样一边看视频,一边接收邮件。在多屏模式下,多个App可以分享同一个屏幕,用户可以随意的拖动切分线,使一个App的显示区域变大而使另外一个App的显示区域变小。多屏模式下,即使用户现在正在操作App A,系统也允许App B继续展示他的内容。
Android N支持以下几种模式的多屏模式:
1.Side-By-Side模式:两个 App 瓜分一个屏幕(可以左右并排,也可以上下并排)
2.One-above-the-other模式:一个 App 浮动在另一个 App 之上
3.Picture-in-picture模式:主要应用在Android TV上,用户在浏览其他 App 的时候,视频可以浮动在屏幕上。
用户有以下两种方法进入多屏模式:
1.点击Menu键进入任务视图,长按App的标题栏,拖动App到屏幕高亮的位置。然后在任务视图中选择另一个App,单击它使得这个App也进入分屏模式。
2.用户长按Menu键,当前App将进入多屏模式,然后打开任务视图让用户选择另外的App一起分享屏幕。
功能的视频演示:1.
2.
Multi-Window下Activty的生命周期
Google并没有改变Multi-Window模式下的Activity的生命周期。在Multi-Window模式下,只有用户最近一个操作的Activity是Actived状态的(该Activity被称为topmost),其他可见的Activity都是Paused状态。当用户再次操作这些Paused状态的Activity的时候,这些Activity的状态将onResume变成Actived,而其他Activity将变成Paused状态。但一个与以前不同的地方在于,系统会给予更高的权限和优先级给这些可见而非Active状态的Activity(相比于非Active又不可见的Activity)。
Activity的生命周期
Demo1
首先我写了一个视频播放器Demo重温一下在Android N以前,Activity的生命周期是怎么样的。我在Activity里用MediaPlayer播放一段视频,在Android 4.4的机器上当Activity进入Stop状态的时候,视频就被强制停止播放并且MediaPlayer被回调onCompletion方法,代表视频播放被系统强制终结了。
首先起一个半透明的AnotherActivity去遮挡MainActivity,MainActivity会回调onPause,但是视频不会停止播放。
然后起一个不透明的AnotherActivity去覆盖MainActivity,MainActivity会回调onPause和onStop,然后回调Mediaplayer的onCompletion,然后结束视频播放。
从上面的一个试验当中可以看出,在Android N以前应用onPause进入Paused状态之后,应用并非真正的停止。但是这时候由于MainActivity已经被遮挡,所以我们应该在onPause中停止视频播放,并记录下播放进度,在用户回到播放器的时候seekTo调整播放进度重新播放视频。
Demo2
这里我尝试在Android N上进入Multi-Window模式,去看看Android N上系统是如何给予非Active状态下的Activity更高的权限。
上图中我打开了两个App,上面的是一个Gmail App,下面这个是一个Demo App。现在这两个App都是进入了分屏模式。我点击了Gmail,浏览了一封邮件,那么此时Gmail就被系统视为Actived状态,而下面的Demo App虽然对用户可见,但是它仍然是处于Paused状态的。接着我点击了系统的Back按钮返回,响应的是上面的Gmail。然后我又点击了下面的Demo App,这时它从Paused状态onResume变成了Actived状态。而上面的Gmail进入了 Paused状态。
注意,这两个App对于用户都是始终可见的,当它们处于Paused状态时,也将比那些后台的处于不可见的App得到更高系统优先级。这个优先级怎么体现呢?两个App进入分屏模式后,一定有一个处于Paused/Actived状态,假如我一直按Back返回,当这个Actived状态App的Task返回栈已经为空时,那么系统将把另外一个可见的App恢复为全屏模式。
对比Demo1,在Android N的分屏模式中,一个App可以在对用户可见的状态下进入Paused状态,所以你的App在处理业务时,应该知道自己什么时候应该真正的暂停。例如一个视频播放器,如果是进入了Andorid N的分屏模式,就不应该在onPaused()回调中暂停视频播放,而应该在onStop()回调中才暂停视频,然后在onStart回调中恢复视频播放。但是如果你的应用是运行在Android N以前,onPause代表Activity被部分遮挡,这是就应该在onPause中暂停视频播放,并在onResume中恢复播放。
你的App如何使用Multi-Window
如果你适配到了Android N,即build.gradle是这样的(截取自官方Demo):
此外,Andorid N提供了少量新的用于定制是否完全支持Multi-Window的 XML 属性。
android:resizeableActivity=["true" | "false"]
在AndroidManifest.xml的<activity> 或者 <Application>下加入以上属性。如果是true,表示这个activity可以自由随意的操作在多屏模式下。如果是false表示不支持多屏模式,这种情况下用户如果尝试去多屏模式,则会全屏显示。在android n下默认为true。特别注意的一点是:一个根Activity的设置属性将会被应用到所有在这个Task栈中的Activity(一个任务栈的根Activity一般是这个任务栈栈底的Activity,即该任务栈第一个启动的Activity)。即当根Activity的android:resizeableActivity属性为true,则在这个Task栈中的所有其他Activity都将是支持多屏模式的。
android:supportsPictureInPicture=["true" | "false"]
在manifest的<activity> 下加入以下属性,支持画中画(特殊的多屏模式)。如果resizeableActivity为false,这个属性被忽略。
<activity android:name=".MyActivity">
<layout android:defaultHeight="500dp" android:defaultWidth="600dp" android:gravity="top|end" android:minimalSize="450dp" /> </activity>在android中,一个activity的布局可以设置以下属性:在Android N中,我们可以向manifest文件中添加layout节点,并设置一些新增加的属性,通过这些属性来设置分屏模式的一些行为,如最小尺寸等。
如果你的应用支持Android N的话,你可以在manifest 中加入以下属性控制你的App的尺寸和布局。如果你的App的SDK版本低于Android N并且没有限定multi-orientation,当用户使用多屏模式的时候,系统将强制去拉伸你的App,不需要额外的配置字段。系统会弹出一个dialog警告用户,此App并不期望你在多屏模式下操作他。当你的应用是 fixed-orientation的时候,在多屏模式下,则系统不会自动拉伸而是直接全屏显示。
Multi-Window提供的API
Google仅仅为Multi-Window添加少量的 API,这些新的API可以让Activity接收到 Mutil-Window 状态改变的通知,以及判断是否处于 Mutil-Window 模式。
Activity.inMultiWindow()
查看一个Activity是否运行在Multi-Window模式。
Activity.inPictureInPicture()
查看一个Activity是否运行在PictureInPicture模式。(PictureInPicture画中画是一种特殊的多窗口模式,若inPictureInPicture()返回true,则inMultiWindow()也将返回true)
Activity.onMultiWindowChanged(boolean inMultiWindow)
当一个Activity进入或者退出多窗口模式的时候,系统将会回调onMultiWindowChanged()方法。参数inMultiWindow为true表示进入多窗口模式,为false表示退出多窗口模式。
Activity.onPictureInPictureChanged(boolean inPictureInPicture)
当一个Activity进入或者退出PictureInPicture模式的时候,系统将会回调onPictureInPictureChanged()方法。参数inPictureInPicture为true表示进入PictureInPicture模式,为false表示退出PictureInPicture模式。
Activity.enterPictureInPicture()
调用此方法可以使一个Activity进入画中画模式,如果Activity不支持画中画模式则无效果。
在Fragment也有对应的方法,比如说Fragment.inMultiWindow()。
使用Intent.FLAG_ACTIVITY_LAUNCH_TO_ADJACENT可以启动一个activity使其与当前Activity一起运行在多屏模式下。注意:这里只是尝试,但这不一定是100%生效的,前一篇博客里也说过,假如新打开的Activity的android:resizeableActivity属性设置为false,就会禁止分屏浏览这个Activity。所以系统只是尝试去以分屏模式打开一个新的Activity,如果条件不满足,将不会生效!
当满足下面的条件,系统会让这两个Activity进入分屏模式:
当前Activity已经进入到分屏模式。
新打开的Activity支持分屏浏览(即android:resizeableActivity=true)。
如果你在一个任务堆栈中启动一个新的 Activity,这个 Activity 会替换屏幕上的当前 Activity ,并且会继承所有它的多窗口属性,如果要启动一个显示在多窗口中的 Activity 需要启动一个新的任务,新任务才能是新窗口。(intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT | Intent.FLAG_ACTIVITY_NEW_Task);)
如何支持拖拽数据传送
Android N基于Multi-Window提供了一个强大的功能,在多屏模式下允许在两个Activity之间进行数据拖拽传输(在此之前,用户只能在一个 Activity 内部拖放数据),Android N Preview SDK中,View已经增加支持Activity之间拖动的API。
android.view.DropPermissions
接收方App所得到的权限列表,这个对象负责授权给App接收一个数据。
View.startDragAndDrop()
startDrag的替代方法。启用跨 Activity 拖放。
需要传递View.DRAG_FLAG_GLOBAL来实现跨Activity拖拽。如果需要将URI权限传递给接收方Activity,还可以根据需要设置View.DRAG_FLAG_GLOBAL_URI_READ或者View.DRAG_FLAG_GLOBAL_URI_WRITE。
View.cancelDragAndDrop()
取消当前进行中的拖拽,只可以被拖拽的发起方调用。
View.updateDragShadow()
可以给当前进行的拖拽设置阴影,由拖拽的发起方调用。
Activity.requestDropPermissions()
请求推送权限。传递URI权限时,需要调用这个方法。传递的内容存储在DragEvent中的ClipData里。返回值为前面的android.view.DropPermissions。
Demo3
Demo3实现了在分屏模式下,把一个Activity中ImageView中保存的内容到另外一个Activity中进行显示。实际应用中,可以还可以传递图片的url或者Bitmap对象。
上图是一个最基本的例子,实现了把MainActivity中的图片保存的内容,拖拽到SecondActivity中。实现步骤如下:
在MainActivity中,发起拖拽。
在SecondActivity中,接收这个拖拽的结果,在ACTION_DROP事件中,把结果显示出来。
这里实现的关键在新增加的startDragAndDrop方法,看下官方的API文档:清楚地提到了,发出的DragEvent能够被所有可见的View对象接收到,所以在分屏模式下,SecondActivity可以监听View的onDrag事件,于是我们监听它!
接着,我们看下DragEvent.ACTION_DROP事件发生的条件:当被拖拽的View的阴影进入到接收方View的坐标区域,如果此时用户松手,那么接收方View就可以接收到这个Drop事件。一目了然,我们通过拖拽ImageView到图上的灰色区域,松手,便可以触发DragEvent.ACTION_DROP,把数据传到SecondActivity中了。
面对更复杂的一些情况,需要调用requestDropPermissions。
带来的问题
1.应用如要支持多窗口,也有一些需要注意的地方,最主要的是分辨率的适配。在多窗口模式下,应用的显示比例不一定是手机屏幕的比例。这里可能会影响到一些代码,比如应用一启动就全局存储一下屏幕的宽高,在N下可能就有问题了,需要开发者做相应的修改。
2.在多屏模式以下特性无法使用:应用无法隐藏状态栏如果他不是全屏模式下。
3.系统忽略android:screenOrientation属性(无法根据屏幕方向来旋转App的界面)。
参考文章
1.
2.
3.
4.
5.