一、技术背景
SystemUI结构复杂,模块数量众多,最重要的是SystemUI属于常驻进程是一个系统的门面,且不能自升级,如果定制功能对主项目做复杂的修改,首先会造成适配压力,如果对主框架不甚理解,有可能会造成很多隐藏的Bug,且不易修复,一旦崩溃对整个系统的影响很大,那么怎么才能在不修改主结构的基础上定制我们自己的功能呢?
Google的SystemUI团队对该模块做了插件化的功能,可以动态实现对SystemUI的修改,一方面在一定程度上解决了不能自升级造成的问题,另一方面也解决了定制功能和原生主框架的解耦,再者,即便使用Plugin实现的功能crash了,也不影响SystemUI的运行,保证了稳定性。
所以SystemUI Plugin机制在运行稳定性、代码健壮性、项目兼容性等方面都是很好的选择!
二、代码组成
SystemUI的Plugin项目开发主要涉及四部分代码模块:plugin、plugin_core、share的plugins模块和systemui内部,下面分别介绍各个模块的作用。
2.1 plugin
以interface的形式定义了SystemUI支持的插件,包括NavigationEdgeBackPlugin、VolumeDialog、GlobalActions等,其中子模块ExamplePlugin是Google提供的示例,子模块VolumeDialogPlugin是我本地移植原生Volume后的模块,结构如下
ExamplePlugin和VolumeDialogPlugin都是一个完整的项目,VolumeDialogPlugin我是根据Google提供的ExamplePlugin的位置,在同目录下新建了VolumeDialogPlugin并实现,这两个项目都是可以通过单编编译成APK进行push的,这个后面会讲到,有兴趣的同学可以尝试将自己要实现的项目新建到plugin同一级别的目录尝试一下,应该也是可以的。
2.2 plugin_core
该模块主要定义plugin使用的注解类
plugin/plugin_core这两个目录对SystemUI的可被插件替换的业务进行interface声明。这里的东西是SystemUI和plugin apk共同引用,最好不要修改,修改会导致插件不识别甚至crash。
2.3 share/com.android.systemui.shared.plugins
该模块是插件化的核心模块,从插件的读取、加载到禁用等逻辑都是在这里实现
2.4 systemui
这里systemui就是plugin插件的调用方,下面实现举例可以加深我们对第三部分的理解。
三、实现举例
我们通过分析systemui模块自带的ExamplePlugin工程,看看plugin机制是怎么一步步的完成并在systemUI中运行起来的,需要注意的是为了保证安全,非debug的版本,只有config_pluginWhitelist列表里的插件会被读取。
3.1 ExamplePlugin的实现
3.1.1 在plugin项目中定义接口OverlayPlugin
OverlayPlugin必须继承Plugin接口,并且必须定义ACTION和VERSION字段,在类头部使用注解
ProvidesInterface对ACTION和VERSION进行声明,详细见上图
3.1.2 在ExamplePlugin的AndroidManifest对OverlayPlugin进行注册
使用service进行注册,但它并非是真正的service,所以android:exported的值必须设置为false
同时action必须和OverlayPlugin类中定义的ACTION保持一致,另外必须加上权限
<uses-permission android:name="com.android.systemui.permission.PLUGIN" />
3.1.3 实现OverlayPlugin
注意实现类需要Requires注解进行声明,包括target和version。
3.1.4 在SystemUI的文件StatusBar.java进行调用
主要看onPluginConnected和onPluginDisconnected,OverlayPlugin主要实现对NotificationShadeWindowView的替换
3.1.5 实机操作
根据ExamplePlugin的Android.bp中项目的name,在终端进行单编,完成后会在out\target\product\k6877v1_64\system\app目录下生成MtkExamplePlugin包(基于CP05),将该包中的APK push到手机/system/app/下重启,就可以看到systemui中NotificationShadeWindowView对应的xml文件被修改了颜色
3.2 VolumeDialogPlugin的实现
VolumeDialogPlugin完成编译并push到手机上,这时候SystemUI有两套Volume的模块,从
VolumeDialogComponent.java的构造函数中可以看到,首先允许插件引用systemui中实现的VolumeDialogController,然后加载VolumeDialogPlugin,如果不成功,则使用default中的实现,代码如下
// 允许插件引用systemui中实现的VolumeDialogController
Dependency.get(PluginDependencyProvider.class)
.allowPluginDependency(VolumeDialogController.class);
//加载插件
Dependency.get(ExtensionController.class).newExtension(VolumeDialog.class)
.withPlugin(VolumeDialog.class) //在plugin模块中定义的接口
.withDefault(this::createDefault) //设置默认的实现
.withCallback(dialog -> {
//初始化完成后,回调,这时候通过打印mDialog的路径就可以知道
//我们现在使用的具体是哪一个Volume模块
if (mDialog != null) {
mDialog.destroy();
}
mDialog = dialog;
mDialog.init(LayoutParams.TYPE_VOLUME_OVERLAY, mVolumeDialogCallback);
}).build();
实现前
实现后:
VolumeDialogPlugin模块是我自己把原生的Volume模块从SystemUI中移植到Plugin中的实现,细节不在赘述,但是有没有注意到3.1.4中在statusbar.java中调用方式和3.2最开始介绍的调用方式有所不同?
两种不同的调用方式,我认为是针对不同的场景,第一种通过PluginManager调用的方式,主要是用在对SystemUI已经有的文件进行覆盖式的修改,有点类似overlay机制;第二种通过Dependency.get的方式主要是针对某一个完整的模块,直接替换,或者我们需求新增的模块,通过在SystemUI中加入少量的类似上面的代码,直接使用。我们可以根据不同的需求场景进行选择,有兴趣的同学可以更深入的研究探讨一下。
四、代码分析
后续补上
参考
https://android.googlesource.com/platform/frameworks/base/+/master/packages/SystemUI/docs/plugins.md