SystemUI之StatusBar建立

本文是基於Android 10代碼分析。java

啓動 SystemUI服務

ActivityManagerService 建立完成時會啓動 SystemUIandroid

// 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服務。app

// 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 中定義的,能夠看下這個數組的定義ide

// 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服務ui

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() 方法。this

建立StatusBar

如今把目光集中到如何建立 StatusBar 上。前面說過,SystemBars 這個 SystemUI服務是整個System視圖建立的入口類,它被啓動時會調用 start() 方法spa

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

createStatusBarFromConfig()很簡單,就是調用了StatusBar類的start()方法。code

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)是如何顯示上去的,這就是後面一篇文章分析的主要內容。

相關文章
相關標籤/搜索