一、技術(shù)背景
SystemUI結(jié)構(gòu)復(fù)雜,模塊數(shù)量眾多,最重要的是SystemUI屬于常駐進(jìn)程是一個(gè)系統(tǒng)的門(mén)面,且不能自升級(jí),如果定制功能對(duì)主項(xiàng)目做復(fù)雜的修改,首先會(huì)造成適配壓力,如果對(duì)主框架不甚理解,有可能會(huì)造成很多隱藏的Bug,且不易修復(fù),一旦崩潰對(duì)整個(gè)系統(tǒng)的影響很大,那么怎么才能在不修改主結(jié)構(gòu)的基礎(chǔ)上定制我們自己的功能呢?
Google的SystemUI團(tuán)隊(duì)對(duì)該模塊做了插件化的功能,可以動(dòng)態(tài)實(shí)現(xiàn)對(duì)SystemUI的修改,一方面在一定程度上解決了不能自升級(jí)造成的問(wèn)題,另一方面也解決了定制功能和原生主框架的解耦,再者,即便使用Plugin實(shí)現(xiàn)的功能crash了,也不影響SystemUI的運(yùn)行,保證了穩(wěn)定性。
所以SystemUI Plugin機(jī)制在運(yùn)行穩(wěn)定性、代碼健壯性、項(xiàng)目兼容性等方面都是很好的選擇!
二、代碼組成
SystemUI的Plugin項(xiàng)目開(kāi)發(fā)主要涉及四部分代碼模塊:plugin、plugin_core、share的plugins模塊和systemui內(nèi)部,下面分別介紹各個(gè)模塊的作用。
2.1 plugin
以interface的形式定義了SystemUI支持的插件,包括NavigationEdgeBackPlugin、VolumeDialog、GlobalActions等,其中子模塊ExamplePlugin是Google提供的示例,子模塊VolumeDialogPlugin是我本地移植原生Volume后的模塊,結(jié)構(gòu)如下

ExamplePlugin和VolumeDialogPlugin都是一個(gè)完整的項(xiàng)目,VolumeDialogPlugin我是根據(jù)Google提供的ExamplePlugin的位置,在同目錄下新建了VolumeDialogPlugin并實(shí)現(xiàn),這兩個(gè)項(xiàng)目都是可以通過(guò)單編編譯成APK進(jìn)行push的,這個(gè)后面會(huì)講到,有興趣的同學(xué)可以嘗試將自己要實(shí)現(xiàn)的項(xiàng)目新建到plugin同一級(jí)別的目錄嘗試一下,應(yīng)該也是可以的。
2.2 plugin_core
? ? 該模塊主要定義plugin使用的注解類

plugin/plugin_core這兩個(gè)目錄對(duì)SystemUI的可被插件替換的業(yè)務(wù)進(jìn)行interface聲明。這里的東西是SystemUI和plugin apk共同引用,最好不要修改,修改會(huì)導(dǎo)致插件不識(shí)別甚至crash。
2.3 share/com.android.systemui.shared.plugins
? ? 該模塊是插件化的核心模塊,從插件的讀取、加載到禁用等邏輯都是在這里實(shí)現(xiàn)

2.4 systemui
這里systemui就是plugin插件的調(diào)用方,下面實(shí)現(xiàn)舉例可以加深我們對(duì)第三部分的理解。
三、實(shí)現(xiàn)舉例
我們通過(guò)分析systemui模塊自帶的ExamplePlugin工程,看看plugin機(jī)制是怎么一步步的完成并在systemUI中運(yùn)行起來(lái)的,需要注意的是為了保證安全,非debug的版本,只有config_pluginWhitelist列表里的插件會(huì)被讀取。
3.1 ExamplePlugin的實(shí)現(xiàn)
3.1.1 在plugin項(xiàng)目中定義接口OverlayPlugin
OverlayPlugin必須繼承Plugin接口,并且必須定義ACTION和VERSION字段,在類頭部使用注解

ProvidesInterface對(duì)ACTION和VERSION進(jìn)行聲明,詳細(xì)見(jiàn)上圖
3.1.2 在ExamplePlugin的AndroidManifest對(duì)OverlayPlugin進(jìn)行注冊(cè)

使用service進(jìn)行注冊(cè),但它并非是真正的service,所以android:exported的值必須設(shè)置為false
同時(shí)action必須和OverlayPlugin類中定義的ACTION保持一致,另外必須加上權(quán)限
<uses-permission android:name="com.android.systemui.permission.PLUGIN" />
3.1.3 實(shí)現(xiàn)OverlayPlugin

注意實(shí)現(xiàn)類需要Requires注解進(jìn)行聲明,包括target和version。
3.1.4 在SystemUI的文件StatusBar.java進(jìn)行調(diào)用

主要看onPluginConnected和onPluginDisconnected,OverlayPlugin主要實(shí)現(xiàn)對(duì)NotificationShadeWindowView的替換
3.1.5 實(shí)機(jī)操作
根據(jù)ExamplePlugin的Android.bp中項(xiàng)目的name,在終端進(jìn)行單編,完成后會(huì)在out\target\product\k6877v1_64\system\app目錄下生成MtkExamplePlugin包(基于CP05),將該包中的APK push到手機(jī)/system/app/下重啟,就可以看到systemui中NotificationShadeWindowView對(duì)應(yīng)的xml文件被修改了顏色
3.2 VolumeDialogPlugin的實(shí)現(xiàn)
VolumeDialogPlugin完成編譯并push到手機(jī)上,這時(shí)候SystemUI有兩套Volume的模塊,從
VolumeDialogComponent.java的構(gòu)造函數(shù)中可以看到,首先允許插件引用systemui中實(shí)現(xiàn)的VolumeDialogController,然后加載VolumeDialogPlugin,如果不成功,則使用default中的實(shí)現(xiàn),代碼如下
// 允許插件引用systemui中實(shí)現(xiàn)的VolumeDialogController
Dependency.get(PluginDependencyProvider.class)
? ? ? ? .allowPluginDependency(VolumeDialogController.class);
//加載插件
Dependency.get(ExtensionController.class).newExtension(VolumeDialog.class)
? ? ? ? .withPlugin(VolumeDialog.class) //在plugin模塊中定義的接口
? ? ? ? .withDefault(this::createDefault) //設(shè)置默認(rèn)的實(shí)現(xiàn)
? ? ? ? .withCallback(dialog -> {
? ? ? ? //初始化完成后,回調(diào),這時(shí)候通過(guò)打印mDialog的路徑就可以知道
? ? ? ? //我們現(xiàn)在使用的具體是哪一個(gè)Volume模塊
? ? ? ? ? ? if (mDialog != null) {
? ? ? ? ? ? ? ? mDialog.destroy();
? ? ? ? ? ? }
? ? ? ? ? ? mDialog = dialog;
? ? ? ? ? ? mDialog.init(LayoutParams.TYPE_VOLUME_OVERLAY, mVolumeDialogCallback);
? ? ? ? }).build();
實(shí)現(xiàn)前

實(shí)現(xiàn)后:

VolumeDialogPlugin模塊是我自己把原生的Volume模塊從SystemUI中移植到Plugin中的實(shí)現(xiàn),細(xì)節(jié)不在贅述,但是有沒(méi)有注意到3.1.4中在statusbar.java中調(diào)用方式和3.2最開(kāi)始介紹的調(diào)用方式有所不同?
兩種不同的調(diào)用方式,我認(rèn)為是針對(duì)不同的場(chǎng)景,第一種通過(guò)PluginManager調(diào)用的方式,主要是用在對(duì)SystemUI已經(jīng)有的文件進(jìn)行覆蓋式的修改,有點(diǎn)類似overlay機(jī)制;第二種通過(guò)Dependency.get的方式主要是針對(duì)某一個(gè)完整的模塊,直接替換,或者我們需求新增的模塊,通過(guò)在SystemUI中加入少量的類似上面的代碼,直接使用。我們可以根據(jù)不同的需求場(chǎng)景進(jìn)行選擇,有興趣的同學(xué)可以更深入的研究探討一下。
四、代碼分析
后續(xù)補(bǔ)上
參考
https://android.googlesource.com/platform/frameworks/base/+/master/packages/SystemUI/docs/plugins.md