最近在学习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