在前面的视频、文章中咱们介绍完了整个车载Android运用开发所需要的根底知识:
- 【视频文稿】车载Android运用开发与剖析 – 走进车载操作体系 –
- 【视频文稿】车载Android运用开发与剖析 – AOSP的下载与编译 –
- 【视频文稿】车载Android运用开发与剖析 – 开发体系运用 –
- 【视频文稿】车载Android运用开发与剖析 – AIDL实践与封装(上) –
- 【视频文稿】车载Android运用开发与剖析 – AIDL实践与封装(下) –
本期内容开始,咱们将介绍原生Android Automotive中车载运用的完结方式和它的原理。首要要介绍的就是车载运用开发中十分重要的一个体系运用,Android体系的UI – SystemUI
。
因为原生Android体系的SystemUI
代码量很大、内容也十分庞杂,这儿我会挑选出对车载SystemUI
开发具有参阅含义的模块进行介绍,大约会有4-5期的内容,首要分为以下几个模块:
-
SystemUI「功用」和「源代码结构」
-
SystemUI 「导航栏」与「状况栏」的完结原理,
-
SystemUI「告诉栏」与「方便控制」的完结原理
-
SystemUI「近期使命」的完结原理
那么本期内容,咱们先来剖析 SystemUI「功用」与「源代码结构」。阅览本期内容你能够得到以下的收成:
-
了解什么是
SystemUI
-
了解
SystemUI
中首要完结了哪些功用 -
了解
SystemUI
源码的结构 -
了解
SystemUI
是怎么被体系发动的,以及它的初始化时序。
SystemUI 简介
在Android体系中SystemUI
是一个体系级的APP,它供给了体系的用户界面,由system_server
进程发动。
SystemUI
自身不属于system_server
进程,它是一个独立的进程。它的HMI包括了状况栏、导航栏、告诉栏、锁屏、近期使命等等。
SystemServer是一个由Zogyte进程发动的程序,它担任发动和办理Android体系中的各种核心服务。 例如:ActivityManagerService和PackageManagerService,这些服务也都运转在system_server进程中,为Android体系供给各种功用和接口,供运用程序和其他服务调用。咱们常说的Android Framework其实就是由这些Service组成的。
SystemUI 功用介绍
这部分将首要介绍那些对咱们定制自己的SystemUI
时有参阅价值的模块。
- 状况栏
StatusBar,担任显现时刻,电量,信号,告诉等状况信息。
- 导航栏
NavigationBar,显现返回,主页,最近使命等按钮。在车载Android中,咱们多数时分会称为Dock栏(DockBar)。一般担任显现车控、主页、蓝牙电话等常用功用的方便控制和入口。
- 告诉栏
NotificationPanel,显现、控制告诉的界面。实践的车载项目中告诉栏往往会和【音讯中心】合并成一个独立的APP。
- 方便控制
QuickSettings,这个面板能够让用户快速地调整一些常用的设置,例如亮度、飞行形式、蓝牙等。QS面板有多种状况,包括初级展开面板(Quick Quick Settings,QQS)和完整QS面板(Quick Settings,QS)。用户能够经过下拉告诉栏来打开或封闭QS面板。
- 其他功用
一些体系级的对话框、弹窗、动画、屏保等,这些内容相对比较简略,不再介绍了。而锁屏、媒体控制等一些功用,车载SystemUI开发时涉及的不多,也相同不再介绍。
SystemUI 源码结构
SystemUI
的源码方位取决于你运用的Android版本和设备类型,本视频基于Android 13的源码进行剖析。
SystemUI 源码方位与结构
Android 13的SystemUI
的源码坐落**frameworks/base/packages/SystemUI**目录下。
SystemUI
的源码首要由Java和XML文件组成,其间Java文件完结了SystemUI
的各种功用和逻辑,XML文件界说了SystemUI
的界面和资源。SystemUI
的源码还包含了一些测验,东西,文档等辅佐文件。SystemUI
的源码结构如下:
-
animation: 包含了一些动画相关的类和资源。
-
checks: 包含了一些代码检查和格式化的东西。
-
compose: 包含了一些运用Jetpack Compose编写的界面组件。
-
customization: 包含了一些用于定制SystemUI的类和资源。
-
docs: 包含了一些文档和阐明文件。
-
monet: 包含了一些用于完结Material主题的类和资源。
-
plugin: 包含了一些用于完结插件化功用的类和接口。
-
plugin_core: 包含了一些用于支撑插件化功用的根底类和接口。
-
res: 包含了一些通用的资源文件,例如布局,图片,字符串等。
-
res-keyguard: 包含了一些用于锁屏界面的资源文件。
-
res-product: 包含了一些用于特定产品或设备的资源文件。
-
screenshot: 包含了一些用于截屏功用的类和资源。
-
scripts: 包含了一些用于编译或运转SystemUI的脚本文件。
-
shared: 包含了一些用于共享给其他运用或模块的类和接口。
-
src: 包含了SystemUI的首要源码文件,依照功用或模块进行分类,例如statusbar, navigationbar, notification, keyguard, recents等。
-
src-debug: 包含了一些用于调试或测验SystemUI的源码文件。
-
src-release: 包含了一些用于发布或优化SystemUI的源码文件。
-
tests: 包含了一些用于测验或验证SystemUI的源码文件。
-
tools: 包含了一些用于开发或剖析SystemUI的东西文件。
-
unfold: 包含了一些用于支撑折叠屏设备的类和资源。
CarSystemUI 源码结构
车载SystemUI
的源码坐落 **/packages/apps/Car/SystemUI**目录下,CarSystemUI
是对SystemUI
的重用和扩展。CarSystemUI
的源码结构如下:
-
res: 包含了一些通用的资源文件,例如布局,图片,字符串等。
-
res-keyguard: 包含了一些用于锁屏界面的资源文件。
-
samples:包含CarSystemUI的换肤资源,首要是利用了Android的RRO机制。
-
src: 包含了CarSystemUI的首要源码文件,依照功用或模块进行分类,例如statusbar, navigationbar, notification, keyguard, recents等。这些文件中有一些是对SystemUI中同名文件的修正或扩展,有一些是新增的文件,用于完结车载设备特有的功用或逻辑。
- car: 包含了一些用于支撑车载设备特有的功用或逻辑的类和资源,例如CarSystemUIFactory, CarNavigationBarController, CarStatusBarController等。
- wm: 包含了一些用于办理窗口形式和布局的类和资源,例如SplitScreenController, PipController, TaskStackListenerImpl等。
- wmshell: 包含了一些用于供给窗口外壳功用的类和资源,例如WmShellImpl, WmShellModule, WmShellStartableModule等。
- 其他子目录和文件:除了以上三个子目录外,其他子目录和文件基本上与SystemUI中的相同或类似,仅仅有一些针对车载设备的修正或扩展。例如,StatusBar类在车载设备上不显现电池图标,而是显现汽油图标。
-
tests: 包含了一些用于测验或验证CarSystemUI的源码文件
修正、编译 SystemUI
在Android源码的根目录下履行mm SystemUI
,这会编译SystemUI模块及其依靠项。如果你修正了其他模块,例如frameworks/base,也能够履行mm framework-minus-apex
来编译framework模块。
编译完结后,能够运用adb指令将新的SystemUI.apk推送到设备中,并重启SystemUI进程。详细的指令如下:
adb root
adb remount
adb push out/target/product/emulator_x86/system_ext/priv-app/CarSystemUI/CarSystemUI.apk /system_ext/priv-app/CarSystemUI/
adb shell ps -lef | grep systemui
adb shell kill <pid>
如果履行remount指令模拟器呈现read only的提示,需要先封闭模拟器,运用下面的指令发动模拟器。
emulator -writable-system -netdelay none -netspeed full
adb root
adb remount
adb reboot // 重启模拟器
SystemUI 的发动时序
SystemUI
的发动时序是指SystemUI
作为一个体系运用在Android体系发动过程中的加载、初始化流程。
SystemUI 发动流程
当Android体系发动完结后,system_server
进程会经过ActivityManagerService
发动一个名为com.android.systemui.SystemUIService的服务,这个服务是SystemUI
的入口类,它承继了Service类。
SystemServer的源码方位:/frameworks/base/services/java/com/android/server/SystemServer.java
mActivityManagerService.systemReady(() -> {
Slog.i(TAG, "Making services ready");
//...
t.traceBegin("StartSystemUI");
try {
startSystemUi(context, windowManagerF);
} catch (Throwable e) {
reportWtf("starting System UI", e);
}
t.traceEnd();
}, t);
从这儿咱们能够看出,SystemUI
实质就是一个Service,经过Pm获取到的Component是com.android.systemui/.SystemUIService。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();
}
以上就是SystemUI
的发动流程,接下来咱们继续看SystemUI
是怎么初始化的。
SystemUI 初始化流程
SystemUI
的初始化流程分为以下几步:
- Application初始化
SystemUIApplication源码方位:/frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
在SystemUI
发动后,首要会调用Application的onCreate办法,并在onCreate办法中对SystemUI
进行初始化。这儿我把它分为四个部分的内容。
- 榜首部分
@Override
public void onCreate() {
super.onCreate();
Log.v(TAG, "SystemUIApplication created.");
// TimingsTraceLog 是一个用于盯梢代码履行时刻的东西类,它能够在traceview中看到。
TimingsTraceLog log = new TimingsTraceLog("SystemUIBootTiming",Trace.TRACE_TAG_APP);
log.traceBegin("DependencyInjection");
// 此行用于设置Dagger的依靠注入,并应保持在onrecate办法的顶部。
mInitializer = mContextAvailableCallback.onContextAvailable(this);
mSysUIComponent = mInitializer.getSysUIComponent();
// BootCompleteCacheImpl 是一个用于缓存 BOOT_COMPLETED 播送的完结类。
mBootCompleteCache = mSysUIComponent.provideBootCacheImpl();
log.traceEnd();
// 设置主线程Looper的traceTag,这样就能够在traceview中看到主线程的音讯处理状况了。
Looper.getMainLooper().setTraceTag(Trace.TRACE_TAG_APP);
// 设置一切服务承继的运用程序主题。请注意,仅在清单中设置运用程序主题仅适用于活动。请将其与在那里设置的主题同步。
setTheme(R.style.Theme_SystemUI);
...见第二部分
}
榜首部分内容不多,首要是经过Dagger拿到SystemUI中的一些创建好的组件,同时设定一些调试东西。
- 第二部分
首要判别当时进程是否属于体系用户。然后根据SF GPU上下文优先级设置设定SystemUI
的烘托器的上下文优先级,最后敞开SystemServer
的binder调用trace盯梢。
@Override
public void onCreate() {
super.onCreate();
...见榜首部分
// 判别当时进程是否是体系进程。如果是体系进程,那么就注册 BOOT_COMPLETED 播送接收器。
if (Process.myUserHandle().equals(UserHandle.SYSTEM)) {
// 创建 BOOT_COMPLETED 播送接收器的意图过滤器。
IntentFilter bootCompletedFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
bootCompletedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
// 如果SF GPU上下文优先级设置为实时,则SysUI应以高优先级运转。优先级默以为中等。
int sfPriority = SurfaceControl.getGPUContextPriority();
Log.i(TAG, "Found SurfaceFlinger's GPU Priority: " + sfPriority);
if (sfPriority == ThreadedRenderer.EGL_CONTEXT_PRIORITY_REALTIME_NV) {
Log.i(TAG, "Setting SysUI's GPU Context priority to: "+ ThreadedRenderer.EGL_CONTEXT_PRIORITY_HIGH_IMG);
// 设置SysUI的GPU上下文优先级为高。
ThreadedRenderer.setContextPriority(ThreadedRenderer.EGL_CONTEXT_PRIORITY_HIGH_IMG);
// ThreadedRenderer能够简略了解为一个烘托器,它能够在后台线程中烘托视图层次结构。优先级越高,烘托速度越快。
}
// 在system_server上为源自SysUI的调用启用trace盯梢
try {
ActivityManager.getService().enableBinderTracing();
} catch (RemoteException e) {
Log.e(TAG, "Unable to enable binder tracing", e);
}
...见第三部分
} else {
...见第四部分
}
}
ThreadedRenderer
能够简略了解为一个烘托器,它能够在后台线程中烘托视图层次结构。优先级越高,烘托速度越快。关于它详细效果能够参阅: 了解Android硬件加速的小白文 –
Process.myUserHandle()
能够获取当时进程的用户类型。如果是从事移动端APP开发,很少会涉及Android体系的多用户机制。可是因为轿车是一种具有共享特点的东西,会存在多个家庭成员运用一辆车的状况,所以Android多用户在车载Android开发中较为常见。
当咱们在体系设置中的「体系」「多用户」增加一个新用户并切换到这个新用户时,实践上会再发动一个SystemUI
进程,新的SystemUI
进程的用户ID会从U1X开始,原始的SystemUI的用户ID则始终是U0。
有关Android的多用户,能够参阅官方材料:支撑多用户 – Android,之后我也会独自写篇博客阐述Android体系的多用户机制。
- 第三部分
注册监听开机播送,并在SystemUIService
发动后,再告诉SystemUI
中的其它组件「体系发动完结」。
// 注册 BOOT_COMPLETED 播送接收器。
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();
// 判别SystemUIService是否发动
if (mServicesStarted) {
final int N = mServices.length;
for (int i = 0; i < N; i++) {
// 告诉SystemUI中各个组件,体系发动完结。
mServices[i].onBootCompleted();
}
}
}
}, bootCompletedFilter);
// Intent.ACTION_LOCALE_CHANGED 是体系言语发生变化时发送的播送。
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;
// 更新SystemUi告诉通道的称号
NotificationChannels.createAll(context);
}
}
}, localeChangedFilter);
- 第四部分
如果当时用户非体系用户那么调用startSecondaryUserServicesIfNeeded办法。
} else {
// 咱们不需要为正在履行某些使命的子进程初始化组件。例如:截图进程等
String processName = ActivityThread.currentProcessName();
ApplicationInfo info = getApplicationInfo();
if (processName != null && processName.startsWith(info.processName + ":")) {
return;
}
// 对于第二个用户,boot-completed永远不会被调用,因为它已经在发动时为主SystemUI进程播送了
// 对于需要每个用户初始化SystemUI组件的组件,咱们现在为当时非体系用户发动这些组件。
startSecondaryUserServicesIfNeeded();
}
startSecondaryUserServicesIfNeeded办法也是经过startServicesIfNeeded办法来初始化SystemUI
中的功用组件。详细是怎么初始化,咱们之后再来剖析。
void startSecondaryUserServicesIfNeeded() {
// 对startables进行排序,以便咱们取得确认的次序。
Map<Class<?>, Provider<CoreStartable>> sortedStartables = new TreeMap<>(Comparator.comparing(Class::getName));
sortedStartables.putAll(mSysUIComponent.getPerUserStartables());
startServicesIfNeeded(sortedStartables, "StartSecondaryServices", null);
}
到这儿,咱们简略总结一下SystemUIApplication
中其实最首要的作业,其实只有两个:
① 在体系用户空间中监听开机播送,并告诉 SystemUI
的功用组件。
② 在非体系用户空间中,直接初始化 SystemUI
的功用组件。
- 发动 SystemUIService
当Application完结初始化之后,紧接着,SystemUIService就会被发动。
SystemUIService源码方位:/frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIService.java
SystemUIService
在onCreate()办法中会调用((SystemUIApplication) getApplication()).startServicesIfNeeded()办法
@Override
public void onCreate() {
super.onCreate();
// Start all of SystemUI
((SystemUIApplication) getApplication()).startServicesIfNeeded();
...
}
这儿可能有个疑问:为什么不把startServicesIfNeeded的相关逻辑写在Service中,非要写到Application中?
是因为,当时用户不是体系用户时,startSecondaryUserServicesIfNeeded也需要去调用startServicesIfNeeded办法进行组件初始化,所以爽性把一切的初始化逻辑都写到Application中了。
public void startServicesIfNeeded() {
// vendorComponent 是一个字符串,它的值是:com.android.systemui.VendorServices
// com.android.systemui.VendorServices 是一个空类,它的效果是在SysUI发动时,发动一些第三方服务。
final String vendorComponent = mInitializer.getVendorComponent(getResources());
// 对startables进行排序,以便咱们取得确认的次序
// TODO: make #start idempotent and require users of CoreStartable to call it.
Map<Class<?>, Provider<CoreStartable>> sortedStartables = new TreeMap<>(
Comparator.comparing(Class::getName));
sortedStartables.putAll(mSysUIComponent.getStartables());
sortedStartables.putAll(mSysUIComponent.getPerUserStartables());
startServicesIfNeeded(sortedStartables, "StartServices", vendorComponent);
}
- Android 13以前
这个办法会根据配置文件config_systemUIServiceComponents或config_systemUIServiceComponentsPerUser中的界说运用反射来创建、发动一系列SystemUI
的服务,例如StatusBar
, NavigationBar
, NotificationPanel
, Keyguard
等。这些服务每一个都扩展自一个名为SystemUI的接口。
SystemUI
会为他们供给了一个Context,并为他们供给onConfigurationChanged和onBootCompleted的回调。这些服务是SystemUI
的首要组件,担任供给各种功用和界面。
- Android 13今后
增加了一个新的vendorComponent
,vendorComponent 是一个字符串,它的值是:com.android.systemui.VendorServices。VendorServices
承继自CoreStartable
可是内部没有任何完结,google的设计目的是,在SysUI发动时,能够用来发动一些第三方服务。
Android 13以前每个SystemUI
服务还会依靠于Dependency
类供给的自界说依靠注入,来获取一些跨过SystemUI
生命周期的目标。可是Android 13之后,SystemUI
功用组件的创建和依靠注入都是Dagger自动完结。
private void startServicesIfNeeded(Map<Class<?>, Provider<CoreStartable>> startables,String metricsPrefix,String vendorComponent) {
if (mServicesStarted) {
return;
}
mServices = new CoreStartable[startables.size() + (vendorComponent == null ? 0 : 1)];
if (!mBootCompleteCache.isBootComplete()) {
// 检查BOOT_COMPLETED是否已经发送。如果是这样,咱们不需要等候它。
// see ActivityManagerService.finishBooting()
if ("1".equals(SystemProperties.get("sys.boot_completed"))) {
mBootCompleteCache.setBootComplete();
if (DEBUG) {
Log.v(TAG, "BOOT_COMPLETED was already sent");
}
}
}
mDumpManager = mSysUIComponent.createDumpManager();
Log.v(TAG, "Starting SystemUI services for user " + Process.myUserHandle().getIdentifier() + ".");
TimingsTraceLog log = new TimingsTraceLog("SystemUIBootTiming",Trace.TRACE_TAG_APP);
log.traceBegin(metricsPrefix);
int i = 0;
for (Map.Entry<Class<?>, Provider<CoreStartable>> entry : startables.entrySet()) {
String clsName = entry.getKey().getName();
int j = i; // Copied to make lambda happy.
// timeInitialization 记载初始化的时刻
timeInitialization(clsName,
() -> mServices[j] = startStartable(clsName, entry.getValue()),
log,
metricsPrefix);
i++;
}
if (vendorComponent != null) {
timeInitialization(
vendorComponent,
() -> mServices[mServices.length - 1] =
startAdditionalStartable(vendorComponent),
log,
metricsPrefix);
}
for (i = 0; i < mServices.length; i++) {
if (mBootCompleteCache.isBootComplete()) {
mServices[i].onBootCompleted();
}
mDumpManager.registerDumpable(mServices[i].getClass().getName(), mServices[i]);
}
mSysUIComponent.getInitController().executePostInitTasks();
log.traceEnd();
mServicesStarted = true;
}
有关Android13 的SystemUI中Dagger是怎么运用的。能够阅览官方文档:frameworks/base/packages/SystemUI/docs/dagger.md
咱们再来小结一下SystemUIService
的初始化流程,能够概括为以下四步:
①调用SystemUIApplication中的startServicesIfNeeded办法
②startServicesIfNeeded办法经过Dagger获取到创建好的SystemUI的功用组件,并根据包名、类名进行排序。
③顺次调用SystemUI功用组件的start()办法,并记载耗时。
④当接收到BOOT_COMPLETED播送或检查SystemProperty中已经完结开机,则顺次调用 SystemUI
功用组件的onBootCompleted()完结 SystemUI
的初始化。
总结
本期内容咱们简略介绍了Android体系中SystemUI
的功用、源码结构以及发动时序。
最近无论是视频还是博客更新的都很慢,原因其实我在B站发了动态阐明,因为裁员,接下来相当一段时刻不得不多花点时刻在作业上了。
好,感谢你的阅览,期望对你有所帮助,咱们下期内容再见。