SystemUI 开发之启动流程(一)

5,232 阅读8分钟

0x00 介绍

SystemUI 是系统应用,其实就是 Android 系统上常见下拉面板,除了通知栏还有很多组件例如快捷键、电量等组件等也是在 SystemUI 中展示。

常见 UI 组件有(包含但不限于,完整列表可以查看 SystemUI 服务组件列表

  • 状态栏 StatusBar
  • 导航栏 NavigationBar
  • 通知栏 NotificationPanel
  • 快捷按键栏 QSPanel
  • 最近任务 Recent
  • 键盘锁 Keyguard

标准 Android 系统中 SystemUI 大概是长这样

当然大多数厂商会根据自身需求对 SystemUI 的样式进行深度定制,例如在我华为手机中的 SystemUI 长这样

通知栏

快捷键栏

0x01 启动流程

启动流程主要包括两个部分

  • framework 中启动 SystemUIService
  • SystemUIService 中启动 SystemUI 所需的各种组件

framework 中的流程

SystemUI 是系统应用,所以它也是一个 APK,有入口 Application,只不过它是由 SystemServer 进程进行启动的。
我们在源码官网上 cs.android.com/ 搜索这个类,看到以下这个路径就是我们要找的类。

frameworks/base/services/java/com/android/server/SystemServer.java

注意本文使用的是 android-12.0.0_r4 分支中的代码

这个就是 SystemServer 进程的入口,它会启动很多系统相关的应用,其中就包括 SystemUI。 找到它的 main 方法

/**
 * The main entry point from zygote.
 */
public static void main(String[] args) {
    new SystemServer().run();

}

从这个方法的注释来看,SystemServer 是由 zygote 进程启动的。 接下来看run() 方法。

private void run(){
    ...
    // 省略代码
    // Start services.
    try {
        t.traceBegin("StartServices");
        startBootstrapServices(t);
        startCoreServices(t);
        startOtherServices(t);
    } catch (Throwable ex) {

            Slog.e("System", "******************************************");
            Slog.e("System", "************ Failure starting system services", ex);
            throw ex;

    } finally {
        t.traceEnd(); // StartServices

    }
    ...
    // 省略代码

}

这个方法很长,根据注释找到 Start Services 的方法,这里有三个启动服务的方法,我们直接进入到 startOtherServices(t) 方法。

为何不看其它方法,因为其它方法我已经看过来,SystemUI 服务的启动就是在 startOtherServices() 里面

/**
 * Starts a miscellaneous grab bag of stuff that has yet to be refactored and organized.
 */    
private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
    ...
    // 省略代码
    t.traceBegin("StartSystemUI");
    try {
        startSystemUi(context, windowManagerF);
    } catch (Throwable e) {
        reportWtf("starting System UI", e);
    }
    t.traceEnd();
    t.traceEnd(); // startOtherServices
}

这个方法也很长,不过根据日志和注释也很容易找到关键方法 startSystemUi()

private static void startSystemUi(Context context, WindowManagerService windowManager) {
    PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
    Intent intent = new Intent();
    intent.setComponent(pm.getSystemUiServiceComponent());    
    intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);

    //Slog.d(TAG, "Starting service: " + intent);        
    context.startServiceAsUser(intent, UserHandle.SYSTEM);        
    windowManager.onSystemUiStarted();
}

可以看到这里通过 PackageManagerInternal.getSystemUiServiceComponent() 获取到 SystemUIService 组件,然后通过 startServiceAsUser 方法启动了服务。

继续在 cs.android.com 中搜索或者直接点击 PackageManagerInternal类,可以进入到以下路径

frameworks/base/services/core/java/android/content/pm/PackageManagerInternal.java

可以看到这是一个抽象类,而且 getSystemUiServiceComponent 也是抽象方法

/**
 * @return The SystemUI service component name.
 */    
public abstract ComponentName getSystemUiServiceComponent();

所以我们要看上面 LocalServices.getService() 方法,点击该方法会跳转到以下目录

frameworks/base/core/java/com/android/server/LocalServices.java

可以看到有两个关键方法 getService()addService()

/**
 * Returns a local service instance that implements the specified interface.
 *
 * @param type The type of service.
 * @return The service object.
 */    
 @SuppressWarnings("unchecked")
 public static <T> T getService(Class<T> type) {

     synchronized (sLocalServiceObjects) {
            return (T) sLocalServiceObjects.get(type);

    }

}
/**
 * Adds a service instance of the specified interface to the global registry of local services.
 */    
public static <T> void addService(Class<T> type, T service) {
    synchronized (sLocalServiceObjects) {
        if (sLocalServiceObjects.containsKey(type)) {
            throw new IllegalStateException("Overriding service registration");
        }
        sLocalServiceObjects.put(type, service);
    }

}

看到这里我们知道这个 PackagerManagerInternal 是从其它地方设置进来的,所以我们回到 PackagerManagerInternal 这个类页面并点击这个类名称,会看到以下页面弹出

看到扩展者 PackageManagerService ,看情况像是我们要找的实现目标,点击该目标进入到 PackageManagerService 这个类。

frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

可以看到里面有一个 PackageManagerInternalImpl 的内部类扩展了 PackageManagerInternal ,在其中找到我们的目标方法

private class PackageManagerInternalImpl extends PackageManagerInternal {
    ...
    // 省略代码
    @Override
    public ComponentName getSystemUiServiceComponent() {
        return ComponentName.unflattenFromString(mContext.getResources().getString(com.android.internal.R.string.config_systemUIServiceComponent));

    }
    // 省略代码
}

可以看到 ComonentName 是从一个内部资源文件中获取到的 com.android.internal.R.string.config_systemUIServiceComponent 这个内部可以在以下路径中搜索到

frameworks/base/core/res/res/values/config.xml

具体定义为

<!-- SystemUi service component -->
<string name="config_systemUIServiceComponent" translatable="false">com.android.systemui/com.android.systemui.SystemUIService</string>

这个 SystemUIService 是在 SystemUI 应用中定义的,所以接下来的流程将转到 SystemUI 应用中来

小结一下

framework 中的 SystemServerrun 方法启动了系统所需要的各种服务,其中就包括 SystemUIService
具体是通过 PackageManagerInternal 获取到 SystemUIService 的配置名称,使用 startServiceAsUser() 来启动。
在这里可以判断由于 SystemUIService 是定义在上层 SystemUI 应用层的,所以这个 serviceexport 属性应该为 true。接下来的流程我们可以验证这个猜想。

在 SystemUI 中的流程

SystemUI 在源码中路径为

frameworks/base/packages/SystemUI/

首先打开 manifest 文件看下 SystemUIService 的配置

    <application
        android:name=".SystemUIApplication"
        android:persistent="true"
        ...
        android:directBootAware="true"
        tools:replace="android:appComponentFactory"
        android:appComponentFactory=".SystemUIAppComponentFactory">
        ...
        // 省略代码
        <!-- Broadcast receiver that gets the broadcast at boot time and starts
             up everything else.
             TODO: Should have an android:permission attribute
             -->
        <service android:name="SystemUIService"
            android:exported="true"
        />
    ...

这里我们知道了三个信息

  • SystemUI 的入口为 SystemUIApplication
  • SystemUIpersistent 应用,即使发生了 crash 系统依然会拉起这个应用
  • 验证了上一节中 SystemUIServiceexported 属性

SystemUIApplication

代码在以下路径

frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java

查看 onCreate 方法

@Override
public void onCreate() {

    super.onCreate();

    Log.v(TAG, "SystemUIApplication created.");
    // This line is used to setup Dagger's dependency injection and should be kept at the
    // top of this method.        
    TimingsTraceLog log = new TimingsTraceLog("SystemUIBootTiming",Trace.TRACE_TAG_APP);
    log.traceBegin("DependencyInjection");
    mContextAvailableCallback.onContextAvailable(this);
    mRootComponent = SystemUIFactory.getInstance().getRootComponent();
    mSysUIComponent = SystemUIFactory.getInstance().getSysUIComponent();
    mComponentHelper = mSysUIComponent.getContextComponentHelper();

    mBootCompleteCache = mSysUIComponent.provideBootCacheImpl();
    log.traceEnd();

    // Set the application theme that is inherited by all services. Note that setting the
    // application theme in the manifest does only work for activities. Keep this in sync with
    // the theme set there.
    setTheme(R.style.Theme_SystemUI);
    if (Process.myUserHandle().equals(UserHandle.SYSTEM)) {
        // 一般情况下走这里来,例如开关机启动系统
        IntentFilter bootCompletedFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
        bootCompletedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);

        // If SF GPU context priority is set to realtime, then SysUI should run at high.
        // The priority is defaulted at medium.            
        int sfPriority = SurfaceControl.getGPUContextPriority();
        Log.i(TAG, "Found SurfaceFlinger's GPU Priority: " + sfPriority);
        if (sfPriority == ThreadedRendererCompat.EGL_CONTEXT_PRIORITY_REALTIME_NV) {

            Log.i(TAG, "Setting SysUI's GPU Context priority to: "+ ThreadedRendererCompat.EGL_CONTEXT_PRIORITY_HIGH_IMG);
         ThreadedRendererCompat.setContextPriority(ThreadedRendererCompat.EGL_CONTEXT_PRIORITY_HIGH_IMG);

        }
        registerReceiver(new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {

                    if (mBootCompleteCache.isBootComplete()) return;

                    if (DEBUG) Log.v(TAG, "BOOT_COMPLETED received");
                    unregisterReceiver(this);
                    mBootCompleteCache.setBootComplete();

                    if (mServicesStarted) {
                        final int N = mServices.length;
                        for (int i = 0; i < N; i++) {
                            mServices[i].onBootCompleted();
                        }
                    }
                }
            }, bootCompletedFilter);            
            IntentFilter localeChangedFilter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED);
            registerReceiver(new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    if (Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) {

                        if (!mBootCompleteCache.isBootComplete()) return;

                        // Update names of SystemUi notification channels                        
                        NotificationChannels.createAll(context);

                    }
                }
            }, localeChangedFilter);
        } else {
            // We don't need to startServices for sub-process that is doing some tasks.
            // (screenshots, sweetsweetdesserts or tuner ..)            
            String processName = ActivityThread.currentProcessName();
            ApplicationInfo info = getApplicationInfo();
            if (processName != null && processName.startsWith(info.processName + ":")) {

                return;
            }

            // For a secondary user, boot-completed will never be called because it has already
            // been broadcasted on startup for the primary SystemUI process.  Instead, for
            // components which require the SystemUI component to be initialized per-user, we
            // start those components now for the current non-system user.
            startSecondaryUserServicesIfNeeded();
        }  
    }

onCreate 方法会做一些初始化操作,然后会有一个判断 Process.myUserHandle().equals(UserHandle.SYSTEM) 所以这里会出现两个分支

  • 如果是系统启动的就会进入到这个分支中,注册监听 boot completed 的通知,最后启动完全启动后就会通知各个组件 onBootCompleted
  • 如果不是系统启动,例如多用户登录使用的情况,这时候系统其实已经启动过了,就会走 else 的分支进入到 startSecondaryUserServicesIfNeeded()用于启动 SystemUI 所需的服务组件,这个分支是根据用户来启动相应的服务的。

注意这里的服务组件并不是四大组件中的 Service ,它们是普通的 Java 类,用来处理各种服务逻辑的。

/**
 * Ensures that all the Secondary user SystemUI services are running. If they are already
 * running, this is a no-op. This is needed to conditionally 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>
 */    
void startSecondaryUserServicesIfNeeded() {
    String[] names = SystemUIFactory.getInstance().getSystemUIServiceComponentsPerUser(getResources());
    startServicesIfNeeded(/* metricsPrefix= */ "StartSecondaryServices", names);

}

在此方法中通过 SystemUIFactory 获取到服务组件名称,然后执行 startServiceIfNeeded() 进行启动

/**
 * Returns the list of system UI components that should be started per user.
 */    
 public String[] getSystemUIServiceComponentsPerUser(Resources resources) {
     return resources.getStringArray(R.array.config_systemUIServiceComponentsPerUser);
}

其中 config_systemUIServiceComponentsPerUser 定义在 看名字可以猜测这个是根据用户来启动的组件列表,目前这里就一个组件

frameworks/base/packages/SystemUI/res/values/config.xml
<!-- SystemUI Services (per user): The classes of the stuff to start for each user. This is a subset of the config_systemUIServiceComponents -->
<string-array name="config_systemUIServiceComponentsPerUser" translatable="false">
    <item>com.android.systemui.util.NotificationChannels</item>
</string-array>

然后就进入到 startServicesIfNeeded(),这里通过反射进行构造后存在一个 mServices 数组里面,并且执行了各个组件的 start() 方法,至此各个组件就完成了启动工作。

private void startServicesIfNeeded(String metricsPrefix, String[] services) {
    if (mServicesStarted) {
        return;
    }
    mServices = new SystemUI[services.length];
    ...
    // 省略代码
    final int N = services.length;
    for (int i = 0; i < N; i++) {
    String clsName = services[i];
    if (DEBUG) Log.d(TAG, "loading: " + clsName);
       log.traceBegin(metricsPrefix + clsName);
       long ti = System.currentTimeMillis();
       try {
           SystemUI obj = mComponentHelper.resolveSystemUI(clsName);
           if (obj == null) {
               Constructor constructor = Class.forName(clsName).getConstructor(Context.class);
               obj = (SystemUI) constructor.newInstance(this);
           }
            mServices[i] = obj;
           } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InstantiationException
 | InvocationTargetException ex) {
               throw new RuntimeException(ex);
           }
           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 " + clsName + " took " + ti + " ms");
           }

           if (mBootCompleteCache.isBootComplete()) {
               mServices[i].onBootCompleted();
           }
           dumpManager.registerDumpable(mServices[i].getClass().getName(), mServices[i]);

        }
        mSysUIComponent.getInitController().executePostInitTasks();
        
        log.traceEnd();
        mServicesStarted = true;
    }

小结一下

SystemUIApplicationonCreate 中做一些初始化工作,如果是系统启动的服务,则进行 boot completed 等通知的注册,然后系统启动完成后,通知到各个组件; 如果是在多用户环境下,从配置文件中获取到一个 com.android.systemui.util.NotificationChannels 组件,然后通过反射构建之后存在 mServices 数组中并启动它。
接下来看 SystemUIService

这个才是真正 Android 中的“服务”

SystemUIService

可以在以下路径找到

frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIService.java

这个类逻辑很简单,就一件事:执行 SystemUIApplication 中的 startServicesIfNeeded() 注意这个是一个没有参数的重载方法

@Override
public void onCreate() {
    super.onCreate();
    // Start all of SystemUI        
    ((SystemUIApplication) getApplication()).startServicesIfNeeded();

    // Finish initializing dump logic
    mLogBufferFreezer.attach(mBroadcastDispatcher);
    // If configured, set up a battery notification
    if (getResources().getBoolean(R.bool.config_showNotificationForUnknownBatteryState)) {
        mBatteryStateNotifier.startListening();
    }
    ...
    // 省略代码     
    }

再次跳转到 SystemUIApplication

/**
 * 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() {
    String[] names = SystemUIFactory.getInstance().getSystemUIServiceComponents(getResources());
    startServicesIfNeeded(/* metricsPrefix= */ "StartServices", names);

}

同样地,在以下路径可以找到所有的 SystemUI 组件定义

frameworks/base/packages/SystemUI/res/values/config.xml

这个是目前 android-12.0.0_r4 分支用到的所有 SystemUI 组件,这里定义了各个组件的类信息

    <!-- SystemUI Services: The classes of the stuff to start. -->
    <string-array name="config_systemUIServiceComponents" translatable="false">
        <item>com.android.systemui.util.NotificationChannels</item>
        <item>com.android.systemui.keyguard.KeyguardViewMediator</item>
        <item>com.android.systemui.recents.Recents</item>
        <item>com.android.systemui.volume.VolumeUI</item>
        <item>com.android.systemui.statusbar.phone.StatusBar</item>
        <item>com.android.systemui.usb.StorageNotification</item>
        <item>com.android.systemui.power.PowerUI</item>
        <item>com.android.systemui.media.RingtonePlayer</item>
        <item>com.android.systemui.keyboard.KeyboardUI</item>
        <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item>
        <item>@string/config_systemUIVendorServiceComponent</item>
        <item>com.android.systemui.util.leak.GarbageMonitor$Service</item>
        <item>com.android.systemui.LatencyTester</item>
        <item>com.android.systemui.globalactions.GlobalActionsComponent</item>
        <item>com.android.systemui.ScreenDecorations</item>
        <item>com.android.systemui.biometrics.AuthController</item>
        <item>com.android.systemui.SliceBroadcastRelayHandler</item>
        <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item>
        <item>com.android.systemui.theme.ThemeOverlayController</item>
        <item>com.android.systemui.accessibility.WindowMagnification</item>
        <item>com.android.systemui.accessibility.SystemActions</item>
        <item>com.android.systemui.toast.ToastUI</item>
        <item>com.android.systemui.wmshell.WMShell</item>
    </string-array>

最终会执行到 startServicesIfNeeded(names) 带参数的方法里面,这个方法在上面已经看过,就是通过反射将各个组件构造后存在 mServices 数组里面。 mServices 是在 SystemUIApplication 中定义的,它保存了所有启动的组件。

/**
 * Hold a reference on the stuff we start.
 */    
private SystemUI[] mServices;

这里还可以看到一个信息就是所有组件都是 SystemUI 这个类的实现。篇幅所限,对各个组件的分析将在之后的文章中说明。
小结一下 SystemServer 启动 SystemUIService 之后,就进入到了应用层中,SystemUIApplicationSystemUI 的入口,在 onCreate 方法中做了一些初始化工作,注册监听通知等操作;如果是多用户则会启动了一个组件 NotificationChannels; 然后就进入到 SystemUIService 中,它在 onCreate 方法中也是执行了 SystemUIApplication 中的 startServicesIfNeeded() 方法,并把所有的服务都存在 mServices 数组中。

0x02 总结

SystemUI 是一个 persistent 应用,它由操作系统启动,主要流程为

  • Android 系统在开机后会创建 SystemServer 进程,它会启动各种系统所需要的服务,其中就包括 SystemUIService
  • SystemUIService 启动后进入到应用层 SystemUI 中,在 SystemUIApplication 它首先会初始化监听boot completed 等通知,待系统完成启动后会通知各个组件 onBootCompleted
  • 在进入 SystemUIService 中依然执行的 SystemUIApplication 中的startServicesIfNeeded() 无参方法启动所有 SystemUI 中的组件。
  • 最终的服务启动逻辑都是在 SystemUIApplication 里面,并且都保存在 mServices 数组中。

0x03 引用