SystemUI Plugin 簡(jiǎn)介及使用

一、技術(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容