在前面的視頻、文章中我們介紹完了整個車載Android應(yīng)用開發(fā)所需要的基礎(chǔ)知識:
【視頻文稿】車載Android應(yīng)用開發(fā)與分析 - 走進車載操作系統(tǒng) - 掘金【視頻文稿】車載Android應(yīng)用開發(fā)與分析 - AOSP的下載與編譯 - 掘金【視頻文稿】車載Android應(yīng)用開發(fā)與分析 - 開發(fā)系統(tǒng)應(yīng)用 - 掘金【視頻文稿】車載Android應(yīng)用開發(fā)與分析 - AIDL實踐與封裝(上) - 掘金【視頻文稿】車載Android應(yīng)用開發(fā)與分析 - AIDL實踐與封裝(下) - 掘金
本期內(nèi)容,我們介紹原生Android Automotive中車載應(yīng)用的實現(xiàn)方式和它的原理。首先要介紹的就是車載應(yīng)用開發(fā)中非常重要的一個系統(tǒng)應(yīng)用,Android系統(tǒng)的UI - SystemUI。
由于原生Android系統(tǒng)的SystemUI代碼量很大、內(nèi)容也非常龐雜,這里我會挑選出對車載SystemUI開發(fā)具有參考意義的模塊進行介紹,大約會有4-5期的內(nèi)容,主要分為以下幾個模塊:
車載Android應(yīng)用開發(fā)與分析 - SystemUI 「功能」與「源碼結(jié)構(gòu)」分析 - 掘金車載Android應(yīng)用開發(fā)與分析 - 初試 SystemUI Plugin
SystemUI的源代碼可能是所有Android原生應(yīng)用中最復(fù)雜的一個,當(dāng)我們需要定制SystemUI時,龐大的源碼量會對的定制化開發(fā)帶來巨大的潛在風(fēng)險。所以目前車載SystemUI常見的做法就是,從原生SystemUI中移植少量必須的源碼,然后從頭定制一個源碼、功能完全可控的SystemUI。
重新開發(fā)一個SystemUI就是唯一的選項嗎?當(dāng)然不是!Google官方早就注意到了這個問題,所以SystemUI中提供插件化的開發(fā)方式?- SystemUI Plugin。
本文源碼地址:/frameworks/base/+/refs/heads/main/packages/SystemUI/plugin/ExamplePlugin/
本文源碼環(huán)境基于Android 13
SystemUI Plugin
SystemUI plugin機制是一種讓SystemUI的功能可以被動態(tài)替換或修改的方法,它可以讓開發(fā)者快速創(chuàng)建和迭代SystemUI的原型,而盡可能少的修改SystemUI的主框架。
注意:使用Plugin并不能保證我們完全不需要修改SystemUI的主框架,畢竟需求永遠(yuǎn)是多變的。
Plugin Hooks
Plugin hooks是一些預(yù)定義的插件接口,它們可以讓應(yīng)用實現(xiàn)一些特定的功能,并通過Intent和注解來注冊和聲明插件的類型和版本。Plugin hooks有多種類型,例如OverlayPlugin, QSFactory, VolumeDialog等,每種類型都有一個對應(yīng)的action和expected interface,用于標(biāo)識插件的功能和要求。
Android 13中Plugin hooks預(yù)定義接口主要有以下幾種:
BcSmartspaceDataPlugin:這個plugin可以讓應(yīng)用提供自定義的數(shù)據(jù)給鎖屏界面上的智能空間(BcSmartspace),例如天氣、日歷、新聞等。ClockProviderPlugin:這個plugin可以讓應(yīng)用提供自定義的時鐘樣式給鎖屏界面和始終應(yīng)用。DozeServicePlugin:這個plugin可以讓應(yīng)用自定義Doze模式的行為,例如控制屏幕亮度、顯示內(nèi)容、傳感器等。FalsingPlugin:這個plugin可以讓應(yīng)用自定義對誤觸(Falsing)事件的檢測和處理,例如判斷用戶是否真的想滑動通知欄或解鎖屏幕等。GlobalActions:這個plugin可以讓應(yīng)用自定義全局操作(GlobalActions)對話框的外觀和行為,例如添加新的操作按鈕或改變對話框樣式。GlobalActionsPanelPlugin:這個plugin可以讓應(yīng)用在全局操作對話框中添加一個可展開的面板,用于顯示更多的操作選項或信息。IntentButtonProvider:這個plugin可以讓應(yīng)用在鎖屏界面上添加一個自定義的按鈕,用于啟動一個指定的Intent。NavigationEdgeBackPlugin:這個plugin可以讓應(yīng)用自定義導(dǎo)航欄邊緣返回(NavigationEdgeBack)手勢的行為,例如改變觸發(fā)區(qū)域或動畫效果。NotificationListenerController:這個plugin可以讓應(yīng)用控制通知監(jiān)聽器(NotificationListener)服務(wù)的連接和斷開,以及獲取通知事件和數(shù)據(jù)。NotificationMenuRowPlugin:這個plugin可以讓應(yīng)用自定義通知菜單欄(NotificationMenuRow)的外觀和行為,例如添加新的菜單項或改變菜單樣式。NotificationPersonExtractorPlugin:這個plugin可以讓應(yīng)用自定義從通知中提取人物信息(NotificationPersonExtractor)的邏輯,例如識別通知中包含的聯(lián)系人或頭像等。OverlayPlugin:這個plugin可以讓應(yīng)用自定義覆蓋在通知欄上方的視圖(OverlayView),用于顯示一些額外的內(nèi)容或功能。PluginFragment:這個plugin可以讓應(yīng)用在SystemUI中嵌入一個Fragment,用于顯示一些自定義的界面或功能。QSFactory:這個plugin可以讓應(yīng)用提供自定義的快速設(shè)置工廠(QSFactory),用于創(chuàng)建快速設(shè)置圖塊或面板。SensorManagerPlugin:這個plugin可以讓應(yīng)用使用SensorManager服務(wù)來注冊和取消注冊傳感器監(jiān)聽器,以及獲取傳感器事件和數(shù)據(jù)。ToastPlugin:這個plugin可以讓應(yīng)用自定義Toast消息(Toast)的外觀和行為,例如改變Toast位置或持續(xù)時間等。ViewProvider:這個plugin可以讓應(yīng)用提供一個自定義的視圖(View),用于替換SystemUI中某些組件或功能。VolumeDialog:這個plugin可以讓應(yīng)用自定義音量調(diào)節(jié)對話框(VolumeDialog)的外觀和行為,例如添加新的音量控制選項或改變音量條的樣式。
Plugin 上手
創(chuàng)建一個AndroidStudio的SystemUI plugin項目,可以參考以下的步驟:
1)編譯SystemUIPluginLib.jar
使用Plugin之前我們需要編譯出SystemUIPluginLib.jar,在AOSP源碼根目錄執(zhí)行下面的指令。
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,不過我編譯時出現(xiàn)了環(huán)境配置問題。
2)配置系統(tǒng)簽名
在build.gradle中配置系統(tǒng)簽名。
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
}
}
}
關(guān)于如何制作系統(tǒng)簽名,請參考:車載Android應(yīng)用開發(fā)與分析 - 開發(fā)系統(tǒng)應(yīng)用 - 掘金
3)創(chuàng)建一個Plugin
在plugin項目中定義一個類,實現(xiàn)自Plugin中已經(jīng)提供的各種插件,并使用Requires注解聲明target和version字段,這些字段用于標(biāo)識插件的類型和版本。
@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屬性指定插件的接口和權(quán)限,這樣SystemUI就可以通過Intent找到插件。
android:name=".SampleOverlayPlugin" android:exported="false" android:label="@string/plugin_label" tools:ignore="Instantiatable">
的name可以在我們實現(xiàn)的plugin接口中找到。
SystemUI為了保證系統(tǒng)安全,對于plugin的加載,構(gòu)筑了兩道防線:
第一道防線是Build.IS_DEBUGGABLE檢查。SysUI 在掃描或加載設(shè)備上的任何插件之前,會檢查Build.IS_DEBUGGABLE,以確保構(gòu)建是可調(diào)試的。
第二道防線是就是簽名權(quán)限。所有插件都必須被系統(tǒng)簽名且持有com.android.systemui.permission.PLUGIN權(quán)限才能加載其任何代碼,否則將記錄違規(guī)行為,并忽略插件。
5)運行Plugin
將plugin.apk push 到Android 13 模擬器的/system/priv-app/ 目錄下,重啟??梢钥吹饺缦碌男Ч?/p>
NavBar的所有子View被移除,并添加了一個紅色的View;NotificationShadeWindowView的所有子View被移除,并添加了一個紅色的View。
總結(jié)
本文初試了SystemUI插件機制,在編寫本文時發(fā)現(xiàn)Plugin相關(guān)的資料少的可憐,即使是官方資料有的也過時了。所以就像標(biāo)題那樣,本文只是簡單嘗試了Plugin,如何使用Plugin來詳細(xì)定制一個完全符合我們需求的SystemUI呢?這個我們放到以后再寫,因為接下來需要先來分析SystemUI Plugin的原理,在資料如此稀少的情況下,不了解原理幾乎無法寫出符合需求的Plugin。在分析的原理的過程中,我們會逐步補完、理解一些Plugin的概念。
以上就是本文的所有內(nèi)容,感謝你的閱讀,希望對你所有幫助。
參考資料
Sysui plugin
SystemUI Plugin 簡介及使用
/SystemUI/docs/plugins.md
推薦閱讀
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。