SystemUI之StatusBar创建

3,322 阅读6分钟

本文是基于Android 10代码分析。

启动 SystemUI服务

ActivityManagerService 创建完成时会启动 SystemUI

    // frameworks/base/services/java/com/android/server/SystemServer.java

    private void startOtherServices() {
        mActivityManagerService.systemReady(() -> {
            try {
                // AMS启动完成时,开启SystemUI
                // 其实就是start一个叫SystemUIService的service
                startSystemUi(context, windowManagerF);
            } catch (Throwable e) {
                    reportWtf("starting System UI", e);
            }
        }
    }

启动 SystemUI 的入口是 SystemUIService,它是四大组件之一的 Service。

    // frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIService.java
    
    public class SystemUIService extends Service {
        @Override
        public void onCreate() {
            super.onCreate();
            // 启动各种服务
            ((SystemUIApplication) getApplication()).startServicesIfNeeded();
        }
    }

调用了 SystemUIApplication#startServicesIfNeeded() 来启动各种服务,而这些服务不是四大组件之一的 Service, 而是继承自 SystemUI 接口的服务,我们称之为 SystemUI服务。

    // frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
    
    public void startServicesIfNeeded() {
        // 这个数组中定义了很多SystemUI服务
        String[] names = getResources().getStringArray(R.array.config_systemUIServiceComponents);
        // 启动服务
        startServicesIfNeeded(names);
    }

SystemUI 要启动的所有服务都是在数组 config_systemUIServiceComponents 中定义的,可以看下这个数组的定义

    // frameworks/base/packages/SystemUI/res/values/config.xml
    
    <string-array name="config_systemUIServiceComponents" translatable="false">
        <item>com.android.systemui.Dependency$DependencyCreator</item>
        <item>com.android.systemui.util.NotificationChannels</item>
        <item>com.android.systemui.statusbar.CommandQueue$CommandQueueStart</item>
        <item>com.android.systemui.keyguard.KeyguardViewMediator</item>
        <item>com.android.systemui.recents.Recents</item>
        <item>com.android.systemui.volume.VolumeUI</item>
        <item>com.android.systemui.stackdivider.Divider</item>
        <item>com.android.systemui.SystemBars</item>
        <item>com.android.systemui.usb.StorageNotification</item>
        <item>com.android.systemui.power.PowerUI</item>
        <item>com.android.systemui.media.RingtonePlayer</item>
        <item>com.android.systemui.keyboard.KeyboardUI</item>
        <item>com.android.systemui.pip.PipUI</item>
        <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item>
        <item>@string/config_systemUIVendorServiceComponent</item>
        <item>com.android.systemui.util.leak.GarbageMonitor$Service</item>
        <item>com.android.systemui.LatencyTester</item>
        <item>com.android.systemui.globalactions.GlobalActionsComponent</item>
        <item>com.android.systemui.ScreenDecorations</item>
        <item>com.android.systemui.biometrics.BiometricDialogImpl</item>
        <item>com.android.systemui.SliceBroadcastRelayHandler</item>
        <item>com.android.systemui.SizeCompatModeActivityController</item>
        <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item>
        <item>com.android.systemui.theme.ThemeOverlayController</item>
    </string-array>    

对这些 SystemUI服务 做一点介绍

  1. com.android.systemui.Dependency 是为了创建全局可用的依赖关系。
  2. com.android.systemui.SystemBars 创建整个SystemUI视图的入口类。
  3. com.android.systemui.statusbar.CommandQueue 是一个 Binder 类,它会被StatusBar注册到 StatusBarManagerService 中,用于接收StatusBarManagerService服务端的消息。

现在接着上面,来看下如何启动这些 SystemUI服务

    private void startServicesIfNeeded(String[] services) {
        if (mServicesStarted) {
            return;
        }
        mServices = new SystemUI[services.length];

        if (!mBootCompleted) {
            // check to see if maybe it was already completed long before we began
            // see ActivityManagerService.finishBooting()
            // sys.boot_completed 属性可以判断系统是否已经启动完毕
            if ("1".equals(SystemProperties.get("sys.boot_completed"))) {
                mBootCompleted = true;
            }
        }

        final int N = services.length;
        for (int i = 0; i < N; i++) {
            String clsName = services[i];
            Class cls;
            try {
                // 1. 通过反射创建实例
                cls = Class.forName(clsName);
                Object o = cls.newInstance();
                if (o instanceof SystemUI.Injector) {
                    o = ((SystemUI.Injector) o).apply(this);
                }
                // 强转为SystemUI保存
                mServices[i] = (SystemUI) o;
            } catch(Exception ex){
                throw new RuntimeException(ex);
            }
            
            mServices[i].mContext = this;
            // 注意这里把mComponents也传进去了,用于全局保存变量,Class<?> -> Object
            mServices[i].mComponents = mComponents;
            
            // 2. 调用start()启动
            mServices[i].start();

            // 3. 系统启动完毕,还用启动相应的onBootCompleted()
            if (mBootCompleted) {
                mServices[i].onBootCompleted();
            }
        }
       
        mServicesStarted = true;
    }

首先根据 SystemUI服务的类名,通过反制加载类并且创建类的对象,然后调用对象的 start() 方法和 onBootCompleted() 方法。

创建StatusBar

现在把目光集中到如何创建 StatusBar 上。前面说过,SystemBars 这个 SystemUI服务是整个System视图创建的入口类,它被启动时会调用 start() 方法

    // frameworks/base/packages/SystemUI/src/com/android/systemui/SystemBars.java
    
    public void start() {
        // 也就是调用 StatusBar#start()
        createStatusBarFromConfig();
    }

createStatusBarFromConfig()很简单,就是调用了StatusBar类的start()方法。

StatusBar#start() 代码很庞大,本文主要分析状态栏的创建,至于状态栏上图标的添加留到下一篇文章进行分析。

    // packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
    public void start() {
        // 创建整个SystemUI视图并添加到WindowManager中
        createAndAddWindows(result);
    }
    
    public void createAndAddWindows(@Nullable RegisterStatusBarResult result) {
        // 创建整个SystemUI视图
        makeStatusBarView(result);
        // 把视图添加到Window中
        mStatusBarWindowController = Dependency.get(StatusBarWindowController.class);
        mStatusBarWindowController.add(mStatusBarWindow, getStatusBarHeight());
    }    

makeStatusBarView()负责创建整个SystemUI视图,其中包括状态栏。下面的代码主要提取了创建视图的代码,分几步进行分析

    // packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
    
    protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {
        // ...
        
        // 1. 实例化整个SystemUI视图,包括状态栏,通知面版, 锁屏
        mStatusBarWindow = (StatusBarWindowView) mInjectionInflater.injectable(
                LayoutInflater.from(context)).inflate(R.layout.super_status_bar, null);    
    }

首先创建整个SystemUI视图,它的布局是 super_status_bar.xml,可以大致来看下布局

<com.android.systemui.statusbar.phone.StatusBarWindowView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:sysui="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    <!--这里省略了无关紧要的视图-->

    <!--状态栏容器-->
    <FrameLayout
        android:id="@+id/status_bar_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <!--整个下拉通知面版,包括锁屏界面-->
    <include layout="@layout/status_bar_expanded"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="invisible" />

    <!--这里省略了无关紧要的视图-->

    <!--这个显示在锁屏底部区域-->
    <LinearLayout
        android:id="@+id/lock_icon_container"
        android:orientation="vertical"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/status_bar_height"
        android:layout_gravity="top|center_horizontal">
        <com.android.systemui.statusbar.phone.LockIcon
            android:id="@+id/lock_icon"
            android:layout_width="@dimen/keyguard_lock_width"
            android:layout_height="@dimen/keyguard_lock_height"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="@dimen/keyguard_lock_padding"
            android:contentDescription="@string/accessibility_unlock_button"
            android:src="@*android:drawable/ic_lock"
            android:scaleType="center" />
        <com.android.keyguard.KeyguardMessageArea
            android:id="@+id/keyguard_message_area"
            style="@style/Keyguard.TextView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/keyguard_lock_padding"
            android:gravity="center"
            android:singleLine="true"
            android:ellipsize="marquee"
            android:focusable="true" />
    </LinearLayout>
</com.android.systemui.statusbar.phone.StatusBarWindowView>

根视图StatusBarWindowView是一个FrameLayout,那么状态栏显示在最下面,然后通知面版会覆盖状态栏,最后还有一个底部视图在最上面。

可以注意到状态栏的窗口ID为status_bar_container,一会就会向这个容器中添加状态栏视图。现在接着上面的makeStatusBarView()继续分析

    // packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
    
    protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {
        // ...
        
        // 1. 实例化整个SystemUI视图,包括状态栏,通知面版, 锁屏
        mStatusBarWindow = (StatusBarWindowView) mInjectionInflater.injectable(
                LayoutInflater.from(context)).inflate(R.layout.super_status_bar, null);   
        
        // 2.创建状态栏视图        
        FragmentHostManager.get(mStatusBarWindow)
                .addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> {
                    CollapsedStatusBarFragment statusBarFragment =
                            (CollapsedStatusBarFragment) fragment;
                    // 用通知图标控制器,初始化了通知图标区域和中心图标区域,并且显示出来
                    statusBarFragment.initNotificationIconArea(mNotificationIconAreaController);
                }).getFragmentManager()
                .beginTransaction()
                // CollapsedStatusBarFragment实现了状态栏的添加
                .replace(R.id.status_bar_container, new CollapsedStatusBarFragment(),
                        CollapsedStatusBarFragment.TAG)
                .commit();                
    }

可以看到CollapsedStatusBarFragment代表的就是状态栏视图,这个视图被添加到ID为status_bar_container的容器中。接下来只要分析CollapsedStatusBarFragment的生命周期,即可知道状态栏的创建过程,首先看的就是CollapsedStatusBarFragment#onCreateView()方法,这里就是创建视图的地方

    // frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
    
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
            Bundle savedInstanceState) {
        // status bar视图
        return inflater.inflate(R.layout.status_bar, container, false);
    }

status_bar.xml 就是状态栏视图布局,接下来再简单看下布局情况

<com.android.systemui.statusbar.phone.PhoneStatusBarView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:systemui="http://schemas.android.com/apk/res/com.android.systemui"
    android:layout_width="match_parent"
    android:layout_height="@dimen/status_bar_height"
    android:id="@+id/status_bar"
    android:background="@drawable/system_bar_background"
    android:orientation="vertical"
    android:focusable="false"
    android:descendantFocusability="afterDescendants"
    android:accessibilityPaneTitle="@string/status_bar"
    >

    <!--这里省略了无关紧要的视图-->

    <LinearLayout android:id="@+id/status_bar_contents"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingStart="@dimen/status_bar_padding_start"
        android:paddingEnd="@dimen/status_bar_padding_end"
        android:paddingTop="@dimen/status_bar_padding_top"
        android:orientation="horizontal"
        >
        <FrameLayout
            android:layout_height="match_parent"
            android:layout_width="0dp"
            android:layout_weight="1">

            <!--这个好像是在状态栏显示信息布局-->
            <include layout="@layout/heads_up_status_bar_layout" />

            <!--状态栏左边视图,依次显示运营商名字,时间,通知图标-->
            <LinearLayout
                android:id="@+id/status_bar_left_side"
                android:layout_height="match_parent"
                android:layout_width="match_parent"
                android:clipChildren="false"
            >
                <ViewStub
                    android:id="@+id/operator_name"
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent"
                    android:layout="@layout/operator_name" />

                <com.android.systemui.statusbar.policy.Clock
                    android:id="@+id/clock"
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent"
                    android:textAppearance="@style/TextAppearance.StatusBar.Clock"
                    android:singleLine="true"
                    android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
                    android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
                    android:gravity="center_vertical|start"
                />

                <!--通知图标区域-->
                <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
                    android:id="@+id/notification_icon_area"
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:orientation="horizontal"
                    android:clipChildren="false"/>

            </LinearLayout>
        </FrameLayout>

        <!--这里省略了无关紧要的视图-->
        
        <!--中心图标区域-->
        <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
            android:id="@+id/centered_icon_area"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:orientation="horizontal"
            android:clipChildren="false"
            android:gravity="center_horizontal|center_vertical"/>

        <!--系统图标区,由状态图标集和电池图标组成,状态图标集由wifi,bt等等组成-->
        <com.android.keyguard.AlphaOptimizedLinearLayout android:id="@+id/system_icon_area"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:orientation="horizontal"
            android:gravity="center_vertical|end"
            >
            
            <include layout="@layout/system_icons" />
        </com.android.keyguard.AlphaOptimizedLinearLayout>
    </LinearLayout>

    <!--这里省略了无关紧要的视图-->

</com.android.systemui.statusbar.phone.PhoneStatusBarView>

从这个布局可以分析出状态栏从左到右到底显示了什么。

  1. 最左边的一块区域,依次显示运营商名字,时间,通知图标。
  2. 中间一块区域,这个我暂时还没发现显示什么。
  3. 然后是最右边一块区域,显示的是状态图标(例如bt, wifi),以及电池图标。

这样一来,我们就对整个状态栏上的布局有个了解,接下来分析下状态上的状态图标(例如bt, wifi)是如何显示上去的,这就是后面一篇文章分析的主要内容。