SystemUI_LightBarController

936 阅读7分钟

View

view层级.png

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 等等;

进程启动

app启动流程.png

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

LightBarController架构.png

大致整个Light机制分为三个模块:

  • App

    • 在View发生变化时用于重新计算各个View的SystemUiVisibility属性;
  • WMS

    • relayoutWindow用于重新布局窗口;
  • SystemUI

    • 响应应用程序SystemUiVisibility属性变更,StatusBar和NavigationBar以及其他模块对此做出DarkIconChanged响应;

setSystemUiVisibility

这种方式在Android R上标注为弃用,但是没有完全弃用,在Android PhoneWindow中,很多地方还是使用到了setSystemUiVisibility的方式进行SystemUiVisibility属性的修改;

setSystemUiVisibility.png

  1. setSystemUiVisibility方法首先将属性赋值给mSystemUiVisibility,然后会调用父控件的recomputeViewAttributes方法,通知父控件子控件属性发生了变化;我们知道在Activity的跟布局DecorView在内的任何View,WindowManager在将它添加到窗口上的过程中,会创建一个ViewRootImpl,并将View设置给ViewRootImpl,这样根View的父类就变成了ViewRootImpl,这就意味着不管任何子View调用recomputeViewAttributes方法,最终所触发的都是ViewRootImpl的recomputeViewAttributes;
  2. scheduleTraversals方法会为mTraversalBarrier对象设置回调,最终会等待系统垂直刷新同步信号,回调TraversalRunnable对象的run方法,该方法会调用doTraversal方法,然后进一步调用performTraversals方法;
  3. performTraversals调用collectViewAttributes方法收集所有子View的属性,会重新计算最新的SystemUIVisibility属性;
  4. 使用adjustLayoutParamsForCompatibility方法更新appearance属性值,用于后续SystemUI Bar颜色的设置;
  5. 最终触发Session的relayoutWindow方法,用于重新布局窗口;

至此,SystemUiVisibility属性设置的逻辑就从App模块跳转到了WMS模块了;

WMS SystemUiVisibility

Window_Session_relayout.png

  1. 在调用collectViewAttributes获取最新的systemUIVisibiliy属性之后,会调用relayoutWindow方法,该方法进一步调用IWindowSession的relayout方法,IWindowSession的具体实现类为Session

  2. relayout方法会进一步调用WindowManagerService的relayoutWindow方法;

  3. relayoutWindow方法会调用一个关键方法performSurfacePlacement,用于刷新界面,其调用了RootWindowContainer的performSurfacePlacement方法,该方法会直接进入真正执行布局的函数:performSurfacePlacementNoTrace;

  4. RootWindowContainer的applySurfaceChangesTransaction方法,其中设置布局水印和StrictMode警告框,然后遍历DisplayContent的applySurfaceChangesTransaction方法;

  5. DisplayContent的applySurfaceChangesTransaction方法会对DisplayContent进行布局;

    • beginPostLayoutPolicyLw:初始化参数;
    • applyPostLayoutPolicyLw:设置上述参数,这个函数的调用顺序是沿着Z轴自上而下对每个窗口进行调用;
    • finishPostLayoutPolicyLw:根据参数执行相应动作;
  6. 最后调用了finishPostLayoutPolicyLw方法,在该方法中调用了DisplayPolicy的updateSystemUiVisibilityLw方法,在这个方法中,获取fullscreenAppearance,然后根据fullscreenAppearance创建了后续变更StatusBar颜色使用的AppearanceRegion[];

  7. 最后调用Handler的post方法,执行Runnable,其中调用了StatusBarManagerInternal的onSystemBarAppearanceChanged()方法;

至此,SystemUiVisibility属性就和SystemUI模块产生了关联;

LightBarController

LightBarController.png

  1. 从StatusBarManagerService进入SystemUI模块之后,StatusBar调用了LightBarController的onStatusBarAppearanceChanged方法,用于通知深色浅色颜色变化的通知;
  2. 在LightBarTransitionsController中,会调用setIconDark方法,循环遍历mReceivers,将SystemUiVisibility属性变化发送到各个监听模块;
addDarkReceiver

addDarkReceiver.png

各个模块需要在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

setSystemBarsAppearance.png

基本上和setSystemUiVisibility的流程差不多;

通过分析Android R代码可知,目前setSystemBarsAppearance()方法仅在PendingInsetsController中调用,PendingInsetsController为缓存对象,即视图还没有被加载时使用的,但是其内部实现方式基本和InsetsController的方式相同;