车载Android应用开发与分析 - 初试 SystemUI Plugin
在前面的视频、文章中我们介绍完了整个车载Android应用开发所需要的基础知识:
- 【视频文稿】车载Android应用开发与分析 - 走进车载操作系统 - 掘金
- 【视频文稿】车载Android应用开发与分析 - AOSP的下载与编译 - 掘金
- 【视频文稿】车载Android应用开发与分析 - 开发系统应用 - 掘金
- 【视频文稿】车载Android应用开发与分析 - AIDL实践与封装(上) - 掘金
- 【视频文稿】车载Android应用开发与分析 - AIDL实践与封装(下) - 掘金
本期内容,我们介绍原生Android Automotive中车载应用的实现方式和它的原理。首先要介绍的就是车载应用开发中非常重要的一个系统应用,Android系统的UI - SystemUI
。
由于原生Android系统的SystemUI
代码量很大、内容也非常庞杂,这里我会挑选出对车载SystemUI
开发具有参考意义的模块进行介绍,大约会有4-5期的内容,主要分为以下几个模块:
- 车载Android应用开发与分析 - SystemUI 「功能」与「源码结构」分析 - 掘金
- 车载Android应用开发与分析 - 初试 SystemUI Plugin
SystemUI的源代码可能是所有Android原生应用中最复杂的一个,当我们需要定制SystemUI时,庞大的源码量会对的定制化开发带来巨大的潜在风险。所以目前车载SystemUI常见的做法就是,从原生SystemUI中移植少量必须的源码,然后从头定制一个源码、功能完全可控的SystemUI。
重新开发一个SystemUI就是唯一的选项吗?当然不是!Google官方早就注意到了这个问题,所以SystemUI中提供插件化的开发方式 - SystemUI Plugin。
本文源码地址:/frameworks/base/+/refs/heads/main/packages/SystemUI/plugin/ExamplePlugin/
本文源码环境基于Android 13
SystemUI Plugin
SystemUI plugin机制是一种让SystemUI的功能可以被动态替换或修改的方法,它可以让开发者快速创建和迭代SystemUI的原型,而尽可能少的修改SystemUI的主框架。
注意:使用Plugin并不能保证我们完全不需要修改SystemUI的主框架,毕竟需求永远是多变的。
Plugin Hooks
Plugin hooks是一些预定义的插件接口,它们可以让应用实现一些特定的功能,并通过Intent和注解来注册和声明插件的类型和版本。Plugin hooks有多种类型,例如OverlayPlugin, QSFactory, VolumeDialog等,每种类型都有一个对应的action和expected interface,用于标识插件的功能和要求。
Android 13中Plugin hooks预定义接口主要有以下几种:
- BcSmartspaceDataPlugin:这个plugin可以让应用提供自定义的数据给锁屏界面上的智能空间(BcSmartspace),例如天气、日历、新闻等。
- ClockProviderPlugin:这个plugin可以让应用提供自定义的时钟样式给锁屏界面和始终应用。
- DozeServicePlugin:这个plugin可以让应用自定义Doze模式的行为,例如控制屏幕亮度、显示内容、传感器等。
- FalsingPlugin:这个plugin可以让应用自定义对误触(Falsing)事件的检测和处理,例如判断用户是否真的想滑动通知栏或解锁屏幕等。
- GlobalActions:这个plugin可以让应用自定义全局操作(GlobalActions)对话框的外观和行为,例如添加新的操作按钮或改变对话框样式。
- GlobalActionsPanelPlugin:这个plugin可以让应用在全局操作对话框中添加一个可展开的面板,用于显示更多的操作选项或信息。
- IntentButtonProvider:这个plugin可以让应用在锁屏界面上添加一个自定义的按钮,用于启动一个指定的Intent。
- NavigationEdgeBackPlugin:这个plugin可以让应用自定义导航栏边缘返回(NavigationEdgeBack)手势的行为,例如改变触发区域或动画效果。
- NotificationListenerController:这个plugin可以让应用控制通知监听器(NotificationListener)服务的连接和断开,以及获取通知事件和数据。
- NotificationMenuRowPlugin:这个plugin可以让应用自定义通知菜单栏(NotificationMenuRow)的外观和行为,例如添加新的菜单项或改变菜单样式。
- NotificationPersonExtractorPlugin:这个plugin可以让应用自定义从通知中提取人物信息(NotificationPersonExtractor)的逻辑,例如识别通知中包含的联系人或头像等。
- OverlayPlugin:这个plugin可以让应用自定义覆盖在通知栏上方的视图(OverlayView),用于显示一些额外的内容或功能。
- PluginFragment:这个plugin可以让应用在SystemUI中嵌入一个Fragment,用于显示一些自定义的界面或功能。
- QSFactory:这个plugin可以让应用提供自定义的快速设置工厂(QSFactory),用于创建快速设置图块或面板。
- SensorManagerPlugin:这个plugin可以让应用使用SensorManager服务来注册和取消注册传感器监听器,以及获取传感器事件和数据。
- ToastPlugin:这个plugin可以让应用自定义Toast消息(Toast)的外观和行为,例如改变Toast位置或持续时间等。
- ViewProvider:这个plugin可以让应用提供一个自定义的视图(View),用于替换SystemUI中某些组件或功能。
- VolumeDialog:这个plugin可以让应用自定义音量调节对话框(VolumeDialog)的外观和行为,例如添加新的音量控制选项或改变音量条的样式。
Plugin 上手
创建一个AndroidStudio的SystemUI plugin项目,可以参考以下的步骤:
1)编译SystemUIPluginLib.jar
使用Plugin之前我们需要编译出SystemUIPluginLib.jar
,在AOSP源码根目录执行下面的指令。
make SystemUIPluginLib
然后就可以在下面的目录中得到SystemUIPluginLib.jar
out/target/product/emulator_x86/obj/JAVA_LIBRARIES/SystemUIPluginLib_intermediates/javalib.jar
在AOSP的文档中建议使用 frameworks/base/packages/SystemUI/plugin/update_plugin_lib.sh 脚本编译 SystemUIPluginLib.jar,不过我编译时出现了环境配置问题。
2)配置系统签名
在build.gradle中配置系统签名。
android {
...
signingConfigs {
sign {
storeFile file('system.keystore')
storePassword '123456'
keyAlias 'cardemo'
keyPassword '123456'
}
}
buildTypes {
release {
minifyEnabled false
signingConfig signingConfigs.sign
}
debug {
minifyEnabled false
signingConfig signingConfigs.sign
}
}
}
关于如何制作系统签名,请参考:车载Android应用开发与分析 - 开发系统应用 - 掘金
3)创建一个Plugin
在plugin项目中定义一个类,实现自Plugin中已经提供的各种插件,并使用Requires注解声明target和version字段,这些字段用于标识插件的类型和版本。
@Requires(target = OverlayPlugin.class, version = OverlayPlugin.VERSION)
public class SampleOverlayPlugin implements OverlayPlugin {
private static final String TAG = "SampleOverlayPlugin";
private Context mPluginContext;
private View mStatusBarView;
private View mNavBarView;
@Override
public void onCreate(Context sysuiContext, Context pluginContext) {
Log.d(TAG, "onCreate");
mPluginContext = pluginContext;
}
@Override
public void onDestroy() {
if (mInputSetup) {
mStatusBarView.getViewTreeObserver().removeOnComputeInternalInsetsListener(
onComputeInternalInsetsListener);
}
Log.d(TAG, "onDestroy");
if (mStatusBarView != null) {
mStatusBarView.post(() -> ((ViewGroup) mStatusBarView.getParent()).removeView(mStatusBarView));
}
if (mNavBarView != null) {
mNavBarView.post(() -> ((ViewGroup) mNavBarView.getParent()).removeView(mNavBarView));
}
}
@Override
public void setup(View notificationShadeWindowView, View navBar) {
Log.d(TAG, "Setup");
if (notificationShadeWindowView instanceof ViewGroup) {
mStatusBarView = LayoutInflater.from(mPluginContext)
.inflate(R.layout.colored_overlay, (ViewGroup) notificationShadeWindowView, false);
((ViewGroup) notificationShadeWindowView).removeAllViews();
((ViewGroup) notificationShadeWindowView).addView(mStatusBarView);
}
if (navBar instanceof ViewGroup) {
mNavBarView = LayoutInflater.from(mPluginContext)
.inflate(R.layout.colored_overlay, (ViewGroup) navBar, false);
((ViewGroup) navBar).removeAllViews();
((ViewGroup) navBar).addView(mNavBarView);
}
}
}
注意:Android不同版本中SystemUI的代码存在不小的差异,例如:Android13中setup(View statusBar, View navBar)中返回的statusBar实际上是NotificationShadeWindowView。
4)注册Plugin
在plugin项目的AndroidManifest中注册一个service,使用action和permission属性指定插件的接口和权限,这样SystemUI就可以通过Intent找到插件。
<uses-permission android:name="com.android.systemui.permission.PLUGIN" />
<application>
<service
android:name=".SampleOverlayPlugin"
android:exported="false"
android:label="@string/plugin_label"
tools:ignore="Instantiatable">
<intent-filter>
<action android:name="com.android.systemui.action.PLUGIN_OVERLAY" />
</intent-filter>
</service>
</application>
的name可以在我们实现的plugin接口中找到。
SystemUI为了保证系统安全,对于plugin的加载,构筑了两道防线:
第一道防线是Build.IS_DEBUGGABLE检查。SysUI 在扫描或加载设备上的任何插件之前,会检查Build.IS_DEBUGGABLE,以确保构建是可调试的。
第二道防线是就是签名权限。所有插件都必须被系统签名且持有com.android.systemui.permission.PLUGIN
权限才能加载其任何代码,否则将记录违规行为,并忽略插件。
5)运行Plugin
将plugin.apk push 到Android 13 模拟器的/system/priv-app/ 目录下,重启。可以看到如下的效果:
- NavBar的所有子View被移除,并添加了一个红色的View;
- NotificationShadeWindowView的所有子View被移除,并添加了一个红色的View。
总结
本文初试了SystemUI插件机制,在编写本文时发现Plugin相关的资料少的可怜,即使是官方资料有的也过时了。所以就像标题那样,本文只是简单尝试了Plugin,如何使用Plugin来详细定制一个完全符合我们需求的SystemUI呢?这个我们放到以后再写,因为接下来需要先来分析SystemUI Plugin的原理,在资料如此稀少的情况下,不了解原理几乎无法写出符合需求的Plugin。在分析的原理的过程中,我们会逐步补完、理解一些Plugin的概念。
以上就是本文的所有内容,感谢你的阅读,希望对你所有帮助。
参考资料