Android从N(7.0)开始正式支持了多窗口(分屏),所谓多窗口,通俗来讲,就是同一时刻,用户可以看到一个以上的应用界面并与之进行交互,这就跟我们平常使用PC的操作系统一样,可以极大地提高操作效率,这对于大屏幕的手持设备来讲更为重要。

本文将讲解分屏的架构设计,旨在从一个比较高的角度将原生的分屏原理讲的通俗透彻。因本文不会对一些Android的基础知识展开描述,请自行补足Activity、Window、Task等概念,另外对于分屏相关的APK开发文档,请参考官方网站。


分屏的交互

要理解分屏的设计,首先要清楚它的交互(这个功能要做成什么样),主要的交互如下:

  1. 只有可分屏的应用才能进入分屏模式,假设A和B是可以分屏的两个应用
  2. 对于A和B,可通过在多任务中拖动其到上半屏进入分屏模式
  3. 下半屏可通过点击另一个分屏应用进入
  4. 对于A或B,切换到应用内的另一个界面,依然是分屏模式
  5. 假设A跟B已经分屏,按Home键退出后,点击可分屏的C应用,此时将是A和C分属上下屏
  6. 拖动中间的调节杠可以调整上下屏的大小

综上,我们大概可以得出如下基本结论:

  1. 应用可以向系统表达自己是否支持分屏,若支持则必须遵循一些规则
  2. 系统要标识分屏应用在屏幕上的位置及尺寸
  3. 分屏应用的位置和尺寸要能动态改变
  4. 系统应该为应用尺寸的改变提供必要的支持,以支撑其绘制出适配新尺寸的UI

关于第1点,相当于界定责任方。因为不管系统以什么样的方式来实现分屏,势必要求应用根据规则做一些适配,而应用当然可以选择不遵循,那么系统也就不提供其进入分屏的机会;而一旦应用声明支持分屏,就必须进行适配并遵守规则。因此这点只是进入分屏的一个限定条件,跟具体的实现无关,不做过多讨论,接下来只讨论后面几点。


Android对界面的抽象

在接下来的分析之前,我们必须先了解Android如何抽象界面的管理,Android自诞生以来基本都维持了一样的管理方式,任何界面相关的特性必然都建立在这套基础之上,抽象出主干的东西有助于我们更好地理解问题。

对应用开发者来说,最熟悉的莫过于Activity,Activity就是界面最小的抽象单位(但可以不显示),显而易见最简单的组织方式不就是Activity放置在屏幕上,所以屏幕也得有一层抽象,我们暂时称其Display,像这样:

-Display-A -Activity-A -Activity-B -Activity-C

但是这样就够了吗,在正常的操作情景中,当我们完成一件事时有时需要多个界面,这多个界面共同构成了一个操作序列,我们希望它是一个整体,每次当我们回到这个场景时都应该保持原样,显然上述的方案不足以满足这个需求,因为Activity之间不存在联系。

于是应该有一种抽象,将多个Activity联系起来组成一个整体,这就是Task,看起来是这样:

-Display-A -Task-A  -Activity-A2  -Activity-A1 -Task-B  -Activity-B1

如果所有的界面都是全屏,并且都遵循一样的策略,到这里似乎已经没问题了,早期的Android版本也确实是这样的设计。我们说系统的设计都源于实际的需求,有时我们希望将Task进行归类,将其作为一个整体进行切换并施加一定的策略,类似Ubuntu和macOS的“虚拟桌面”。同样的,需要再抽象一层,于是Android在后续的版本中就有了Stack这个概念,可惜的是直到L为止,Android也只是简单地将Launcher、多任务这种特殊的Task和普通Task用不同的Stack区别开来,本质上并没有发挥Stack这层定制化的作用。聪明的读者一定能想到,Stack这层可以有很大的想象空间,现在的架构如下:

-Display-A -Stack-A  -Task-A   -Activity-A2   -Activity-A1 -Stack-B  -Task-B   -Activity-B1

从上面的分析可以看到,每一层的抽象都是将一些共性的东西提取出来统一管理,各层抽象间形成类似“职责链模式”中的链条,这样在设计数据结构的时候可以将最详细的信息放在最底层抽象,更通用的数据可以逐层顺着链条往更高的抽象上放置。


设计要点解析

扯了这么多,现在可以回到分屏的设计,让我们看一下分屏的效果:

最重要的一个问题是:如何声明一个界面的大小和位置,这直接关系到API如何定义。

首先,大小和位置作为一个界面的基本属性,必然定义在界面这层的抽象中。最简单的,我们可以在启动每个界面时指定,那么这就要求调用方要记住这些数据,这显然是给调用方添堵,直觉告诉我们,这不合理,调用方最好对这些事一无所知。

这时候让我们往高一层看,一个Task中的所有界面应当是同一个大小和位置。那么可以这么表达:有一个指定大小和位置的Task,所有放入该Task的Activity都要受其约束。这个时候调用方只需要在Task级别指定这些属性即可,那调用方不还是得记录这些信息吗?先别急。

假设Task-B也想跟Task-A一样尺寸怎么办,是不是也要再单独指定?这又回到跟之前一样的问题,这时候来到Stack这层,这么表达:有一个指定大小和位置的Stack,所有放入该Stack的Task都要受其约束。如果进入分屏时对其指定一个默认的属性,那调用方就完全不用关心这些事情。而事实也正是如此,进入分屏时的起始尺寸都将是一个固定的默认值,Bingo!

回到上面提及的分屏交互第4点:

  • 假设A跟B已经分屏,按Home键退出后,点击可分屏的C应用,此时将是A和C分属上下屏

这就要求系统要在比应用更高的一层记录尺寸信息,那启动C时只需要将C放入某个Stack中即可。

这里终于讲到了Stack设计的初衷:它是对屏幕某个区域的一种抽象,并且可以有自己的显示策略。那么对于分屏,系统中至少得存在两个Stack,上下或者左右各一个,显然只要指定其中一个大小就能计算出另外一个。虽然Android在较早的版本中就引入Stack这个概念,但是直到Android-M才开始实验性地将其应用于分屏,而到了N则真正让Stack发扬光大,包括这里没有提及的Freeform模式也都是用Stack来实现。当然,只要你有想法,可以自由定制。

综上所述:大小和位置在Stack这层抽象上进行限制,并施加到其下管理的所有Task和Activity。

关于如何动态改变尺寸,定义一个API即可,如下:

public void resizeDockedStack(Rect dockedBounds, Rect tempDockedTaskBounds,            Rect tempDockedTaskInsetBounds,            Rect tempOtherTaskBounds, Rect tempOtherTaskInsetBounds);

参数有点多,这里只需要知道这里通过Rect来指定Stack的位置和大小,其它的参数这里也可以提一下,是用来优化拖动分屏杠时的场景,保证仅当离手时才重新对分屏界面进行绘制。

调用方也显而易见,就是中间的分屏杠,它会根据拖动的位置计算出最后的尺寸,而调用后执行的逻辑就是重新改变Stack、Task、及Activity(这么说实际上不准确,这里为了便于描述)的尺寸,然后触发界面的重绘。

到这里为止讲的都是系统端的设计,还剩最后一个问题:给定一个尺寸后,系统应该为界面的正常绘制提供哪些必要的支持?

对于进入分屏的应用,系统应该制造一种错觉,那就是当前你是显示在了一块更小的屏幕上,而不是进入了一种特殊的状态。系统应尽可能地让这一过程不被客户端感知,当然也确实需要额外的回调来告诉界面当前进入了分屏状态,因为客户端可能需要在此状态下做一些事情,但是要注意的是,不应该由客户端显式地去处理由此带来的任何副作用。

界面的绘制涉及到如下重要的元素,系统要将这些变化隐藏起来:

  • 一块合适大小的Buffer
  • 正确的Resources

对于Buffer,客户端一般只会看到封装了Buffer的Canvas,重建Buffer的过程不会有任何感知。

对于Resources,我们都知道跟Configuration有关,设想半屏的情况下还在使用全屏时的Resources,显然会有问题。解决方案是尺寸改变时重建适配新尺寸的Configuration,然后自动更新Resources,这就跟转屏时Configuration的改变并无二致,应用只需要直接愉快地使用Resources就好,并不需要做特殊处理。

最后,既然系统已经做了这么多事情,那客户端是不是可以高枕无忧了呢。并不是,如果这样也就不需要客户端去声明是否支持分屏,客户端需要对此进行适配,比如在给定一个尺寸后,若是没有合适的资源匹配,那可能会导致崩溃。另外还需要遵循系统在此模式下某些行为的改变,这里不再赘述,参考官方文档即可。

更多相关文章

  1. Android(安卓)系统权限
  2. 设置Android(安卓)Studio启动时打开欢迎界面(选择最近打开过的工
  3. Android(安卓)的属性系统
  4. Android数据类型之间相互转换系统介绍
  5. Android(安卓)GUI系统之SurfaceFlinger(13)理解Gralloc2 内存分配
  6. 能够删除的安卓(Android)系统自带程序详细列表
  7. Android(安卓)UI常用实例 如何实现欢迎界面(Splash Screen)
  8. S5PV210 Android(安卓)Overlay系统(视频输出系统)分析
  9. Android仿人人客户端(v5.7.1)——应用主界面之左侧面板UI实现

随机推荐

  1. JDBC_mysql---防sql注入,存储图片
  2. Mybatis的动态sql详解,区别于传统的sql拼
  3. 创建56个民族的sql语句
  4. SQL恢复XP_CMDSHLL 以及XPLOG70.DLL被删
  5. 使用带有派生列的SQL排名函数
  6. vs2012连接sql2012,SQLDriverConnect问题
  7. MySQL数据查询之单表查询
  8. 5 - SQL Server 2008 之 四则运算、比较
  9. Mysql查询需要太多时间来执行。
  10. 做了一个工程(VB+SQLServer)后,老板又要我将