android 車載widget小部件部分詳細源碼實戰(zhàn)開發(fā)-千里馬車載車機framework實戰(zhàn)開發(fā)
官網(wǎng)參考鏈接:https://developer.android.google.cn/develop/ui/views/appwidgets/overview[1]
1、什么是小部件
App widgets are miniature application views that can be embedded in other applications (such as the home screen) and receive periodic updates。 通俗解釋:一個能夠定期刷新并且加到其他應(yīng)用上的微型視圖。
更多android framework干貨內(nèi)容請找千里馬微信私聊:

2、小部件的運行機制是什么?

通過 AppWidgetProvider 定義小部件的行為 通過 RemoteView 和布局文件定義小部件的UI 通過AppWidgetManager 更新視圖 在manifeset 里注冊 AppWidgetProvider(繼承于廣播),設(shè)置監(jiān)聽的action
3、小部件運行在什么進程?
首先明白一下Host進程和widget進程,看如下圖所示:

小部件運行波及的進程,及各個進程的責(zé)任:

小部件的運行邏輯需要分為三部分:AppWidgetProvider 中的邏輯運行在小部件所在應(yīng)用進程。小部件的數(shù)據(jù)存儲,權(quán)限校驗的邏輯,widget進程和host進程的溝通橋梁即跨進程通訊中介,運行在system_process中。小部件渲染邏輯在host 進程中。
4、RemoteView如何工作?
RemoteView 繼承于Parcelable,可在進程間傳遞。RemoteView 會將每一個設(shè)置的行為轉(zhuǎn)換成相應(yīng)的Action。在Host 側(cè)進程時再將Action 翻譯成對應(yīng)的行為。
如: 正常自己進程 TextView 的setText方法,直接調(diào)mTextView.setText就行 但是這個widget都不是自己widget進程進行的渲染,這里就只能通過RemoteView方式來操作,RemoteView的原理其實就是,傳遞類是一個方法名字字符,因為字符串是可以跨進程傳遞的,然后到達了Host進程,Host進程可以根據(jù)方法名字的字符串進行反射調(diào)用 原理圖如下:

可以看出RemoteViews本質(zhì)就是個可以進行跨進程控制顯示view的媒介,沒有像控制自己view那么的方便,對應(yīng)view的控制接口都需要一一轉(zhuǎn)化,所以還是很不靈活,而且這里注意了,因為view種類可能很多,但是remoteviews就只有固定一些,不支持隨意各種view或者自定義

5、widget開發(fā)部分
AppWidgetProviderInfo?object?
Describes?the?metadata?for?an?App?Widget,?such?as?the?App?Widget's?layout,?update?frequency,?and?the?AppWidgetProvider?class.?This?should?be?defined?in?XML.
AppWidgetProvider?class?implementation
Defines?the?basic?methods?that?allow?you?to?programmatically?interface?with?the?App?Widget,?based?on?broadcast?events.?Through?it,?you?will?receive?broadcasts?when?the?App?Widget?is?updated,?enabled,?disabled?and?deleted.
1 準(zhǔn)備好AppWidgetProviderInfo信息 AppWidgetProviderInfo 主要來描述Widget的元數(shù)據(jù),比如widget的layout,更新頻率,一般在xml中進行定義
<appwidget-provider?xmlns:android="http://schemas.android.com/apk/res/android"
????android:minWidth="60dp"
????android:minHeight="30dp"
????android:updatePeriodMillis="86400000"
????android:initialLayout="@layout/appwidget_provider"
????android:configure="com.example.android.apis.appwidget.ExampleAppWidgetConfigure"
????android:resizeMode="horizontal"
????>
</appwidget-provider>
最關(guān)鍵的是initialLayout會有初始化布局,即widget默認顯示布局,即widget程序如果還沒有調(diào)用代碼的updateWidget前顯示的默認布局
layout/appwidget_provider
<TextView?xmlns:android="http://schemas.android.com/apk/res/android"
????android:id="@+id/appwidget_text"
????android:layout_width="wrap_content"
????android:layout_height="wrap_content"
????android:background="#ffff00ff"
????android:textColor="#ff000000"
/>
2 準(zhǔn)備好AppWidgetProvider實現(xiàn)類 AppWidgetProvider的實現(xiàn)類 以廣播為基礎(chǔ),回調(diào)通知AppWidget情況,比如更新,enable,disabled等通知
public?class?ExampleAppWidgetProvider?extends?AppWidgetProvider?{
????@Override
????public?void?onUpdate(Context?context,?AppWidgetManager?appWidgetManager,?int[]?appWidgetIds)?{
????????//省略
????}
????//省略
}
3 在manifest中進行注冊
?????<receiver?android:name=".appwidget.ExampleAppWidgetProvider">
????????????<meta-data?android:name="android.appwidget.provider"
????????????????????android:resource="@xml/appwidget_provider"?/>
????????????<intent-filter>
????????????????<action?android:name="android.appwidget.action.APPWIDGET_UPDATE"?/>
????????????</intent-filter>
????????</receiver>
meta-data標(biāo)簽也非常重要,它就是鏈接上了AppWidgetProviderInfo的xml
4 widget的ui通過代碼更新方式
??static?void?updateAppWidget(Context?context,?AppWidgetManager?appWidgetManager,
????????????int?appWidgetId,?String?titlePrefix)?{
????????RemoteViews?views?=?new?RemoteViews(context.getPackageName(),?R.layout.appwidget_provider);
????????views.setTextViewText(R.id.appwidget_text,?text);
????????//?Tell?the?widget?manager
????????appWidgetManager.updateAppWidget(appWidgetId,?views);
????}
主要通過布局構(gòu)建出對應(yīng)的RemoteViews,然后對布局中的具體View進行相關(guān)set操作,最后調(diào)用appWidgetManager的updateAppWidget方法進行更新AppWidget
相關(guān)demo地址,需要下載aosp源碼: aosp/development/samples/ApiDemos/src/com/example/android/apis/appwidget/
widget展示在手機桌面的widget頁面,因為前面的widget的manifest配置了,所以桌面才可以掃描展示出來:

拖動到桌面成功后widget顯示如下:

6、Widget的顯示Host進程詳解
在aosp的CarLauncher中其實本身功能代碼屬于很簡單,沒有相關(guān)appwidget顯示的需求點,但是是手機桌面是有完整的作為widgethost的代碼部分。所以了解清除怎么作為一個WidgetHost就變得非常關(guān)鍵了。
需要
App?widget?host:?The?AppWidgetHost?provides?the?interaction?with?the?AppWidget?service?for?apps?that?want?to?embed?app?widgets?in?their?UI.?An?AppWidgetHost?must?have?an?ID?that?is?unique?within?the?host's?own?package.?This?ID?remains?persistent?across?all?uses?of?the?host.?The?ID?is?typically?a?hard-coded?value?that?you?assign?in?your?application.
AppWidgetHost ?負責(zé)和系統(tǒng)systemserver的AppWidget service進行相關(guān)的交互,每個AppWidgetHost需要有獨立的id,也就意味者其實不僅僅只有桌面可以作為host,只是我們平常最常見是桌面,也代表可以有多個host同時顯示widget
App?widget?ID:?Each?widget?instance?is?assigned?a?unique?ID?at?the?time?of?binding?(see?bindAppWidgetIdIfAllowed(),?covered?in?more?detail?in?Binding?widgets?on?this?page.?The?unique?ID?is?obtained?by?the?host?using?allocateAppWidgetId().?This?ID?is?persistent?across?the?lifetime?of?the?widget,?that?is,?until?it?is?deleted?from?the?host.?Any?host-specific?state?(such?as?the?size?and?location?of?the?widget)?should?be?persisted?by?the?hosting?package?and?associated?with?the?app?widget?ID.
App?widget?host?view:?Think?of?AppWidgetHostView?as?a?frame?that?the?widget?is?wrapped?in?whenever?it?needs?to?be?displayed.?A?widget?is?associated?with?an?AppWidgetHostView?every?time?the?widget?is?inflated?by?the?host.?Note?the?following?points:
By?default,?the?system?will?create?an?AppWidgetHostView,?but?the?host?can?create?its?own?subclass?of?AppWidgetHostView?by?extending?it.
Starting?in?Android?12?(API?level?31),?AppWidgetHostView?introduces?the?the?setColorResources()?and?resetColorResources()?methods?for?handling?dynamically?overloaded?colors.?The?host?is?responsible?for?providing?the?colors?to?these?methods.

App widget ID代表每個App Widget實例都會有個獨立的id,通過AppWidgetHost的allocateAppWidgetId的方法來獲取,主要是在對widget進行binding時候需要這個id,就是bindAppWidgetIdIfAllowed方法需要這個id。
AppWidgetHostView作為渲染widget的view,負責(zé)inflate widget的相關(guān)view
需要權(quán)限BIND_APPWIDGET權(quán)限級別如下:

需要有platform簽名,或者屬于內(nèi)置priv/app才可以,這點其實CarLauncher一般都是滿足的,一般都是platform簽名和系統(tǒng)一樣。
具體實戰(zhàn)分析(參考aosp的源碼demo:development/apps/WidgetPreview/src/com/android/widgetpreview/WidgetPreviewActivity.java):
??第一步,根據(jù)獨特HOST_ID,構(gòu)造出AppWidgetHost?:
??mAppWidgetHost?=?new?AppWidgetHost(getApplicationContext(),?APPWIDGET_HOST_ID);
??第二步,AppWidgetHost?申請出獨特的widget?id:
??int?id?=?mAppWidgetHost.allocateAppWidgetId();
??第三步,用申請的widget?id綁定到對應(yīng)的widget的provider的componentName:
??mAppWidgetManager.bindAppWidgetId(mAppWidgetId,?intent.getComponent(),?options);
??第四步,獲取providerInfo,創(chuàng)建對應(yīng)的AppWidgetHostView,進行add:
?????AppWidgetProviderInfo?providerInfo?=
????????????AppWidgetManager.getInstance(getBaseContext()).getAppWidgetInfo(appWidgetId);
mAppWidgetView?=?mAppWidgetHost.createView(getBaseContext(),?appWidgetId,?providerInfo);
mAppWidgetFrame.addView(mAppWidgetView,?mPreviewWidth,?mPreviewHeight);

現(xiàn)象演示:

引用鏈接
[1]
官網(wǎng)參考鏈接:https://developer.android.google.cn/develop/ui/views/appwidgets/overview: https://developer.android.google.cn/develop/ui/views/appwidgets/overview[2]
更多android framework干貨內(nèi)容請找千里馬私聊:https://www.bilibili.com/video/BV1wj411o7A9/: https://www.bilibili.com/video/BV1wj411o7A9/