1. Widget 概述
Widget妹笆,又叫“微件”、“小部件”。小部件是放置在主屏幕(Launcher)上的Android應(yīng)用程序的小工具或控件姐扮。通過(guò)小部件可以將自己喜歡的應(yīng)用程序放在主屏幕上,以便快速訪問(wèn)它們或是顯示一些重點(diǎn)信息衣吠。
小部件可以是多種類型茶敏,例如信息小部件、集合小部件缚俏、控件小部件和混合小部件惊搏。Android為我們提供了一個(gè)完整的框架來(lái)開(kāi)發(fā)我們自己的小部件贮乳。在手機(jī)上我們已經(jīng)看過(guò)一些常見(jiàn)的小部件,例如音樂(lè)小部件恬惯,天氣小部件向拆,時(shí)鐘小部件等。
由于車載系統(tǒng)需要我們額外開(kāi)發(fā)天氣酪耳、音樂(lè)浓恳、時(shí)鐘等應(yīng)用,所以Widget在車載應(yīng)用開(kāi)發(fā)中碗暗,也算是必修課了颈将。不僅如此,開(kāi)發(fā)車載Launcher時(shí)還需要做額外開(kāi)發(fā)言疗,使Launcher具有擺放Widget的能力吆鹤。
本文參考資料:https://developer.android.google.cn/guide/topics/appwidgets/overview
2. 創(chuàng)建一個(gè)最簡(jiǎn)單的Widget
1.創(chuàng)建Widget
的布局,simple_widget.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/Widget.CarWidget.AppWidget.Container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:theme="@style/Theme.CarWidget.AppWidgetContainer">
<TextView
android:id="@+id/appwidget_text"
style="@style/Widget.CarWidget.AppWidget.InnerView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:layout_margin="8dp"
android:contentDescription="@string/appwidget_text"
android:text="@string/appwidget_text"
android:textSize="24sp"
android:textStyle="bold|italic" />
</RelativeLayout>
2.在res/xml
下創(chuàng)建一個(gè)新的XML
XML文件的資源類型應(yīng)設(shè)置為appwidget-provider
用于定義Widget的基本屬性洲守。在XML文件中疑务,定義一些屬性,如下所示:
<? xml version="1.0" encoding="utf-8" ?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/simple_widget"
android:minWidth="100dp"
android:minHeight="100dp"
android:updatePeriodMillis="0" />
各個(gè)屬性的具體含義梗醇,下一節(jié)會(huì)詳細(xì)介紹知允。
3.擴(kuò)展AppWidgetProvider
的實(shí)現(xiàn)
重寫(xiě)AppWidgetProvider
的Updae
方法,并在其中調(diào)用AppWidgetManager.updateAppWidget()
將數(shù)據(jù)更新到布局RemoteViews
中叙谨,完整的代碼如下:
class SimpleWidget : AppWidgetProvider() {
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray
) {
for (appWidgetId in appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId)
}
Log.e(TAG, "onUpdate: $appWidgetIds")
}
}
internal fun updateAppWidget(context: Context,appWidgetManager: AppWidgetManager, appWidgetId: Int) {
val widgetText = "林栩"
val views = RemoteViews(context.packageName, R.layout.simple_widget)
views.setTextViewText(R.id.appwidget_text, widgetText)
// 更新整個(gè)widget
appWidgetManager.updateAppWidget(appWidgetId, views)
}
4.最后温鸽,在AndroidManifes.xml中聲明AppWidgetProvider
<receiver
android:name=".SimpleWidget"
android:exported="false">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/simple_widget_info" />
</receiver>
運(yùn)行這個(gè)程序,并在Launcher上添加這個(gè)Widget手负,就可以看到一個(gè)最簡(jiǎn)單的Widget了涤垫。
到這一步,我們就完成了Widget的helloworld竟终◎疴總體來(lái)說(shuō)Widget的架構(gòu)組成如下所示,接下來(lái)我們逐個(gè)介紹每個(gè)組件的作用统捶。
3. 定義小部件的基礎(chǔ)屬性 - AppWidgetProviderInfo
AppWidgetProviderInfo
用于描述這個(gè)Widget的各種基本信息榆芦,包括layout布局,刷新頻率以及AppWidgetProvider
喘鸟。這些信息都會(huì)定義在xml中匆绣,tag標(biāo)記是<appwidget-provider>
3.1. AppWidgetProviderInfo 常用屬性與說(shuō)明
屬性 | 說(shuō)明 |
---|---|
updatePeriodMillis | 定義小部件通過(guò)調(diào)用onUpdate()回調(diào)方法從AppWidgetProvider請(qǐng)求更新的頻率。實(shí)際更新不能保證使用此值準(zhǔn)時(shí)進(jìn)行什黑,盡可能不頻繁地更新崎淳。updatePeriodMillis不支持小于30分鐘的值。如果要禁用定期更新愕把,可以指定為0小部件的其他更新方式拣凹,請(qǐng)參考后面的 《小部件進(jìn)階用法 - 優(yōu)化更新頻率》 |
initialLayout | 指向定義小部件布局的布局資源茵瘾。 |
initialKeyguardLayout | 指向定義小部件布局的布局資源。 |
configure | 定義用戶添加小部件時(shí)啟動(dòng)的Activity咐鹤,允許他們配置小部件屬性拗秘。 |
description | 指定要為小部件顯示的小部件選擇器的描述。 Android 12中引入祈惶。 |
previewLayout (Android 12)previewImage (Android 11 and lower) | 從Android 12開(kāi)始雕旨,previewLayout屬性指定了一個(gè)可擴(kuò)展的預(yù)覽,您將提供一個(gè)設(shè)置為小部件默認(rèn)大小的XML布局捧请。理想情況下凡涩,指定為該屬性的布局XML應(yīng)該與具有實(shí)際默認(rèn)值的實(shí)際小部件相同。 在Android 11或更低版本中疹蛉,previewImage屬性指定了小部件配置后的預(yù)覽活箕,用戶在選擇應(yīng)用程序小部件時(shí)會(huì)看到該預(yù)覽。如果未提供可款,則用戶會(huì)看到應(yīng)用程序的啟動(dòng)器圖標(biāo)育韩。該字段對(duì)應(yīng)于AndroidManifest中<receiver>元素中的android:previewImage屬性。 注意:建議同時(shí)指定previewImage和previewLayout屬性闺鲸,以便在用戶的設(shè)備不支持previewLayout的情況下筋讨,應(yīng)用程序可以使用previewImage。 |
autoAdvanceViewId | 指定小部件主機(jī)應(yīng)自動(dòng)推進(jìn)的小部件子視圖的視圖ID摸恍。 Android 3.0中引入悉罕。 |
widgetCategory | 聲明小部件是否可以顯示在主屏幕(home_screen)、鎖屏(keyguard)或兩者上立镶。只有低于5.0的Android版本支持鎖屏小部件壁袄。對(duì)于Android 5.0及更高版本,只有home_screen有效媚媒。 |
widgetFeatures | 聲明小部件支持的功能嗜逻。例如,如果您希望小部件在用戶添加時(shí)使用其默認(rèn)配置欣范,請(qǐng)指定configuration_optional和reconfigurable 变泄。這繞過(guò)了在用戶添加小部件后啟動(dòng)配置活動(dòng)令哟。(之后用戶仍然可以重新配置小部件恼琼。) |
targetCellWidth、targetCellHeight (Android 12)minWidth屏富、minHeight | 從Android 12開(kāi)始晴竞,targetCellWidth和targetCellHeight屬性指定小部件的默認(rèn)大小(以網(wǎng)格單元為單位)狠半。 在Android 11及更低版本中噩死,這些屬性將被忽略颤难,如果主屏幕不支持基于網(wǎng)格的布局,則這些屬性可能會(huì)被忽略已维。minWidth和minHeight屬性指定dp中小部件的默認(rèn)大小行嗤。如果小部件的最小寬度或高度的值與單元格的尺寸不匹配,則將這些值四舍五入到最接近的單元格大小垛耳。 注意:建議同時(shí)指定targetCellWidth/targetCellHeight和minWidth/minHeight屬性集栅屏,以便在用戶的設(shè)備不支持targetCellWidth和targetCellHeight的情況下,應(yīng)用程序可以使用minWidth和minHeight堂鲜。如果支持栈雳,targetCellWidth和targetCellHeight屬性優(yōu)先于minWidth和minHeight屬性。 |
minResizeWidthminResizeHeight | 指定小部件的絕對(duì)最小大小缔莲。這些值應(yīng)指定小部件無(wú)法辨認(rèn)或無(wú)法使用的大小哥纫。使用這些屬性,用戶可以將小部件的大小調(diào)整為可能小于默認(rèn)小部件大小的大小痴奏。如果minResizeWidth屬性大于minWidth或未啟用水平調(diào)整大小蛀骇,則忽略該屬性(請(qǐng)參見(jiàn)resizeMode)。 同樣读拆,如果minResizeHeight屬性大于minHeight或未啟用垂直調(diào)整大小松靡,則忽略該屬性。 Android 4.0中引入建椰。 |
maxResizeWidthmaxResizeHeight | 指定小部件的建議最大大小雕欺。如果值不是網(wǎng)格單元尺寸的倍數(shù),則會(huì)將其四舍五入到最近的單元尺寸棉姐。如果maxResizeWidth屬性小于minWidth或未啟用水平調(diào)整大小屠列,則忽略該屬性(請(qǐng)參見(jiàn)resizeMode)。 同樣伞矩,如果maxResizeHeight屬性大于minHeight或未啟用垂直調(diào)整大小笛洛,則忽略該屬性。 Android 12中引入乃坤。 |
resizeMode | 指定可以調(diào)整小部件大小的規(guī)則苛让。可以使用此屬性使主屏幕小部件可以水平湿诊、垂直或在兩個(gè)軸上調(diào)整大小狱杰。用戶長(zhǎng)按小部件以顯示其大小調(diào)整手柄,然后拖動(dòng)水平和/或垂直手柄以更改其在布局網(wǎng)格上的大小厅须。resizeMode屬性的值包括horizontal仿畸、vertical和none。 要將小部件聲明為可水平和垂直調(diào)整大小,請(qǐng)使用horizontal vertical错沽。 在Android 3.1中引入簿晓。 |
關(guān)于小部件尺寸的計(jì)算問(wèn)題請(qǐng)參考 : Provide flexible widget layouts
3.2. AppWidgetProviderInfo 使用方法
AppWidgetProviderInfo
需要在res/xml中使用<appwidget-provider/>
標(biāo)記將需要的屬性定義出來(lái)即可。
<? xml version="1.0" encoding="utf-8" ?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:configure="com.android.car.carwidget.SimpleWidgetConfigureActivity"
android:description="@string/app_widget_description"
android:initialKeyguardLayout="@layout/simple_widget"
android:initialLayout="@layout/simple_widget"
android:minWidth="50dp"
android:minHeight="50dp"
android:previewImage="@drawable/example_appwidget_preview"
android:previewLayout="@layout/simple_widget"
android:resizeMode="horizontal|vertical"
android:targetCellWidth="2"
android:targetCellHeight="2"
android:updatePeriodMillis="86400000"
android:widgetCategory="home_screen|keyguard" />
4.Widget功能提供者 - AppWidgetProvider
AppWidgetProvider繼承自BroadcastReceiver千埃,本質(zhì)上就是一個(gè)廣播接收器憔儿,AppWidgetProvider也只是在onReceive中解析接收到的intent,并使用接收到的數(shù)據(jù)調(diào)用其他擴(kuò)展方法放可。
public void onReceive(Context context, Intent intent) {
//防止惡意更新廣播(不是真正的安全問(wèn)題皿曲,只是過(guò)濾出壞的Broacast,這樣子類就不太可能崩潰)吴侦。
String action = intent.getAction();
if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
Bundle extras = intent.getExtras();
if (extras != null) {
int[] appWidgetIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
if (appWidgetIds != null && appWidgetIds.length > 0) {
this.onUpdate(context, AppWidgetManager.getInstance(context), appWidgetIds);
}
}
} else if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)) {
Bundle extras = intent.getExtras();
if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID)) {
final int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID);
this.onDeleted(context, new int[] { appWidgetId });
}
} else if (AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED.equals(action)) {
Bundle extras = intent.getExtras();
if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID)
&& extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS)) {
int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID);
Bundle widgetExtras = extras.getBundle(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS);
this.onAppWidgetOptionsChanged(context, AppWidgetManager.getInstance(context),
appWidgetId, widgetExtras);
}
} else if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action)) {
this.onEnabled(context);
} else if (AppWidgetManager.ACTION_APPWIDGET_DISABLED.equals(action)) {
this.onDisabled(context);
} else if (AppWidgetManager.ACTION_APPWIDGET_RESTORED.equals(action)) {
Bundle extras = intent.getExtras();
if (extras != null) {
int[] oldIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_OLD_IDS);
int[] newIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
if (oldIds != null && oldIds.length > 0) {
this.onRestored(context, oldIds, newIds);
this.onUpdate(context, AppWidgetManager.getInstance(context), newIds);
}
}
}
}
源碼不復(fù)雜主要就是完成以下事件的分發(fā)邏輯
ACTION_APPWIDGET_UPDATE -> onUpdate
ACTION_APPWIDGET_DELETED -> onDeleted
ACTION_APPWIDGET_OPTIONS_CHANGED -> onAppWidgetOptionsChanged
ACTION_APPWIDGET_ENABLED -> onEnabled
ACTION_APPWIDGET_DISABLED -> onDisabled
ACTION_APPWIDGET_RESTORED -> onRestored
4.1. AppWidgetProvider 基本屬性與說(shuō)明
該類將BroadcastReceiver擴(kuò)展為一個(gè)方便的類來(lái)處理小部件廣播屋休。它只接收與小部件相關(guān)的事件廣播,例如當(dāng)小部件被更新备韧、刪除劫樟、啟用和禁用時(shí)。當(dāng)這些廣播事件發(fā)生時(shí)织堂,將調(diào)用以下方法:
- onUpdate
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
}
如果在前面的AppWidgetProviderInfo
中定義了updatePeriodMillis
叠艳,系統(tǒng)會(huì)根據(jù)這個(gè)時(shí)間周期性的產(chǎn)生ACTION_APPWIDGET_UPDATE事件。當(dāng)用戶添加widget時(shí)也會(huì)產(chǎn)生這一事件易阳。
此方法在用戶添加小部件時(shí)也會(huì)調(diào)用附较,因此它應(yīng)執(zhí)行基本設(shè)置,例如為 View
對(duì)象定義事件處理程序或啟動(dòng)作業(yè)以加載要在小部件中顯示的數(shù)據(jù)潦俺。但是拒课,如果您聲明了一個(gè)沒(méi)有標(biāo)志的配置活動(dòng),則在用戶添加小部件時(shí)不會(huì)調(diào)用此方法事示,而是為后續(xù)更新調(diào)用此方法早像。配置活動(dòng)負(fù)責(zé)在配置完成后執(zhí)行第一次更新。
- onAppWidgetOptionsChanged
public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager,
int appWidgetId, Bundle newOptions) {
}
在第一次放置小部件或調(diào)整小部件的大小時(shí)產(chǎn)生這一事件肖爵。使用此回調(diào)可以根據(jù)小部件的大小范圍顯示或隱藏內(nèi)容或者獲取大小范圍卢鹦。
通過(guò)AppWidgetManager.getAppWidgetOptions(appWidgetId)
可以獲取對(duì)應(yīng)WidgetId的Bundle,其中包括以下內(nèi)容:
OPTION_APPWIDGET_MIN_WIDTH:包含小部件實(shí)例的寬度下限(單位dp)劝堪。
OPTION_APPWIDGET_MIN_HEIGHT:包含小部件實(shí)例高度的下限(單位:dp)冀自。
OPTION_APPWIDGET_MAX_WIDTH:包含小部件實(shí)例的寬度上限(單位:dp)。
OPTION_APPWIDGET_MAX_HEIGHT:包含小部件實(shí)例高度的上限(單位:dp)秒啦。
- onDeleted
public void onDeleted(Context context, int[] appWidgetIds) {
}
每次從窗口小部件主機(jī)中刪除窗口小部件時(shí)熬粗,都會(huì)調(diào)用該函數(shù)。
- onEnabled
public void onEnabled(Context context) {
}
這在第一次創(chuàng)建小部件的實(shí)例時(shí)調(diào)用帝蒿。
例如荐糜,如果用戶添加了兩個(gè)小部件實(shí)例,則這只是第一次調(diào)用葛超。如果您需要打開(kāi)一個(gè)新的數(shù)據(jù)庫(kù)或執(zhí)行另一個(gè)只需要對(duì)所有小部件實(shí)例執(zhí)行一次的設(shè)置暴氏,那么這是一個(gè)很好的地方。
- onDisabled
public void onDisabled(Context context) {
}
當(dāng)創(chuàng)建的小部件的最后一個(gè)實(shí)例從AppWidgetHost中刪除時(shí)绣张,將調(diào)用此函數(shù)答渔。
- onRestored
public void onRestored(Context context, int[] oldWidgetIds, int[] newWidgetIds) {
}
當(dāng)AppWidget提供的實(shí)例從備份中恢復(fù)使調(diào)用。此方法調(diào)用后侥涵,會(huì)立即調(diào)用onUpdate沼撕。
當(dāng)需要從持久化數(shù)據(jù)中恢復(fù)Widget時(shí),需要重寫(xiě)此方法將舊的AppWidgetID重新映射到新值芜飘,并更新任何其他可能相關(guān)的狀態(tài)务豺。
- onReceive
這是為每個(gè)廣播調(diào)用的,通常不需要實(shí)現(xiàn)此方法嗦明。
5. Widget 的布局 - RemoteViews
RemoteViews
是一個(gè)用于描述可在另一個(gè)進(jìn)程中顯示的視圖層次結(jié)構(gòu)的類笼沥。主要用于通知欄和Widget上。
在定義AppWidgetProviderInfo時(shí)需要把Widget的布局文件引入娶牌,Widget的布局與傳統(tǒng)的Android布局文件一樣奔浅,保存在項(xiàng)目的res/layout/
下。
但是需要注意的是诗良,Widget的布局基于RemoteViews汹桦,與傳統(tǒng)的布局方式不同,并不是每種布局或視圖Widget都支持鉴裹。RemoteViews 僅支持以下布局類型:
FrameLayout
LinearLayout
RelativeLayout
GridLayout
以及以下控件類:
AnalogClock
Button
Chronometer
ImageButton
ImageView
ProgressBar
TextView
ViewFlipper
ListView
GridView
StackView
AdapterViewFlipper
Android 12 之后舞骆,支持的控件類增加了三個(gè)
CheckBox
Switch
RadioButton
RadioGroup
RemoteViews 也支持 ViewStub
,它是一個(gè)大小為零的不可見(jiàn)視圖径荔,我們?cè)谑褂脗鹘y(tǒng)布局葛作,進(jìn)行性能優(yōu)化時(shí)也會(huì)經(jīng)常使用。
5.1. RemoteViews 常用方法與說(shuō)明
- 創(chuàng)建
RemoteViews
RemoteViews(String packageName, int layoutId)創(chuàng)建一個(gè)新的 RemoteViews 對(duì)象猖凛,該對(duì)象將顯示指定布局文件中包含的視圖赂蠢。 |
RemoteViews(String packageName, int layoutId, int viewId)創(chuàng)建一個(gè)新的 RemoteViews 對(duì)象,該對(duì)象將顯示指定布局文件中包含的視圖辨泳,并將根視圖的 ID 更改為指定的 id虱岂。 |
RemoteViews(RemoteViews landscape, RemoteViews portrait)創(chuàng)建一個(gè)新的 RemoteViews 對(duì)象,該對(duì)象將填充為指定的橫向或縱向 RemoteViews菠红,具體取決于當(dāng)前配置第岖。 |
RemoteViews(Map<SizeF, RemoteViews> remoteViews)創(chuàng)建一個(gè)新的 RemoteViews 對(duì)象,該對(duì)象將使用最接近的大小規(guī)范來(lái)膨脹布局试溯。 |
RemoteViews(RemoteViews src)基于RemoteViews創(chuàng)建一個(gè)副本蔑滓。 |
- 設(shè)定文字
void setTextViewText(@IdRes int viewId, CharSequence text)
相當(dāng)于TextVIew.setText()
,setTextViewText
內(nèi)部使用了setCharSequence
,所以其實(shí)也可以調(diào)用setCharSequence
來(lái)完成設(shè)定文字的操作。
public void setTextViewText(@IdRes int viewId, CharSequence text) {
setCharSequence(viewId, "setText", text);
}
- 設(shè)定字體顏色
void setTextColor(@IdRes int viewId, @ColorInt int color)
void setInt(viewId, "setTextColor", color);
- 設(shè)定字體大小
void setTextViewTextSize(@IdRes int viewId, int units, float size)
- 設(shè)定圖片
void setImageViewResource(@IdRes int viewId, @DrawableRes int srcId)
void setInt(viewId, "setImageResource", srcId);
void setImageViewUri(@IdRes int viewId, Uri uri)
void setUri(viewId, "setImageURI", uri);
void setImageViewBitmap(@IdRes int viewId, Bitmap bitmap)
void setBitmap(viewId, "setImageBitmap", bitmap);
void setImageViewIcon(@IdRes int viewId, Icon icon)
void setIcon(viewId, "setImageIcon", icon);
- 設(shè)定單個(gè)控件的點(diǎn)擊事件
void setOnClickPendingIntent(@IdRes int viewId, PendingIntent pendingIntent)
void setOnClickResponse(@IdRes int viewId, @NonNull RemoteResponse response)
val url = "http://www.baidu.com"
val intent = Intent(Intent.ACTION_VIEW)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.data = Uri.parse(url)
val pending = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_MUTABLE)
views.setOnClickPendingIntent(R.id.appwidget_text, pending)
appWidgetManager.updateAppWidget(appWidgetId, views)
- 設(shè)定ProgressBar
void setProgressBar(@IdRes int viewId, int max, int progress,
boolean indeterminate)
或者使用
setBoolean(viewId, "setIndeterminate", indeterminate);
if (!indeterminate) {
setInt(viewId, "setMax", max);
setInt(viewId, "setProgress", progress);
}
- 調(diào)整RemoteViews的布局屬性
void setViewLayoutMargin(@IdRes int viewId, @MarginType int type, float value, @ComplexDimensionUnit int units)
void setViewLayoutHeight(@IdRes int viewId, float height, @ComplexDimensionUnit int units)
void setViewLayoutWidth(@IdRes int viewId, float width, @ComplexDimensionUnit int units)
以上就是常用的一些方法键袱,更多API燎窘,請(qǐng)參考官方文檔:RemoteViews | Android Developers
6. Widget 進(jìn)階用法
6.1. 優(yōu)化更新方式
在AppWidgetProvider
中更新RemoteViews有以下三種不同方式可供選擇:
完整更新
調(diào)用AppWidgetManager.updateAppWidget
可以完整更新整個(gè) widget。性能成本最大蹄咖。
val appWidgetManager = AppWidgetManager.getInstance(context)
val views = RemoteViews(context.packageName, R.layout.simple_widget)
views.setTextViewText(R.id.appwidget_text, widgetText)
appWidgetManager.updateAppWidget(appWidgetId, views)
部分更新
調(diào)用AppWidgetManager.partialupdateAppWidget
可以只更新小部件指定的部分褐健。此更新與updateAppWidget
的不同之處在于,傳遞的RemoteViews對(duì)象被理解為小部件的不完整表示澜汤,因此AppWidgetService不會(huì)緩存它蚜迅。
注意,由于這些更新沒(méi)有緩存俊抵,因此在使用AppWidgetService中的緩存版本還原Widget的情況下谁不,它們修改的任何未由restoreInstanceState還原的狀態(tài)都不會(huì)持久。
val appWidgetManager = AppWidgetManager.getInstance(context)
val views = RemoteViews(context.packageName, R.layout.simple_widget)
views.setTextViewText(R.id.appwidget_text, widgetText)
appWidgetManager.partiallyUpdateAppWidget(appWidgetId, views)
集合數(shù)據(jù)的更新
在RemoteViews中使用StackView徽诲、ListView刹帕、GridView時(shí),需要使用
AppWidgetManager.notifyAppWidgetViewDataChanged
來(lái)更新視圖的集合數(shù)據(jù)馏段,這將觸發(fā)RemoteViewsFactory.onDataSetChanged
轩拨。在此期間,舊數(shù)據(jù)將顯示在Widget中院喜。
val appWidgetManager = AppWidgetManager.getInstance(context)
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.widget_listview)
集合Widget專門用于顯示許多相同類型的元素亡蓉,例如來(lái)自圖庫(kù)應(yīng)用程序的圖片集合、來(lái)自新聞應(yīng)用程序的文章集合或來(lái)自通信應(yīng)用程序的消息集合喷舀。
關(guān)于如何開(kāi)發(fā)Widget集合砍濒,請(qǐng)參考官方文檔:https://developer.android.google.cn/guide/topics/appwidgets/collections
2. 優(yōu)化更新頻率
定期更新
定期更新Widget很常見(jiàn),但是updatePeriodMillis
不能設(shè)定小于30分鐘的數(shù)值硫麻,如果需要小于30分鐘定時(shí)更新事件爸邢,建議搭配WorkManger
使用,同時(shí)要把updatePeriodMillis
設(shè)為0拿愧,禁用Widget的定期更新杠河。
依據(jù)廣播的更新
在車載HMI的開(kāi)發(fā)中,有時(shí)候需要依據(jù)廣播更新Widget浇辜,比較常見(jiàn)的是地圖Widget券敌,可選的做法是根據(jù)Location廣播更新Widget。
根據(jù)廣播更新Widget有以下注意事項(xiàng):
更新持續(xù)時(shí)間
通常柳洋,系統(tǒng)允許廣播接收器(通常在應(yīng)用程序的主線程中運(yùn)行)運(yùn)行10 秒待诅,然后再將其視為無(wú)響應(yīng)并觸發(fā)ANR錯(cuò)誤。如果更新小組件需要更多時(shí)間熊镣,需要考慮以下替代方法:
使用 WorkManager
使用
BroadcastReceiver.``goAsync
方法為接收方提供更多時(shí)間卑雁。這允許接收器執(zhí)行 30 秒募书。但是,在此處執(zhí)行的任何工作都會(huì)阻止進(jìn)一步的廣播测蹲,直到它完成為止莹捡,因此過(guò)度利用這一點(diǎn)可能會(huì)適得其反,并導(dǎo)致以后的事件接收速度更慢
更新優(yōu)先級(jí)
默認(rèn)情況下弛房,廣播作為后臺(tái)進(jìn)程運(yùn)行道盏,這意味著當(dāng)系統(tǒng)資源緊張時(shí)可能會(huì)導(dǎo)致廣播接收器調(diào)用延遲而柑∥拇罚可以通過(guò)將廣播設(shè)定為前臺(tái)廣播Intent.FLAG_RECEIVER_FOREGROUND
,提高廣播的優(yōu)先級(jí)媒咳。
7. 總結(jié)
最后我們?cè)倏偨Y(jié)一下Widget的使用方法粹排,<appwidget-provider>
用于定義widget的基本屬性和初始布局。AppWidgetProvider
本質(zhì)上就是一個(gè)廣播接收器涩澡,我們?cè)?code>AppWidgetProvider中使用RemoteViews
顯示UI并填充數(shù)據(jù)顽耳,最后使用AppWidgetManger
刷新UI。
在車載Android系統(tǒng)中妙同,雖然Widget的宿主也是Launcher射富,但是由于Launcher一般是我們自己重新開(kāi)發(fā)的,所以粥帚,如何容納Widget也是需要Launcher的開(kāi)發(fā)者額外開(kāi)發(fā)的胰耗,這塊的內(nèi)容比較復(fù)雜,建議閱讀構(gòu)建應(yīng)用Widget宿主芒涡,并參考AOSP-Launcher3的源碼實(shí)現(xiàn)柴灯。
下一篇,我們來(lái)介紹泊車?yán)走_(dá)费尽、Camera中需要用到的Android HMI 組件 - SurfaceView赠群、TextureView。