1. 程式人生 > >Android 8.0 SystemUI啟動

Android 8.0 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() 方法也不相同。

7.1.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
}

7.1.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();
}

7.1.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()等方式方便地啟動相應的功能。

7.2 深入理解狀態列

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

·  通知資訊:它可以在狀態列左側顯示一個圖示以引起使用者的主意,並在下拉捲簾中為使用者顯示更加詳細的資訊。這是狀態列所能提供的資訊顯示服務之中最靈活的一種功能。它對資訊種類以及來源沒有做任何限制。使用者可以通過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()介面為外界提供了修改系統狀態圖示區的圖示的途徑,而然它對資訊的內容有很強的限制。首先,系統狀態圖示區無法顯示圖示以外的資訊,另外,系統狀態圖示區的對其所顯示的圖示數量以及圖示所表示的意圖有著嚴格的限制。

7.2.1 狀態列視窗的建立與控制元件樹結構

在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