Android 车载应用开发指南 - SystemUI

一 SystemUI 概述

SystemUI全称System User Interface,直译过来就是系统级用户交互界面 ,在 Android 系统中由SystemUI负责统一管理整个系统层的 UI,它是一个系统级应用程序(APK),源码在/frameworks/base/packages/目录下。

1.1 SystemUI

Android - Phone中SystemUI从源码量看就是一个相当复杂的程序,常见的如:状态栏、消息中心、近期任务、截屏以及一系列功能都是在SystemUI中实现的。

源码位置:/frameworks/base/packages/SystemUI

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

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

原生 Android 系统中 SystemUI 大概是这样

1.2 CarSystemUI

Android-AutoMotive中的SystemUI相对手机中要简单不少,目前商用车载系统中几乎必备的顶部状态栏、消息中心、底部导航栏在原生的Android系统中都已经实现了。

源码位置:frameworks/base/packages/CarSystemUI

虽然CarSystemUISystemUI的源码位置不同,但是二者实际上是复用关系。通过阅读CarSystemUI的 Android.bp 文件可以发现CarSystemUI在编译时把SystemUI以静态库的方式引入进来了。

android.bp 源码位置:/frameworks/base/packages/CarSystemUI/Android.bp

java 复制代码
android_library {
    name: "CarSystemUI-core",
    ...
    static_libs: [
        "SystemUI-core",
        "SystemUIPluginLib",
        "SystemUISharedLib",
        "SystemUI-tags",
        "SystemUI-proto",
        ...
    ],
    ...
}

二 SystemUI 启动流程

System UI的启动大致可分为以下两个流程:

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

说明:本文源码分析基于版本:android-12.0.0_r3

2.1 Framework 中的流程

SystemUI 是系统应用,所以它也是一个 APK,有入口 Application,只不过它是由 system_server 进程直接启动的。

关于SystemServer,它是 Android framework 中关键系统的服务,由 Android 系统最核心的进程Zygotefork 生成,进程名为system_server。常见的ActivityManagerServicePackageManagerServiceWindowManageService都是由SystemServer启动的。

SystemServer 源码路径:/frameworks/base/services/java/com/android/server/SystemServer.java

第一步: SystemServermain() 方法中调用 SystemServer.run()run()中调用startOtherServices()

java 复制代码
public static void main(String[] args) {
    new SystemServer().run();
}
java 复制代码
private void run() {
    ... ...
    // Start services.
    try {
        startBootstrapServices(t);
        startCoreServices(t);
        startOtherServices(t); //SystemServer在startOtherServices()被启动
     } 
    ... ...
}

第二步: startOtherServices()中通过AMS的回调方法ready(),然后调用startSystemUi()

java 复制代码
    mActivityManagerService.systemReady(() -> {
        ... ...  
        try {
            startSystemUi(context, windowManagerF);
        } catch (Throwable e) {
            reportWtf("starting System UI", e);
        }
        ... ...
    ,t);  

第三步: startSystemUi()中可以看出,SystemUI本质就是一个Service,通过PM获取到的Componentcom.android.systemui/.SystemUIService,然后通过调用context.startServiceAsUser()完成对SystemUIService的启动。

java 复制代码
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();
    }

第四步: SystemUIService 依附于SystemUI应用,所以SystemUIService启动前需要完成SystemUI整个应用的启动,其流程也就是应用常见的冷启动流程,这里展开讲一下:

SystemUI 应用启动流程

context中的startServiceAsUser()是一个抽象方法,具体实现在ContextImpl.java里。实现方法startServiceCommon()中,通过ActivityManager.getService()就会走到AMS中,最终在AMS来启动SystemUIService

java 复制代码
@Override
public ComponentName startServiceAsUser(Intent service, UserHandle user) {
    return startServiceCommon(service, false, user);
}

@Override
private ComponentName startServiceCommon(Intent service, boolean requireForeground,
                                         UserHandle user) {
    try {
        validateServiceIntent(service);
        service.prepareToLeaveProcess(this);
        ComponentName cn = ActivityManager.getService().startService(  //在AMS中开启Service
            mMainThread.getApplicationThread(), service,
            service.resolveTypeIfNeeded(getContentResolver()), requireForeground,
            getOpPackageName(), getAttributionTag(), user.getIdentifier());
        ... ...  
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}

接下来进入AMS,一探究竟:

AMS 源码路径:/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

AMSstartService()方法里,会经过一系列内部流程,调用到bringUpServiceLocked()方法。

java 复制代码
@Override
public ComponentName startService(IApplicationThread caller, Intent service,
        String resolvedType, boolean requireForeground, String callingPackage,
        String callingFeatureId, int userId)
        throws TransactionTooLargeException {
  		... ... 
        try {
            res = mServices.startServiceLocked(caller, service,
                    resolvedType, callingPid, callingUid,
                    requireForeground, callingPackage, callingFeatureId, userId); // 内部调用到 startServiceLocked()
        }
      	... ...
    }
}

ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType, int callingPid, int callingUid, boolean fgRequired, String callingPackage,
        @Nullable String callingFeatureId, final int userId,
        boolean allowBackgroundActivityStarts) throws TransactionTooLargeException {
    ... ... 
    if (caller != null) {
        // 这里记录app的进程信息
        final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller);
    ... ...
    ComponentName cmp = startServiceInnerLocked(smap, service, r, callerFg, addToStarting); //内部调用到startServiceInnerLocked()
    ... ... 
    return cmp;
}
    
ComponentName startServiceInnerLocked(ServiceMap smap, Intent service, ServiceRecord r, boolean callerFg, boolean addToStarting) throws TransactionTooLargeException {
    ... ... 
    String error = bringUpServiceLocked(r, service.getFlags(), callerFg, false, false); //调用到bringUpServiceLocked()
    if (error != null) {
        return new ComponentName("!!", error);
    }
    ... ... 
    return r.name;
}

继续调用了bringUpServiceLocked()方法,

java 复制代码
private String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg, boolean whileRestarting, boolean permissionsReviewRequired)
        throws TransactionTooLargeException {
    ... ... 
    if (!isolated) {
        app = mAm.getProcessRecordLocked(procName, r.appInfo.uid, false);
        if (DEBUG_MU) Slog.v(TAG_MU, "bringUpServiceLocked: appInfo.uid=" + r.appInfo.uid + " app=" + app);
        //如果service进程存在
        if (app != null && app.thread != null) {
            try {
                app.addPackage(r.appInfo.packageName, r.appInfo.longVersionCode, mAm.mProcessStats);
                //启动service
                realStartServiceLocked(r, app, execInFg);
                return null;
            } catch (TransactionTooLargeException e) {
                throw e;
            } catch (RemoteException e) {
                Slog.w(TAG, "Exception when starting service " + r.shortInstanceName, e);
            }
        }
    }
    ... ... 

    // 如果不存在此进程
    if (app == null && !permissionsReviewRequired) {
        // 启动运行的线程
        if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,
                hostingRecord, ZYGOTE_POLICY_FLAG_EMPTY, false, isolated, false)) == null) {
            String msg = "Unable to launch app "
                    + r.appInfo.packageName + "/"
                    + r.appInfo.uid + " for service "
                    + r.intent.getIntent() + ": process is bad";
            Slog.w(TAG, msg);
            bringDownServiceLocked(r);
            return msg;
        }
    }
    ... ... 
    return null;
}

这个方法做了两件事:

  1. 如果SystemUIService所属进程已经存在,则直接调用realStartServiceLocked()
  2. 如果SystemUIService所属进程不存在,则执行startProcessLocked()方法创建进程,经过层层调用,最终也会走到realStartServiceLocked()中:
java 复制代码
private final void realStartServiceLocked(ServiceRecord r,
        ProcessRecord app, boolean execInFg) throws RemoteException {
    ... ... 
    try {
    ... ... 
        app.thread.scheduleCreateService(r, r.serviceInfo,
                mAm.compatibilityInfoForPackage(r.serviceInfo.applicationInfo),
                app.getReportedProcState());
        r.postNotification();
        created = true;
    }
}

这个方法内部调用了app.thread.scheduleCreateService(),而app.thread是一个IApplicationThread类型的,他的实现是ActivityThread的一个内部类ApplicationThread,而这个类正好实现了IApplicationThread.Stub在ApplicationThread类中,找到对应的调用方法:

java 复制代码
public final void scheduleCreateService(IBinder token,
        ServiceInfo info, CompatibilityInfo compatInfo, int processState) {
    updateProcessState(processState, false);
    CreateServiceData s = new CreateServiceData();
    s.token = token;
    s.info = info;
    s.compatInfo = compatInfo;

    sendMessage(H.CREATE_SERVICE, s);
}

可以看出,是发送一个消息给Handler,这个HandlerActivityThread的内部类H

java 复制代码
public void handleMessage(Message msg) {
    switch (msg.what) {
        ... ... 
        case CREATE_SERVICE:
            if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                        ("serviceCreate: " + String.valueOf(msg.obj)));
            }
            handleCreateService((CreateServiceData)msg.obj);
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            break;
        ... ... 
    }

}

最终调用了handleCreateService()方法:

java 复制代码
private void handleCreateService(CreateServiceData data) {

    LoadedApk packageInfo = getPackageInfoNoCheck(
            data.info.applicationInfo, data.compatInfo);
    Service service = null;
    try {
        //创建service的context
        ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
        //创建Application
        Application app = packageInfo.makeApplication(false, mInstrumentation);
        //获取类加载器
        java.lang.ClassLoader cl = packageInfo.getClassLoader();
        //加载service实例
        service = packageInfo.getAppFactory()
                .instantiateService(cl, data.info.name, data.intent);
        // Service resources must be initialized with the same loaders as the application
        // context.
        context.getResources().addLoaders(
                app.getResources().getLoaders().toArray(new ResourcesLoader[0]));

        context.setOuterContext(service);
        //初始化service
        service.attach(context, this, data.info.name, data.token, app,
                ActivityManager.getService());
        //调用service的onCreate方法
        service.onCreate();
        mServices.put(data.token, service);
        try {
            //通过serviceDoneExecuting告知AMS,service已经启动完成
            ActivityManager.getService().serviceDoneExecuting(
                    data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    } catch (Exception e) {
        if (!mInstrumentation.onException(service, e)) {
            throw new RuntimeException(
                "Unable to create service " + data.info.name
                + ": " + e.toString(), e);
        }
    }
}

这个方法主要做了以下几件事:

  1. 首先,创建上下文
  2. 创建SystemUIApplication,获取类加载器
  3. 加载SystemUIService实例,初始化SystemUIService, 调用onCreate()方法
  4. 最后通知AMSSystemUIService启动完成。

到这里SystemUIService已经启动完成。

第五步: 前面在SystemUIApplication创建成功后会回调内部的OnCreate()方法,在OnCreate()中方法注册了一个开机广播,当接收到开机广播后会调用SystemUIonBootCompleted()方法来告诉每个子模块 Android 系统已经完成开机。

java 复制代码
@Override
    public void onCreate() {
        super.onCreate();
        Log.v(TAG, "SystemUIApplication created.");
        // 设置所有服务继承的应用程序主题。
        // 请注意,在清单中设置应用程序主题仅适用于activity。这里是让Service保持与主题设置同步。
        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);
            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(); //通知SystemUI子模块
                        }
                    }
                }
            }, bootCompletedFilter);
               ...
        } else {
            // 我们不需要为正在执行某些任务的子进程启动服务。
           ...
        }
    }

2.2 SystemUI 中的流程

第六步: SystemUIService初始化完成后会调用onCreate()方法,onCreate()中调用了SystemUIApplication中的startServiceIfNeeded()方法完成SystemUI子模块的初始化。

SystemUIService 源码位置:/frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIService.java

java 复制代码
public class SystemUIService extends Service {
    ... ...
 
    @Override
    public void onCreate() {
        super.onCreate();
 
        // Start all of SystemUI
        ((SystemUIApplication) getApplication()).startServicesIfNeeded(); //调用startServicesIfNeeded()
 
        ... ...
    }
}

第七步:SystemUIApplicationstartServicesIfNeeded()方法中,通过SystemUIFactory获取到配置在config.xml中每个子模块的className

SystemUIApplication 源码位置:/frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java

java 复制代码
// SystemUIApplication
public void startServicesIfNeeded() {
    String[] names = SystemUIFactory.getInstance().getSystemUIServiceComponents(getResources());
    startServicesIfNeeded("StartServices", names);
}

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

config.xml 位置:/frameworks/base/packages/SystemUI/res/values/config.xml

xml 复制代码
<!-- 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.stackdivider.Divider</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.pip.PipUI</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.SizeCompatModeActivityController</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>
     </string-array>

第八步:startServicesIfNeeded()中通过反射完成了每个SystemUI组件的创建,然后再调用各个SystemUIonStart()方法来继续执行子模块的初始化。

java 复制代码
private SystemUI[] mServices;

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);
        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]);
        // 调用各个子模块的start()
        mServices[i].start();
        // 首次启动时,这里始终为false,不会被调用
        if (mBootCompleteCache.isBootComplete()) {
            mServices[i].onBootCompleted();
        }
    }
    mServicesStarted = true;
}

这里的SystemUI是一个抽象类,状态栏、近期任务等等模块都是继承自SystemUI,通过这种方式可以很大程度上简化复杂的SystemUI程序中各个子模块创建方式,同时我们可以通过配置资源的方式动态加载需要的SystemUI模块。

SystemUI的源码如下,方法基本都能见名知意,就不再介绍了。

java 复制代码
public abstract class SystemUI implements Dumpable {
    protected final Context mContext;

    public SystemUI(Context context) {
        mContext = context;
    }

    public abstract void start();

    protected void onConfigurationChanged(Configuration newConfig) {
    }

    @Override
    public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
    }

    protected void onBootCompleted() {
    }

2.3 CarSystemUI 的启动流程

前文提到CarSystemUI复用了手机SystemUI的代码,所以CarSystemUI的启动流程和SystemUI的是完全一致的。

CarSystemUI中需要的功能与SystemUI中也有部分差异,那么是这些差异化的功能是如何引入并完成初始化?以及一些手机的SystemUI才需要的功能是如何去除的呢?

其实很简单,在SystemUI的启动流程中我们得知,各个子模块的 className 是通过SystemUIFactorygetSystemUIServiceComponents()获取到的,那么只要继承SystemUIFactory并重写getSystemUIServiceComponents()就可以了。

java 复制代码
public class CarSystemUIFactory extends SystemUIFactory {

    @Override
    protected SystemUIRootComponent buildSystemUIRootComponent(Context context) {
        return DaggerCarSystemUIRootComponent.builder()
                .contextHolder(new ContextHolder(context))
                .build();
    }

    @Override
    public String[] getSystemUIServiceComponents(Resources resources) {
        Set<String> names = new HashSet<>();
        // 先引入systemUI中的components
        for (String s : super.getSystemUIServiceComponents(resources)) {
            names.add(s);
        }
        // 再移除CarsystemUI不需要的components
        for (String s : resources.getStringArray(R.array.config_systemUIServiceComponentsExclude)) {
            names.remove(s);
        }
        // 最后再添加CarsystemUI特有的components
        for (String s : resources.getStringArray(R.array.config_systemUIServiceComponentsInclude)) {
            names.add(s);
        }

        String[] finalNames = new String[names.size()];
        names.toArray(finalNames);

        return finalNames;
    }
}
xml 复制代码
<!-- 需要移除的Components. -->
    <string-array name="config_systemUIServiceComponentsExclude" translatable="false">
        <item>com.android.systemui.recents.Recents</item>
        <item>com.android.systemui.volume.VolumeUI</item>
        <item>com.android.systemui.stackdivider.Divider</item>
        <item>com.android.systemui.statusbar.phone.StatusBar</item>
        <item>com.android.systemui.keyboard.KeyboardUI</item>
        <item>com.android.systemui.pip.PipUI</item>
        <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item>
        <item>com.android.systemui.LatencyTester</item>
        <item>com.android.systemui.globalactions.GlobalActionsComponent</item>
        <item>com.android.systemui.SliceBroadcastRelayHandler</item>
        <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item>
        <item>com.android.systemui.accessibility.WindowMagnification</item>
        <item>com.android.systemui.accessibility.SystemActions</item>
    </string-array>

    <!-- 新增的Components. -->
    <string-array name="config_systemUIServiceComponentsInclude" translatable="false">
        <item>com.android.systemui.car.navigationbar.CarNavigationBar</item>
        <item>com.android.systemui.car.voicerecognition.ConnectedDeviceVoiceRecognitionNotifier</item>
        <item>com.android.systemui.car.window.SystemUIOverlayWindowManager</item>
        <item>com.android.systemui.car.volume.VolumeUI</item>
    </string-array>

通过以上方式,就完成了CarSystemUI子模块的替换。

2.4 小结

总结一下,SystemUI的大致启动流程可以归纳如下:

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

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

三 总结

SystemUI在原生的车载 Android 系统是一个较为复杂的模块,本文主要介绍了SystemUICarSystemUI的功能、源码结构及启动时序,希望能帮到从事SystemUI开发的同学。

四 参考文档

Android 车载应用开发与分析(12) - SystemUI (一)

相关推荐
水瓶丫头站住17 分钟前
安卓APP如何适配不同的手机分辨率
android·智能手机
桂月二二24 分钟前
探索前端开发中的 Web Vitals —— 提升用户体验的关键技术
前端·ux
xvch1 小时前
Kotlin 2.1.0 入门教程(五)
android·kotlin
hunter2062062 小时前
ubuntu向一个pc主机通过web发送数据,pc端通过工具直接查看收到的数据
linux·前端·ubuntu
qzhqbb2 小时前
web服务器 网站部署的架构
服务器·前端·架构
刻刻帝的海角2 小时前
CSS 颜色
前端·css
浪浪山小白兔3 小时前
HTML5 新表单属性详解
前端·html·html5
lee5763 小时前
npm run dev 时直接打开Chrome浏览器
前端·chrome·npm
2401_897579653 小时前
AI赋能Flutter开发:ScriptEcho助你高效构建跨端应用
前端·人工智能·flutter
limit for me4 小时前
react上增加错误边界 当存在错误时 不会显示白屏
前端·react.js·前端框架