Android 7.0 SystemUI 之啟動和狀態列和導航欄簡介

Dennis-Android發表於2019-03-04

PS:已同步至我的部落格 點選開啟

一、SystemUI 是什麼

首先SystemUI 是一個系統應用,apk路徑位於/system/priv-app

原始碼路徑位於:/framework/base/packages/SystemUI

它負責的功能如下:

  • 狀態列資訊的展示:比如電量資訊,時間,wifi狀態等
  • 通知欄訊息
  • 桌布管理
  • 截圖功能
  • 近期工作列顯示,比如長按home鍵顯示最近使用的app
  • 錄製螢幕功能
  • 截圖服務

以下是7.0 SystemUI 的程式碼截圖


Android 7.0 SystemUI 之啟動和狀態列和導航欄簡介

二、SystemUI 的啟動

SystemUI 是在SystemServer裡的AMS例項的systemReady方法裡呼叫startSystemUi方法啟動

SystemServer路徑:/base/services/java/com/android/server/SystemServer.java


 mActivityManagerService.systemReady(new Runnable() {
 ......
 static final void startSystemUi(Context context) {
        Intent intent = new Intent();
        intent.setComponent(new ComponentName("com.android.systemui",
                    "com.android.systemui.SystemUIService"));
        intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
        //Slog.d(TAG, "Starting service: " + intent);
        context.startServiceAsUser(intent, UserHandle.SYSTEM);
    }
 ......複製程式碼

在這個方法裡啟動一個SystemUIService服務.

public class SystemUIService extends Service {

    @Override
    public void onCreate() {
        super.onCreate();
        ((SystemUIApplication) getApplication()).startServicesIfNeeded();
    }
    ......複製程式碼

在onCreate方法中會呼叫SystemUIApplication的startServicesIfNeeded方法,這個方法會呼叫 startServicesIfNeeded(SERVICES)方法啟動一系列服務(並不是真正的服務,都繼承自SystemUI),可以這麼說SystemUI就是一個容器,裡面裝有負責不同功能的模組。

public class SystemUIApplication extends Application {
    ......

    private final Class<?>[] SERVICES = new Class[] {
            com.android.systemui.tuner.TunerService.class,
            com.android.systemui.keyguard.KeyguardViewMediator.class,
            com.android.systemui.recents.Recents.class,
            com.android.systemui.volume.VolumeUI.class,
            Divider.class,
            com.android.systemui.statusbar.SystemBars.class,
            com.android.systemui.usb.StorageNotification.class,
            com.android.systemui.power.PowerUI.class,
            com.android.systemui.media.RingtonePlayer.class,
            com.android.systemui.keyboard.KeyboardUI.class,
            com.android.systemui.tv.pip.PipUI.class,
            com.android.systemui.shortcut.ShortcutKeyDispatcher.class
    };

public void startServicesIfNeeded() {
        startServicesIfNeeded(SERVICES);
    }
    ......複製程式碼

startServicesIfNeeded方法會遍歷services這個陣列,依次呼叫service的start方法啟動服務


private void startServicesIfNeeded(Class<?>[] services) {
        //如果已經啟動了就返回
        if (mServicesStarted) {
            return;
        }
        //如果沒啟動完成完成
        if (!mBootCompleted) {
            if ("1".equals(SystemProperties.get("sys.boot_completed"))) {
                mBootCompleted = true;
                if (DEBUG) Log.v(TAG, "BOOT_COMPLETED was already sent");
            }
        }

        final int N = services.length;

        for (int i=0; i<N; i++) {
            Class<?> cl = services[i];
            if (DEBUG) Log.d(TAG, "loading: " + cl);
            try {
                Object newService = SystemUIFactory.getInstance().createInstance(cl);
                mServices[i] = (SystemUI) ((newService == null) ? cl.newInstance() : newService);
            } catch (IllegalAccessException ex) {
                throw new RuntimeException(ex);
            } catch (InstantiationException ex) {
                throw new RuntimeException(ex);
            }

            //啟動服務        
            mServices[i].start();

            //如果啟動完成了
            if (mBootCompleted) {
                mServices[i].onBootCompleted();
            }
        }
        mServicesStarted = true;
    }複製程式碼

這裡以com.android.systemui.statusbar.SystemBars.class為例,講解一下


三、狀態列和導航欄 的啟動

SystemBars的start方法會建立一個ServiceMonitor(服務監聽者),會進入到ServiceMonitor的start方法

 public class SystemBars extends SystemUI implements ServiceMonitor.Callbacks {
 ......
 public void start() {

        //    ServiceMonitor是服務監聽者
        mServiceMonitor = new ServiceMonitor(TAG, DEBUG, mContext, Settings.Secure.BAR_SERVICE_COMPONENT, this);
        mServiceMonitor.start();  // will call onNoService if no remote service is found
    }
 ......   
 }複製程式碼

在ServiceMonitor的start方法啟動

  public class ServiceMonitor {
  ......    
  public void start() {
        ......
        mHandler.sendEmptyMessage(MSG_START_SERVICE);
    }
  ......    
    }複製程式碼

在Handler裡處理這個MSG_START_SERVICE

  public class ServiceMonitor {
  ......    
  private final Handler mHandler = new Handler() {
        public void handleMessage(Message msg) {
            switch(msg.what) {
                case MSG_START_SERVICE:
                    //啟動服務
                    startService();
                    break;
                case MSG_CONTINUE_START_SERVICE:
                    continueStartService();
                    break;
                case MSG_STOP_SERVICE:
                    stopService();
                    break;
                case MSG_PACKAGE_INTENT:
                    packageIntent((Intent)msg.obj);
                    break;
                case MSG_CHECK_BOUND:
                    checkBound();
                    break;
                case MSG_SERVICE_DISCONNECTED:
                    serviceDisconnected((ComponentName)msg.obj);
                    break;
            }
        }
    };
    ......
    }複製程式碼

startService方法如下


 public class ServiceMonitor {
  ......    
 private void startService() {
    //獲取服務元件名稱
    mServiceName = getComponentNameFromSetting();

    //如果為空,回撥服務的onNoService方法
    if (mServiceName == null) {
            mBound = false;
            mCallbacks.onNoService();
        } else {
        //不為空,回撥服務的的onServiceStartAttempt方法
            long delay = mCallbacks.onServiceStartAttempt();
            mHandler.sendEmptyMessageDelayed(MSG_CONTINUE_START_SERVICE, delay);
        }
    }
    ......
    }複製程式碼

這裡對mServiceName是否為空進行判斷,總之無論如何它最終都會啟動這個服務。

回撥SystemBars的onNoService裡建立StatusBar

   public class SystemBars extends SystemUI implements ServiceMonitor.Callbacks {
   ......
 @Override
    public void onNoService() {
    if (DEBUG) Log.d(TAG, "onNoService");
     //建立StatusBar
    createStatusBarFromConfig();  // fallback to using an in-process implementation
    }
   ......複製程式碼
  private void createStatusBarFromConfig() {

        //config_statusBarComponent就是PhoneStatusBar
        final String clsName = mContext.getString(R.string.config_statusBarComponent);

        Class<?> cls = null;
        try {
            cls = mContext.getClassLoader().loadClass(clsName);
        } catch (Throwable t) {
            throw andLog("Error loading status bar component: " + clsName, t);
        }
        try {
            //建立BaseStatusBar例項
            mStatusBar = (BaseStatusBar) cls.newInstance();
        } catch (Throwable t) {
            throw andLog("Error creating status bar component: " + clsName, t);
        }
        mStatusBar.mContext = mContext;
        mStatusBar.mComponents = mComponents;
        //啟動
        mStatusBar.start();

    }複製程式碼

在createStatusBarFromConfig方法裡會獲取一個config_statusBarComponent的字串值,這個值就是PhoneStatusBar的clasName

所以這裡的mStatusBar是PhoneStatusBar例項,啟動了PhoneStatusBar

PhoneStatusBar的start方法

public class PhoneStatusBar extends BaseStatusBar implements DemoMode, DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener,HeadsUpManager.OnHeadsUpChangedListener {
        ......

        public void start() {
         ......

        //呼叫父類的start方法,在父類BaseStatusBar裡呼叫createAndAddWindows方法
        // 3.1
        super.start(); // calls createAndAddWindows()

         ......

        //新增導航欄
        // 3.2
        addNavigationBar();

        ......
    }複製程式碼

它會回撥父類BaseStatusBar 的start方法

3.1、 super.start()

public abstract class BaseStatusBar extends SystemUI implements
        CommandQueue.Callbacks, ActivatableNotificationView.OnActivatedListener,
        ExpandableNotificationRow.ExpansionLogger, NotificationData.Environment,
        ExpandableNotificationRow.OnExpandClickListener,
        OnGutsClosedListener {    

    ......

    public void start() {

    ......

    /*例項化IStatusBarService,
    隨後BaseStatusBar將自己註冊到IStatusBarService之中。以此宣告本例項才是狀態列的真正
        實現者,IStatusBarService會將其所接受到的請求轉發給本例項。
    IStatusBarService會儲存SystemUi的狀態資訊,避免SystemUi崩潰而造成資訊的丟失
    */
        mBarService = IStatusBarService.Stub.asInterface(
                ServiceManager.getService(Context.STATUS_BAR_SERVICE));

        ......

        //IStatusBarService與BaseStatusBar進行通訊的橋樑。
        mCommandQueue = new CommandQueue(this);

     /*switches則儲存了一些雜項:禁用功能列表,SystemUIVisiblity,是否在導航欄中顯示虛擬的
           選單鍵,輸入法視窗是否可見、輸入法視窗是否消費BACK鍵、是否接入了實體鍵盤、實體鍵盤是否被啟用。
          */
        int[] switches = new int[9];

        ArrayList<IBinder> binders = new ArrayList<IBinder>();

          /*它儲存了用於顯示在狀態列的系統狀態
          區中的狀態圖示列表。在完成註冊之後,IStatusBarService將會在其中填充兩個陣列,一個字串
          陣列用於表示狀態的名稱,一個StatusBarIcon型別的陣列用於儲存需要顯示的圖示資源。
          */

        ArrayList<String> iconSlots = new ArrayList<>();
        ArrayList<StatusBarIcon> icons = new ArrayList<>();

        Rect fullscreenStackBounds = new Rect();
        Rect dockedStackBounds = new Rect();

    //IStatusBarService註冊一些資訊
    try {
            mBarService.registerStatusBar(mCommandQueue, iconSlots, icons, switches, binders,
                    fullscreenStackBounds, dockedStackBounds);
        } catch (RemoteException ex) {

        }

        //建立狀態列視窗
        createAndAddWindows();

        ......
        }
    ......
    }複製程式碼

BaseStatusBar進行一些設定,獲取了IStatusBarService例項並註冊一些資訊到IStatusBarService中,IStatusBarService是一個系統服務,BaseStatusBar將自己註冊到IStatusBarService之中,IStatusBarService會把操作狀態列和導航欄的請求轉發給BaseStatusBar

為了保證SystemUI意外退出後不會發生資訊丟失,IStatusBarService儲存了所有需要狀態列與導航欄進行顯示或處理的資訊副本。 在註冊時將一個繼承自IStatusBar.Stub的CommandQueue的例項註冊到IStatusBarService以建立通訊,並將資訊副本取回。

 public class CommandQueue extends IStatusBar.Stub {複製程式碼

IStatusBarService的真身是StatusBarManagerService

路徑:./services/core/java/com/android/server/statusbar/StatusBarManagerService.java

它的註冊方法做一些資料的初始化

public class StatusBarManagerService extends IStatusBarService.Stub {
     ......
 public void registerStatusBar(IStatusBar bar, List<String> iconSlots,
            List<StatusBarIcon> iconList, int switches[], List<IBinder> binders,
            Rect fullscreenStackBounds, Rect dockedStackBounds) {
        //檢查許可權   
        enforceStatusBarService();

        mBar = bar;

        synchronized (mIcons) {
            for (String slot : mIcons.keySet()) {
                iconSlots.add(slot);
                iconList.add(mIcons.get(slot));
            }
        }
        synchronized (mLock) {
            switches[0] = gatherDisableActionsLocked(mCurrentUserId, 1);
            switches[1] = mSystemUiVisibility;
            switches[2] = mMenuVisible ? 1 : 0;
            switches[3] = mImeWindowVis;
            switches[4] = mImeBackDisposition;
            switches[5] = mShowImeSwitcher ? 1 : 0;
            switches[6] = gatherDisableActionsLocked(mCurrentUserId, 2);
            switches[7] = mFullscreenStackSysUiVisibility;
            switches[8] = mDockedStackSysUiVisibility;
            binders.add(mImeToken);
            fullscreenStackBounds.set(mFullscreenStackBounds);
            dockedStackBounds.set(mDockedStackBounds);
        }
    }
    ......
    }複製程式碼

這幾者的關係如下

Android 7.0 SystemUI 之啟動和狀態列和導航欄簡介

回到PhoneStatusBar中, 父類BaseStatusBar中的createAndAddWindows為抽象方法,由子類實現,看下PhoneStatusBar的
createAndAddWindows

  @Override
    public void createAndAddWindows() {
        //新增狀態列視窗
        addStatusBarWindow();
    }複製程式碼

方法實現如下

  private void addStatusBarWindow() {
    //建立控制元件    
        makeStatusBarView();
    //建立StatusBarWindowManager例項    
        mStatusBarWindowManager = new StatusBarWindowManager(mContext);
    //建立遠端輸入控制例項    
        mRemoteInputController = new RemoteInputController(mStatusBarWindowManager,
                mHeadsUpManager);
    //新增狀態列視窗    
        mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
    }複製程式碼

看下makeStatusBarView方法

makeStatusBarView的方法裡呼叫 inflateStatusBarWindow(context)載入佈局

 protected void inflateStatusBarWindow(Context context) {
        mStatusBarWindow = (StatusBarWindowView) View.inflate(context, R.layout.super_status_bar, null);
    }複製程式碼

這裡介紹下佈局

狀態列佈局介紹

整個狀態列的父佈局是R.layout.super_status_bar,對應的是StatusBarWindowView這個自定義佈局.


<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">

    ......

    <!--正常狀態列下的佈局 -->
    <include layout="@layout/status_bar"
        android:layout_width="match_parent"
        android:layout_height="@dimen/status_bar_height" />

    <!--狀態列圖示下的SeekBar佈局 -->
    <include layout="@layout/brightness_mirror" />

    <!--車載模式的佈局  -->
    <ViewStub android:id="@+id/fullscreen_user_switcher_stub"
              android:layout="@layout/car_fullscreen_user_switcher"
              android:layout_width="match_parent"
              android:layout_height="match_parent"/>

    <!--狀態列下拉的佈局  -->
    <include layout="@layout/status_bar_expanded"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="gone" />

</com.android.systemui.statusbar.phone.StatusBarWindowView>複製程式碼

我這裡以主要的佈局層次做介紹,結合圖片分析會更加清楚

StatusBarWindowView裡有幾個主要的佈局

  • layout/status_bar
  • layout/brightness_mirror
  • layout/status_bar_expanded

如下圖

Android 7.0 SystemUI 之啟動和狀態列和導航欄簡介


1.layout/status_bar

這個是正常狀態下(未下拉的狀態列圖示區域)

這個佈局對應的是PhoneStatusBarView

<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:id="@+id/status_bar"
    android:background="@drawable/system_bar_background"
    android:orientation="vertical"
    android:focusable="false"
    android:descendantFocusability="afterDescendants"
    >
    ......
    <!--狀態列  -->
    <LinearLayout android:id="@+id/status_bar_contents"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingStart="6dp"
        android:paddingEnd="8dp"
        android:orientation="horizontal"
        >

        <!-- The alpha of this area is controlled from both PhoneStatusBarTransitions and
             PhoneStatusBar (DISABLE_NOTIFICATION_ICONS). -->
        <!-- 通知圖示區域-->
        <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
            android:id="@+id/notification_icon_area"
            android:layout_width="0dip"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:orientation="horizontal" />

        <!-- 系統圖示區域 -->
        <com.android.keyguard.AlphaOptimizedLinearLayout android:id="@+id/system_icon_area"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:orientation="horizontal"
            >

            <!-- 系統圖示 -->
            <include layout="@layout/system_icons" />

            <!-- 時鐘資訊 -->
            <com.android.systemui.statusbar.policy.Clock
                android:id="@+id/clock"
                android:textAppearance="@style/TextAppearance.StatusBar.Clock"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:singleLine="true"
                android:paddingStart="@dimen/status_bar_clock_starting_padding"
                android:paddingEnd="@dimen/status_bar_clock_end_padding"
                android:gravity="center_vertical|start"
                />
        </com.android.keyguard.AlphaOptimizedLinearLayout>
    </LinearLayout>

</com.android.systemui.statusbar.phone.PhoneStatusBarView>複製程式碼

以下是細節圖,連線表示層次結構


Android 7.0 SystemUI 之啟動和狀態列和導航欄簡介

其中,狀態列的區域分為以下幾種

  • 通知欄圖示,在狀態列的最左側顯示通知資訊,比如來了一個簡訊,那麼就會彈出一個簡訊圖示
  • 時間資訊,顯示一個時間,比如上午9:58
  • 訊號圖示,顯示手機訊號,wifi訊號等
  • 電量圖示,顯示當前電量狀態
  • 狀態圖示,wifi,藍芽等開關狀態

2.@layout/brightness_mirror

這個佈局就是中間那個調整亮度的seekBar.沒啥好介紹的.

Android 7.0 SystemUI 之啟動和狀態列和導航欄簡介


3.@layout/status_bar_expanded

這個佈局是下拉時的狀態列的佈局

<com.android.systemui.statusbar.phone.NotificationPanelView 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:systemui="http://schemas.android.com/apk/res/com.android.systemui"
    android:id="@+id/notification_panel"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/transparent"
    >
    <!--鎖屏時的時鐘佈局  -->
    <include
        layout="@layout/keyguard_status_view"
        android:layout_height="wrap_content"
        android:visibility="gone" />

    <com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="@integer/notification_panel_layout_gravity"
        android:id="@+id/notification_container_parent"
        android:clipToPadding="false"
        android:clipChildren="false">

        <!--quciksetting區域  -->
        <com.android.systemui.AutoReinflateContainer
            android:id="@+id/qs_auto_reinflate_container"
            android:layout="@layout/qs_panel"
            android:layout_width="@dimen/notification_panel_width"
            android:layout_height="match_parent"
            android:layout_gravity="@integer/notification_panel_layout_gravity"
            android:clipToPadding="false"
            android:clipChildren="false" />

        <!-- 通知欄區域 -->
        <com.android.systemui.statusbar.stack.NotificationStackScrollLayout
            android:id="@+id/notification_stack_scroller"
            android:layout_width="@dimen/notification_panel_width"
            android:layout_height="match_parent"
            android:layout_gravity="@integer/notification_panel_layout_gravity"
            android:layout_marginBottom="@dimen/close_handle_underlap" />

        <!--鎖屏切換 -->
        <ViewStub
            android:id="@+id/keyguard_user_switcher"
            android:layout="@layout/keyguard_user_switcher"
            android:layout_height="match_parent"
            android:layout_width="match_parent" />

        <!--鎖屏下的狀態列 -->
        <include
            layout="@layout/keyguard_status_bar"
            android:visibility="invisible" />

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

    <!--鎖屏介面底部的圖示 -->
    <include
            layout="@layout/keyguard_bottom_area"
            android:visibility="gone" />


</com.android.systemui.statusbar.phone.NotificationPanelView><!-- end of sliding panel -->複製程式碼

細節圖


Android 7.0 SystemUI 之啟動和狀態列和導航欄簡介


建立完佈局後,就會新增視窗到WindowManager裡,這樣狀態列就建立完成了.接下來會回到3.2 addNavigationBar()的步驟中.

3.2、addNavigationBar

這個方法是新增底部的導航欄的,就是那些home鍵,back鍵所在的區域.


public class PhoneStatusBar extends BaseStatusBar implements DemoMode, DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener,HeadsUpManager.OnHeadsUpChangedListener {
    ......
    protected void addNavigationBar() {

        if (mNavigationBarView == null) return;
        //初始化導航欄
        prepareNavigationBarView();
        //新增到WindowManager中
        mWindowManager.addView(mNavigationBarView, getNavigationBarLayoutParams());
    }
    ......
    }複製程式碼

在這個方法裡先初始化導航欄,然後把導航欄新增到視窗中.

prepareNavigationBarView()

public class PhoneStatusBar extends BaseStatusBar implements DemoMode, DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener,HeadsUpManager.OnHeadsUpChangedListener {
        ......
 private void prepareNavigationBarView() {
        //重新初始化    
        mNavigationBarView.reorient();
        //最近應用鍵
        ButtonDispatcher recentsButton = mNavigationBarView.getRecentsButton();
        recentsButton.setOnClickListener(mRecentsClickListener);
        recentsButton.setOnTouchListener(mRecentsPreloadOnTouchListener);
        recentsButton.setLongClickable(true);
        recentsButton.setOnLongClickListener(mRecentsLongClickListener);

        //後退鍵
        ButtonDispatcher backButton = mNavigationBarView.getBackButton();
        backButton.setLongClickable(true);
        backButton.setOnLongClickListener(mLongPressBackListener);

        //home鍵
        ButtonDispatcher homeButton = mNavigationBarView.getHomeButton();
        homeButton.setOnTouchListener(mHomeActionListener);
        homeButton.setOnLongClickListener(mLongPressHomeListener);

        //監聽配置改變
        mAssistManager.onConfigurationChanged();
        }
        ......
    }複製程式碼

四、結束

關於SystemUI的狀態列和導航欄就介紹完了,講的很淺顯,只是從整體上梳理了下流程.

相關文章