一、Android屏幕相关的知识
Android横竖屏设置
Android设置横竖屏的入口多种多样,如下:
1、Activity窗口:应用Mainfest中配置screenOrientation参数、Activity中调用setRequestedOrientation(),这是通过设置Activity的screenOrientation属性
2、系统栏屏幕自动旋转:使用systemui包中的RotationLockTile类的handleClick()方法设置的
3、非Activity窗口:指定WindowManager.LayoutParams.screenOrientation属性
注:屏幕旋转会触发onConfigurationChanged(),如果Activity不设置忽略屏幕方向改变,Activity生命周期会重走,可以按项目需求做相应操作
虽然设置的入口多种多样,中间经过流程也各不相同,但最终的都是调用了PhoneWindowManager的rotationForOrientationLw(int orientation, int lastRotation)的方法(到这一步,已经是最终的切换了,前面还有一些步骤),具体源码就不在贴出,其实大家可以想到,如果修改framework源码,在这里就可实现强制横屏了
Android的屏幕绘制简介
关于android绘制原理比较复杂,特别是具体到View层面,大致就是通过ViewRootImpl与WMS通信(Bind通信),完成performTraversals -> performMeasure() -> performLayout() -> performDraw()流程,最终通过canvs在surface上绘制,生成FrameBuffer去显示,具体细节与本文主题关系不大,暂不深入。时序图如下:
这里我们主要说下view、window、surface、surfaceFlinger的关系:
View:android 视图组成的基本控件
Window:屏幕上的某块显示区域,用来承载 View(PhoneWinow就是window的实现类),主要用来提供添加,移除,渲染的能力(继承ViewManager),事件响应,如Toast,状态栏,Activity界面,Dialog,包括锁屏界面(这个有点特殊),window是Z-order的,即window在Z轴是有层级关系的
Surface:对应一块屏幕缓冲区,每个 window 对应一个 Surface
SurfaceFlinger:管理surface,Android 的一个服务进程,负责管理 Surface
WindowManagerService(WMS):Android 框架层的一个服务进程,用来管理 Window
下面三张图可以帮助大家了解下:
Window的层级
通过上面知识,大家了解到window是有层级的,在系统中,WindowManager中使用WindowManager.LayoutParams的type字段来存储window的层级,由于window本身存在父子关系,具体层级关系如下:
窗口层级(type) | 层级值 |
Application windows(应用窗口) | 1~99 |
Sub-windows(子窗口) | 1000~1999 |
System windows(系统窗口) | 2000~2999 |
注意:从表面上看系统窗口 > 子窗口 > 应用窗口,从数值上来说是正确的,但是如果说 Window Layer的层级关系(大部分情况都是按照层级关系),这个说法就是错误的,举个很简单的例子,壁纸层 属于系统窗口管理,但是Window Layer的层级关系 却是最底端;WindowType 的值划分是为了更好的管理Window的类别、权限、依赖关系,和实际的Window Layer层级并没有关系。
从层级到Z-order(android定义的z轴界面层级,有1-33,33级)android有一套对应关系,具体在WindowManagerPolicy的getWindowLayerFromTypeLw()方法中,源码就不贴出了,部分对应和z轴定义如下截图
最终在Framework中计算方式:Layer = Z-Order * 10000 + 1000,即SurfaceFlinger中的混合顺序,我们可以通过adb shell dumpsys SurfaceFlinger来看下层级,下面截图是我在强制横屏demo下场景下截的图(如果window没有长宽,是不会在SurfaceFlinger中显示)
横竖屏设置选项
这里我们介绍下横竖屏的设置选项。默认是unspecified,即由系统决定
注意:虽然Android只提供了几种旋转角度,但其实framework可以设置任意角度
二、强制横屏实现
通过前面知识介绍,我们知道了几种设置横竖屏的方法,这里使用添加高layer的window并设置横竖屏
申请在其他应用上层显示权限
1.判断权限
2.申请权限
添加window并进行横竖屏设置
由于window本身并不是一个真正view,仅仅是个载体,我们需要添加一个长宽均0的view,代码如下:
1.创建布局
2.添加window
这里有个小技巧,我们使用service去实现,并且stopself(),这样状态栏不会有notification。
可能大家会疑惑,为什么我只是使用WindowManger.addview(),并没有创建新的window,其实使用WindowManager 的addview方法,系统会自动创建一个window,可以通过 Android Studio的layout inspector工具查看页面
三、原理介绍
屏幕方向的确定的过程
从上到下遍历窗口堆栈,在可见的窗口中确定一个基准screenOrientation
1、非Activity窗口的screenOrientation如果不是(SCREEN_ORIENTATION_UNSPECIFIED、SCREEN_ORIENTATION_BEHIND)两个值,则结束遍历
2、一旦遍历到一个Activity窗口,接下来仅在Activity窗口中遍历,不再关心非Activity窗口总结来讲就是根据窗口的优先级高低来确定屏幕旋转方向,这也是我们方案的基本原理
转屏的准备
大家可能考虑到,系统检测到要改变屏幕方向,接下来屏幕上能看到的所有窗口都要以新的尺寸来重新绘制,由于各个窗口的重绘不可能在同一时刻完成,也不可能同时刷新,如果我们什么都不做,在屏幕上将会看到各种闪烁。这个应该怎么办?
系统解决方案:
1、截屏保存屏幕状态 (WindowManagerService.screenRotationAnimation())
2、用第一步的截屏生成一个非常高层级的,完全不透明的Layer,让用户在视觉上一直停留在竖屏状态,由于是完全不透明,所以在它下方的所有内容完全不可见,那么就尽情地绘制吧(WindowManagerService .updateRotationUncheckedLocked())
3、方向改变前哪些窗口可见,就必须等待那些窗口绘制完成,这个很好理解
4、截屏Layer为整体,施加旋转并淡出的动画,重绘的所有横屏界面为另一个整体,也施加一个旋转动画,这样当动画结束时看到的就是重绘后的横屏界面。还有一点需要注意,转屏过程中系统会禁用所有其它动画,避免动画的叠加。
(WMS.startAnimation()->
WMS.performSurfacePlacementInner->
WindowAnimator.animateLocked()->
WindowStateAnimator.computeShownFrameLocked())
过程演示图如下: