RE: 從零開(kāi)始的車載Android HMI(二) - Widget

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ě)AppWidgetProviderUpdae方法,并在其中調(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。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末旱幼,一起剝皮案震驚了整個(gè)濱河市查描,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌柏卤,老刑警劉巖冬三,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異闷旧,居然都是意外死亡长豁,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門忙灼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)匠襟,“玉大人钝侠,你說(shuō)我怎么就攤上這事∷嵘幔” “怎么了帅韧?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)啃勉。 經(jīng)常有香客問(wèn)我忽舟,道長(zhǎng),這世上最難降的妖魔是什么淮阐? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任叮阅,我火速辦了婚禮,結(jié)果婚禮上泣特,老公的妹妹穿的比我還像新娘浩姥。我一直安慰自己,他們只是感情好状您,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布勒叠。 她就那樣靜靜地躺著,像睡著了一般膏孟。 火紅的嫁衣襯著肌膚如雪眯分。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,268評(píng)論 1 309
  • 那天柒桑,我揣著相機(jī)與錄音弊决,去河邊找鬼。 笑死幕垦,一個(gè)胖子當(dāng)著我的面吹牛丢氢,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播先改,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼疚察,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了仇奶?” 一聲冷哼從身側(cè)響起貌嫡,我...
    開(kāi)封第一講書(shū)人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎该溯,沒(méi)想到半個(gè)月后岛抄,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡狈茉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年夫椭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片氯庆。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蹭秋,死狀恐怖扰付,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情仁讨,我是刑警寧澤羽莺,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站洞豁,受9級(jí)特大地震影響盐固,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜丈挟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一刁卜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧礁哄,春花似錦长酗、人聲如沸溪北。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)之拨。三九已至茉继,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蚀乔,已是汗流浹背烁竭。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留吉挣,地道東北人派撕。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像睬魂,于是被迫代替她去往敵國(guó)和親终吼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容