天天看點

Android 8.0 SystemUI啟動深入了解SystemUI

最近在學習SystemUI的内容,就網上各種找相關的結合源碼來學習。以下作為最近總結的,作為記錄。

深入了解SystemUI

SystemUIService的啟動

SystemUI大部分功能之間互相獨立。比較特殊的是導航欄和狀态欄,它們運作于一個稱為SystemUIService的一個Service中。是以讨論狀态欄和導航欄的啟動過程就是讨論SystemUIService的啟動。

1.SystemUIService的啟動時機

在負責啟動各種系統服務的ServerThread中,當核心系統服務啟動完成後ServerThread會通過調用ActivityManagerService.systemReady()方法通知AMS系統已經就緒。這個systemReady()擁有一個名為goingCallback的Runnable執行個體作為參數。顧名思義,當AMS完成對systemReady()的處理後将會回調這一Runnable的run()方法。而在這一run()方法中可以找到SystemUI的身影

mActivityManagerService.systemReady(() -> {
                traceBeginAndSlog("StartSystemUI");
        try {
            startSystemUi(context, windowManagerF);
        } catch (Throwable e) {
            reportWtf("starting System UI", e);
        }
        traceEnd();
      }

static final void startSystemUi(Context context, WindowManagerService windowManager) {
    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);
    windowManager.onSystemUiStarted();
           

當核心的系統服務啟動完畢後,ServerThread通過Context.startServiceAsUser()方法完成了SystemUIService的啟動。

  1. SystemUIService的建立

SystemUIService繼承Service,首先看onCreate方法,

public void onCreate() {
    super.onCreate();
    ((SystemUIApplication) getApplication()).startServicesIfNeeded();

    // For debugging RescueParty
    if (Build.IS_DEBUGGABLE && SystemProperties.getBoolean("debug.crash_sysui", false)) {
        throw new RuntimeException();
    }
}
           

它調用SystemUIApplication的startServicesIfNeeded(),代碼如下

**
 * Makes sure that all the SystemUI services are running. If they are already running, this is a
 * no-op. This is needed to conditinally start all the services, as we only need to have it in
 * the main process.
 * <p>This method must only be called from the main thread.</p>
 */

public void startServicesIfNeeded() {
    startServicesIfNeeded(SERVICES);
}
           

接着,

rivate void startServicesIfNeeded(Class<?>[] services) {
    if (mServicesStarted) {
        return;
    }
。。。。
    log.traceBegin("StartServices");
    final int N = services.length;
    for (int i = 0; i < N; i++) {
        Class<?> cl = services[i];
        if (DEBUG) Log.d(TAG, "loading: " + cl);
        log.traceBegin("StartServices" + cl.getSimpleName());
        long ti = System.currentTimeMillis();
        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].mContext = this;
        mServices[i].mComponents = mComponents;
        if (DEBUG) Log.d(TAG, "running: " + mServices[i]);
        mServices[i].start();
        log.traceEnd();

        // Warn if initialization of component takes too long
        ti = System.currentTimeMillis() - ti;
        if (ti > 1000) {
            Log.w(TAG, "Initialization of " + cl.getName() + " took " + ti + " ms");
        }
        if (mBootCompleted) {
            mServices[i].onBootCompleted();
        }
    }
    log.traceEnd();
   ...
}
           

通過for循環,mServicesz中去取SystemUI相關的類。這裡是拿到每個和 SystemUI 相關的類的反射,存到了 service[] 裡,然後指派給cl,緊接着将通過反射将其轉化為具體類的對象,存到了mService[i]數組裡,最後對象調 start() 方法啟動相關類的服務,啟動完成後,回調 onBootCompleted( ) 方法。

mService[i] 裡的值不同時,調用的 start() 方法也不相同。

2 狀态欄與導航欄的建立

進入SystemBar.class,start方法會直接調用createStatusBarFromConfig去建立StatusBar,

@Override
public void start() {
    if (DEBUG) Log.d(TAG, "start");
    createStatusBarFromConfig();
}
           

StatusBar建立過程

private void createStatusBarFromConfig() {
    if (DEBUG) Log.d(TAG, "createStatusBarFromConfig");
    final String clsName = mContext.getString(R.string.config_statusBarComponent);
    if (clsName == null || clsName.length() == 0) {
        throw andLog("No status bar component configured", null);
    }
    Class<?> cls = null;
    try {
        cls = mContext.getClassLoader().loadClass(clsName);
    } catch (Throwable t) {
        throw andLog("Error loading status bar component: " + clsName, t);
    }
    try {
        mStatusBar = (SystemUI) cls.newInstance();
    } catch (Throwable t) {
        throw andLog("Error creating status bar component: " + clsName, t);
    }
    mStatusBar.mContext = mContext;
    mStatusBar.mComponents = mComponents;
    mStatusBar.start();
    if (DEBUG) Log.d(TAG, "started " + mStatusBar.getClass().getSimpleName());
}
           

R.string.config_statusBarComponent的值為

<string name="config_statusBarComponent" translatable="false">com.android.systemui.statusbar.phone.StatusBar</string>
           

然後通過SystemUI建立執行個體mStatusBar = (SystemUI) cls.newInstance();

然後mStatusBar.start();//啟動

在StatusBar.class,start()中首先例化IStatusBarService,

 随後BaseStatusBar将自己注冊到IStatusBarService之中。以此聲明本執行個體才是狀态欄的真正實作者,IStatusBarService會将其所接受到的請求轉發給本執行個體。IStatusBarService會儲存SystemUi的狀态資訊,避免SystemUi崩潰而造成資訊的丢失。

@Override
public void start() {
    ...
//執行個體化IStatusBarService
    mBarService = IStatusBarService.Stub.asInterface(
            ServiceManager.getService(Context.STATUS_BAR_SERVICE));
    // Connect in to the status bar manager service
//IStatusBarService與BaseStatusBar進行通信的橋梁。
    mCommandQueue = getComponent(CommandQueue.class);
    mCommandQueue.addCallbacks(this);
 /*switches則存儲了一些雜項:禁用功能清單,SystemUIVisiblity,是否在導航欄中顯示虛拟的菜單鍵,輸入法視窗是否可見、輸入法視窗是否消費BACK鍵、是否接入了實體鍵盤、實體鍵盤是否被啟用。*/
    int[] switches = new int[9];
    ArrayList<IBinder> binders = new ArrayList<>();
/*它儲存了用于顯示在狀态欄的系統狀态區中的狀态圖示清單。在完成注冊之後,  IStatusBarService将會在其中填充兩個數組,一個字元串數組用于表示狀态的名稱,一個StatusBarIcon類型的數組用于存儲需要顯示的圖示資源。 */
    ArrayList<String> iconSlots = new ArrayList<>();
    ArrayList<StatusBarIcon> icons = new ArrayList<>();
    Rect fullscreenStackBounds = new Rect();
    Rect dockedStackBounds = new Rect();
//注冊
    try {
        mBarService.熱(mCommandQueue, iconSlots, icons, switches, binders,fullscreenStackBounds, dockedStackBounds);
    } catch (RemoteException ex) {
        // If the system process isn't there we're doomed anyway.
    }
   //建立并添加狀态欄視窗
    createAndAddWindows();

    mSettingsObserver.onChange(false); // set up
    mCommandQueue.disable(switches[0], switches[6], false /* animate */);
    setSystemUiVisibility(switches[1], switches[7], switches[8], 0xffffffff,
            fullscreenStackBounds, dockedStackBounds);
    topAppWindowChanged(switches[2] != 0);
    // StatusBarManagerService has a back up of IME token and it's restored here.
    setImeWindowStatus(binders.get(0), switches[3], switches[4], switches[5] != 0);

    // Set up the initial icon state
//建立初始化圖示狀态
    int N = iconSlots.size();
    for (int i=0; i < N; i++) {
        mCommandQueue.setIcon(iconSlots.get(i), icons.get(i));
    }
               icons.size(),
               switches[0],
               switches[1],
               switches[2],
               switches[3]
               ));
    }
 }
           

CommandQueue:繼承自IStatusBar.stub遠端接口,繼承自IStatusBar.Stub,是IStatusBar的服務端,是IStatusBarService與BaseStatusBar進行通信的橋梁。

為了保證SystemUI意外退出後不會發生資訊丢失,IStatusBarService儲存了所有需要狀态欄與導航欄進行顯示或處理的資訊副本。 在注冊時将一個繼承自IStatusBar.Stub的CommandQueue的執行個體注冊到IStatusBarService以建立通信,并将資訊副本取回。

然後看看StatusBarManagerService.class中的registerStatusBar方法。

@Override
public void registerStatusBar(IStatusBar bar, List<String> iconSlots,
        List<StatusBarIcon> iconList, int switches[], List<IBinder> binders,
        Rect fullscreenStackBounds, Rect dockedStackBounds) {
/* 首先是權限檢查。狀态欄與導航欄是Android系統中一個十分重要的元件,是以必須避免其他應用調用此方法對狀态欄與導航欄進行偷梁換柱。是以要求方法的調用者必須具有一個簽名級的權限android.permission.STATUS_BAR_SERVICE*/
    enforceStatusBarService();

    Slog.i(TAG, "registerStatusBar bar=" + bar);
/*  将bar參數儲存到mBar成員中。bar的類型是IStatusBar,它即是BaseStatusBar中的CommandQueue的Bp端。從此之後,StatusBarManagerService将通過mBar與BaseStatusBar進行通信。是以可以了解mBar就是SystemUI中的狀态欄與導航欄 */
    mBar = bar;
    try {
        mBar.asBinder().linkToDeath(new DeathRecipient() {
            @Override
            public void binderDied() {
                mBar = null;
                notifyBarAttachChanged();
            }
        }, 0);
    } catch (RemoteException e) {
    }
    notifyBarAttachChanged();
//将圖示和名稱添加
    synchronized (mIcons) {
        for (String slot : mIcons.keySet()) {
            iconSlots.add(slot);
            iconList.add(mIcons.get(slot));
        }
    }
//switches中的内容
    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);
    }
}
           

接下來是視窗的建立createAndAddWindows();

public void createAndAddWindows() {
    addStatusBarWindow();
}
           

調用addStatusBarWindow

private void addStatusBarWindow() {
//建立控件
    makeStatusBarView();
//建立StatusBarWindowManager執行個體 
    mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
//建立遠端輸入控制執行個體
    mRemoteInputController = new RemoteInputController(mHeadsUpManager);
//添加狀态欄視窗
    mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
}
           

再看makeStatusBarView();

protected void makeStatusBarView() {
...
    inflateStatusBarWindow(context);//初始化StatusBarWindow
}
           

3 了解IStatusBarService

它的實作者是StatusBarManagerService。由于狀态欄導航欄與它的關系十分密切,是以需要對其有所了解。

它的建立方式和其他服務一樣,在Server.Thread中建立。

代碼如下:

SystemServer.java中startOtherServices方法下

if (!disableSystemUI) {
    traceBeginAndSlog("StartStatusBarManagerService");
    try {
       /* 建立一個StatusBarManagerService的執行個體,并注冊到ServiceManager中使其成為
          一個系統服務 */
        statusBar = new StatusBarManagerService(context, wm);
        ServiceManager.addService(Context.STATUS_BAR_SERVICE, statusBar);
    } catch (Throwable e) {
        reportWtf("starting StatusBarManagerService", e);
    }
    traceEnd();
}
           

4 SystemUI的體系結構

SystemUIService,一個普通的Android服務,它以一個容器的角色運作于SystemUI程序中。在它内部運作着多個子服務,其中之一便是狀态欄與導航欄的實作者——BaseStatusBar的子類之一。·  IStatusBarService,即系統服務StatusBarManagerService是狀态欄導航欄向外界提供服務的前端接口,運作于system_server程序中。·  BaseStatusBar及其子類是狀态欄與導航欄的實際實作者,運作于SystemUIService中。·  IStatusBar,即SystemUI中的CommandQueue是聯系StatusBarManagerService與BaseStatusBar的橋梁。·  SystemUI中還包含了ImageWallpaper、RecentPanel以及TakeScreenshotService等功能的實作。它們是Service、Activity等标準的Android應用程式元件,并且互相獨立。對這些功能感興趣的使用者可以通過startService()/startActivity()等方式友善地啟動相應的功能。

5 深入了解狀态欄

作為一個将所有資訊集中顯示的場所,狀态欄對需要顯示的資訊做了以下的五個分類。

·  通知資訊:它可以在狀态欄左側顯示一個圖示以引起使用者的主意,并在下拉卷簾中為使用者顯示更加詳細的資訊。這是狀态欄所能提供的資訊顯示服務之中最靈活的一種功能。它對資訊種類以及來源沒有做任何限制。使用者可以通過StatusBarManagerService所提供的接口向狀态欄中添加或移除一條通知資訊。

·  時間資訊:顯示在狀态欄最右側的一個小型數字時鐘,是一個名為Clock的繼承自TextView的控件。它監聽了幾個和時間相關的廣播:ACTION_TIME_TICK、ACTION_TIME_CHANGED、ACTION_TIMEZONE_CHANGED以及ACTION_CONFIGURATION_CHANGED。當其中一個廣播到來時從Calendar類中擷取目前的系統時間,然後進行字元串格式化後顯示出來。時間資訊的維護工作在狀态欄内部完成,是以外界無法通過API修改時間資訊的顯示或行為。

·  電量資訊:顯示在數字時鐘左側的一個電池圖示,用于提示裝置目前的電量情況。它是一個被BatteryController類所管理的ImageView。BatteryController通過監聽android.intent.action.BATTERY_CHANGED廣播以從BetteryService中擷取電量資訊,并根據電量資訊選擇一個合适的電池圖示顯示在ImageView上。同時間資訊一樣,這也是在狀态欄内部維護的,外界無法幹預狀态欄對電量資訊的顯示行為。

·  信号資訊:顯示在電量資訊的左側的一系列ImageView,用于顯示系統目前的Wifi、移動網絡的信号狀态。使用者所看到的Wifi圖示、手機信号圖示、飛行模式圖示都屬于信号資訊的範疇。它們被NetworkController類維護着。NetworkController監聽了一系列與信号相關的廣播如WIFI_STATE_CHANGED_ACTION、ACTION_SIM_STATE_CHANGED、ACTION_AIRPLANE_MODE_CHANGED等,并在這些廣播到來時顯示、更改或移除相關的ImageView。同樣,外界無法幹預狀态欄對信号資訊的顯示行為。

·  系統狀态圖示區:這個區域用一系列圖示辨別系統目前的狀态,位于信号資訊的左側,與狀态欄左側通知資訊隔岸相望。通知資訊類似,StatusBarManagerService通過setIcon()接口為外界提供了修改系統狀态圖示區的圖示的途徑,而然它對資訊的内容有很強的限制。首先,系統狀态圖示區無法顯示圖示以外的資訊,另外,系統狀态圖示區的對其所顯示的圖示數量以及圖示所表示的意圖有着嚴格的限制。

6 狀态欄視窗的建立與控件樹結構

在StatusBar的addStatusBarWindow方法中,通過

mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());

來建立視窗和布局。getStatusBarHeight擷取狀态欄高度,mStatusBarWindow布局。

StatusBarWindowManager的add方法如下

mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
來建立視窗和布局。getStatusBarHeight擷取狀态欄高度,mStatusBarWindow布局。
StatusBarWindowManager的add方法如下
public void add(View statusBarView, int barHeight) {

    // Now that the status bar window encompasses the sliding panel and its
    // translucent backdrop, the entire thing is made TRANSLUCENT and is
    // hardware-accelerated.
//為狀态欄建立WindowManager.LayoutParams
    mLp = new WindowManager.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT,// 狀态欄的寬度為充滿整個螢幕寬度
            barHeight,//狀态欄的高度
            WindowManager.LayoutParams.TYPE_STATUS_BAR,//視窗類型
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE//狀态欄不接受按鍵事件
 /* FLAG_TOUCHABLE_WHEN_WAKING這一标記将使得狀态欄接受導緻裝置喚醒的觸摸事件。通常這一事件會在interceptMotionBeforeQueueing()的過程中被用于喚醒裝置(或從變暗狀态下恢複),而InputDispatcher會阻止這一事件發送給視窗。*/
                    | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
 // FLAG_SPLIT_TOUCH允許狀态欄支援觸摸事件序列的拆分
                    | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
                    | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
                    | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
            PixelFormat.TRANSLUCENT); // 狀态欄的Surface像素格式為支援透明度
    mLp.token = new Binder();
    mLp.gravity = Gravity.TOP;
    mLp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
    mLp.setTitle("StatusBar");
    mLp.packageName = mContext.getPackageName();
    mStatusBarView = statusBarView;
    mBarHeight = barHeight;
    mWindowManager.addView(mStatusBarView, mLp);
    mLpChanged = new WindowManager.LayoutParams();
    mLpChanged.copyFrom(mLp);
}
           

之後再去總結SystemUI下各子伺服器流程,比如KeyguardView

繼續閱讀