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

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

一、SystemUI 是什麼

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

源碼路徑位於:/framework/base/packages/SystemUI

它負責的功能如下:

  • 狀態欄信息的展示:比如電量信息,時間,wifi狀態等
  • 通知欄消息
  • 壁紙管理
  • 截圖功能
  • 近期任務欄顯示,比如長按home鍵顯示最近使用的app
  • 錄製屏幕功能
  • 截圖服務

以下是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);
        }
    }
    ......
    }

這幾者的關係如下


回到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

如下圖


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>

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


其中,狀態欄的區域分爲以下幾種

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

2.@layout/brightness_mirror

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


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

細節圖



創建完佈局後,就會添加窗口到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的狀態欄和導航欄就介紹完了,講的很淺顯,只是從整體上梳理了下流程.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章