View
View层级可以分为两部分:
- 抽象
Activity启动的时候会创建一个PhoneWindow对象,将UI处理交给PhoneWindow。而PhoneWindow在后续Activity加载布局资源的时候生成一个DecorView,所以在抽象概念上,是Activity > PhoneWindow > DecorView的;
- 具象
Activity和PhoneWindow都是抽象的概念,我们直接看到的其实是DecorView。DecorView在生成View层级结构的时候,是作为根View的,所以在具象概念上,或者说我们能够看到的层面上,DecorView是最外层的;
view层级
-
Activity:Activity创建于performLaunchActivity方法中,在startActivity时候触发;
-
PhoneWindow:是Activity和View交互的中间层,帮助Activity管理View,同样创建于performLaunchActivity方法中,再具体点就是Activity的attach方法;
-
DecorView:是所有View的最顶层View,是所有View的parent,创建于setContentView->PhoneWindow.installDecor;
-
ViewRootImpl:用于处理View相关的事件,比如绘制,事件分发,也是DecorView的parent,创建于handleResumeActivity方法中,最后通过addView被创建;
但是ViewRootImpl并不是一个真正的 View,只是继承了 ViewParent 接口,用来掌管 View 的各种事件,包括 requestLayout、invalidate、dispatchInputEvent 等等;
进程启动
setContentView方法的作用就是将指定的View加载到根View之上,这样当显示view时,先显示根view,然后在显示子view,以此类推,最终将所有的view显示出来。setContentView的本质就是为要显示的View分配内存;
在setContentView方法中,执行了generateLayout方法,generateLayout方法的作用就是设置一些窗体的属性值,然后窗体布局添加到DecorView中,并返回窗体布局中ID为content的帧布局;
然后后续调用handleResumeActivity方法,准备显示Activity,调用Window的addView接口,将创建好的DecorView添加到WindowManager中
在generateLayout方法中,有一对判断:
// 用于更新View自己的属性值
if (a.getBoolean(R.styleable.Window_windowLightStatusBar, false)) {
decor.setSystemUiVisibility(
decor.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
if (a.getBoolean(R.styleable.Window_windowLightNavigationBar, false)) {
decor.setSystemUiVisibility(
decor.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
}
-
Window_windowLightStatusBar:设置StatusBar颜色
- true:表示StatusBar风格为Light,字体颜色为黑色;
- false:表示StatusBar风格不是Light,字体颜色为白色;
-
Window_windowLightNavigationBar:设置NavigationBar颜色;
- true:表示NavigationBar风格为Light,字体颜色为黑色;
- false:表示NavigationBar风格不是Light,字体颜色为白色;
-
SYSTEM_UI_FLAG_LIGHT_STATUS_BAR:顶部状态栏的风格设置
-
SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR:底部导航栏的风格设置
SystemUiVisibility 属性
SystemUiVisibility属性主要用来控制系统状态栏和导航栏的行为,状态栏和导航栏都属于SystemUI模块的StatusBar,所以SystemUiVisibility属性的消费者包含StatusBar,同时当状态栏和导航栏发生变化时,窗口的布局一般也会跟着发生变化,这就意味着PhoneWindowManager也要消费SystemUiVisibility属性;
| 属性 | 说明 |
|---|---|
| View.SYSTEM_UI_FLAG_VISIBLE | 默认显示状态栏和导航栏 |
| View.SYSTEM_UI_FLAG_LOW_PROFILE | 低调模式,隐藏不重要的状态栏图标,导航栏中相应的图标都变成了一个小点,点击状态栏或者导航栏还原成正常状态 |
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | 隐藏导航栏,点击屏幕任意区域,导航栏将重新出现 |
| View.SYSTEM_UI_FLAG_FULLSCREEN | 隐藏状态栏,从状态栏位置下拉,状态栏将重新出现 |
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | 将布局内容拓展到导航栏和状态栏后面 |
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | 将布局内容拓展到状态栏后面 |
| View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR | 顶部状态栏的风格设置,6.0版本后生效 |
| View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR | 底部导航栏的风格设置,6.0版本后生效 |
| …………………… | ……………………(参考:setSystemUiVisibility属性详解) |
设置SystemUiVisibility属性的方式有三种:
- 直接在窗口的WindowManager.LayoutParams对象的systemUiVisibility属性上进行设置,并通过WindowManager.updateViewLayout()方法使其生效;
public interface WindowManager extends ViewManager {
public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
//隐藏窗口的所有装饰,比如状态栏和导航栏
public static final int FLAG_FULLSCREEN = 0x00000400;
//控制窗口状态栏、导航栏的显示和隐藏
public int systemUiVisibility;
}
}
- 在一个已经显示在窗口上的控件中调用setSystemUiVisibility方法;
public static void changeStatusBarContrastStyle(Window window, Boolean lightIcons) {
View decorView = window.getDecorView();
if (lightIcons) {
// Draw light icons on a dark background color
decorView.setSystemUiVisibility(decorView.getSystemUiVisibility() & ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
} else {
// Draw dark icons on a light background color
decorView.setSystemUiVisibility(decorView.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
}
这种方式最终影响的其实是窗口的WindowManager.LayoutParams对象的subtreeSystemUiVisibility属性:
public interface WindowManager extends ViewManager {
public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
//控制窗口状态栏、导航栏的显示和隐藏
public int subtreeSystemUiVisibility;
}
}
- 在Android R(API 30)版本之后,弃用setSystemUiVisibility方式,改用WindowInsetsController#setSystemBarsAppearance方法设置systemUiVisibility属性;
//icon color -> black
activity.getWindow().getDecorView().getWindowInsetsController().setSystemBarsAppearance(APPEARANCE_LIGHT_STATUS_BARS, APPEARANCE_LIGHT_STATUS_BARS);
//icon color -> white
activity.getWindow().getDecorView().getWindowInsetsController().setSystemBarsAppearance(0, APPEARANCE_LIGHT_STATUS_BARS);
LightBarController
大致整个Light机制分为三个模块:
-
App
- 在View发生变化时用于重新计算各个View的SystemUiVisibility属性;
-
WMS
- relayoutWindow用于重新布局窗口;
-
SystemUI
- 响应应用程序SystemUiVisibility属性变更,StatusBar和NavigationBar以及其他模块对此做出DarkIconChanged响应;
setSystemUiVisibility
这种方式在Android R上标注为弃用,但是没有完全弃用,在Android PhoneWindow中,很多地方还是使用到了setSystemUiVisibility的方式进行SystemUiVisibility属性的修改;
- setSystemUiVisibility方法首先将属性赋值给mSystemUiVisibility,然后会调用父控件的recomputeViewAttributes方法,通知父控件子控件属性发生了变化;我们知道在Activity的跟布局DecorView在内的任何View,WindowManager在将它添加到窗口上的过程中,会创建一个ViewRootImpl,并将View设置给ViewRootImpl,这样根View的父类就变成了ViewRootImpl,这就意味着不管任何子View调用recomputeViewAttributes方法,最终所触发的都是ViewRootImpl的recomputeViewAttributes;
- scheduleTraversals方法会为mTraversalBarrier对象设置回调,最终会等待系统垂直刷新同步信号,回调TraversalRunnable对象的run方法,该方法会调用doTraversal方法,然后进一步调用performTraversals方法;
- performTraversals调用collectViewAttributes方法收集所有子View的属性,会重新计算最新的SystemUIVisibility属性;
- 使用adjustLayoutParamsForCompatibility方法更新appearance属性值,用于后续SystemUI Bar颜色的设置;
- 最终触发Session的relayoutWindow方法,用于重新布局窗口;
至此,SystemUiVisibility属性设置的逻辑就从App模块跳转到了WMS模块了;
WMS SystemUiVisibility
-
在调用collectViewAttributes获取最新的systemUIVisibiliy属性之后,会调用relayoutWindow方法,该方法进一步调用IWindowSession的relayout方法,IWindowSession的具体实现类为Session;
-
relayout方法会进一步调用WindowManagerService的relayoutWindow方法;
-
relayoutWindow方法会调用一个关键方法performSurfacePlacement,用于刷新界面,其调用了RootWindowContainer的performSurfacePlacement方法,该方法会直接进入真正执行布局的函数:performSurfacePlacementNoTrace;
-
RootWindowContainer的applySurfaceChangesTransaction方法,其中设置布局水印和StrictMode警告框,然后遍历DisplayContent的applySurfaceChangesTransaction方法;
-
DisplayContent的applySurfaceChangesTransaction方法会对DisplayContent进行布局;
- beginPostLayoutPolicyLw:初始化参数;
- applyPostLayoutPolicyLw:设置上述参数,这个函数的调用顺序是沿着Z轴自上而下对每个窗口进行调用;
- finishPostLayoutPolicyLw:根据参数执行相应动作;
-
最后调用了finishPostLayoutPolicyLw方法,在该方法中调用了DisplayPolicy的updateSystemUiVisibilityLw方法,在这个方法中,获取fullscreenAppearance,然后根据fullscreenAppearance创建了后续变更StatusBar颜色使用的AppearanceRegion[];
-
最后调用Handler的post方法,执行Runnable,其中调用了StatusBarManagerInternal的onSystemBarAppearanceChanged()方法;
至此,SystemUiVisibility属性就和SystemUI模块产生了关联;
LightBarController
- 从StatusBarManagerService进入SystemUI模块之后,StatusBar调用了LightBarController的onStatusBarAppearanceChanged方法,用于通知深色浅色颜色变化的通知;
- 在LightBarTransitionsController中,会调用setIconDark方法,循环遍历mReceivers,将SystemUiVisibility属性变化发送到各个监听模块;
addDarkReceiver
各个模块需要在View初始化成功的时候,即在onAttachedToWindow方法中,将DarkReceiver监听器注册到DarkIconDispatcherImpl中,使用ArrayMap类型的mReceivers进行维护;
WindowInsetsController
setSystemUIVisibilty实现App沉浸式或者对状态栏、导航栏图标进行反色时,可以通过setSystemUIVisibilty传入对应的Flag进行实现。但是从Android API 30开始,setSystemUIVisibilty就不被推荐使用了;
WindowInsetsController是一个接口类,它主要作用就是控制窗口行为,此类在Android R(API 30) 添加,意在简化原先代码动态修改窗口SystemBars(StatusBar&NavigationBar)表现;
主要功能包括:
- 显示/隐藏 System bar;
- 设置 System bar 前景(如状态栏的文字图标)是亮色还是暗色;
- 逐帧控制 insets 动画,例如可以让软键盘弹出得更丝滑
WindowInsetsController 属性
- APPEARANCE_LIGHT_NAVIGATION_BARS:控制NavigationBar(导航栏)反色的标志,相当于SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
- APPEARANCE_LIGHT_STATUS_BARS:控制StatusBar(状态栏)反色的标志,相当于SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
setSystemBarsAppearance
基本上和setSystemUiVisibility的流程差不多;
通过分析Android R代码可知,目前setSystemBarsAppearance()方法仅在PendingInsetsController中调用,PendingInsetsController为缓存对象,即视图还没有被加载时使用的,但是其内部实现方式基本和InsetsController的方式相同;