建立進階小工具

本頁說明建立更進階小工具的建議做法 進而改善使用者體驗

小工具內容更新最佳化

更新小工具內容的運算費用可能非常高。省電模式 最佳化更新類型、頻率和時間

小工具更新類型

小工具的更新方式有三種:完整的更新、部分更新及 如果是集合小工具,系統就會重新整理資料。每種類型 運算費用和影響

以下說明每種更新類型,並分別提供程式碼片段。

  • 完整的更新:請呼叫 AppWidgetManager.updateAppWidget(int, android.widget.RemoteViews) 即可完全更新小工具。這會取代先前 RemoteViews 包含新的 RemoteViews。這項更新作業最昂貴。

    Kotlin

    val appWidgetManager = AppWidgetManager.getInstance(context)
    val remoteViews = RemoteViews(context.getPackageName(), R.layout.widgetlayout).also {
    setTextViewText(R.id.textview_widget_layout1, "Updated text1")
    setTextViewText(R.id.textview_widget_layout2, "Updated text2")
    }
    appWidgetManager.updateAppWidget(appWidgetId, remoteViews)
    

    Java

    AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
    RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widgetlayout);
    remoteViews.setTextViewText(R.id.textview_widget_layout1, "Updated text1");
    remoteViews.setTextViewText(R.id.textview_widget_layout2, "Updated text2");
    appWidgetManager.updateAppWidget(appWidgetId, remoteViews);
    
  • 部分更新:呼叫 AppWidgetManager.partiallyUpdateAppWidget敬上 更新小工具的某些部分這會將新的 RemoteViews 與 先前提供的 RemoteViews。如果小工具 透過 updateAppWidget(int[], RemoteViews) 未收到至少一次完整更新。

    Kotlin

    val appWidgetManager = AppWidgetManager.getInstance(context)
    val remoteViews = RemoteViews(context.getPackageName(), R.layout.widgetlayout).also {
    setTextViewText(R.id.textview_widget_layout, "Updated text")
    }
    appWidgetManager.partiallyUpdateAppWidget(appWidgetId, remoteViews)
    

    Java

    AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
    RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widgetlayout);
    remoteViews.setTextViewText(R.id.textview_widget_layout, "Updated text");
    appWidgetManager.partiallyUpdateAppWidget(appWidgetId, remoteViews);
    
  • 更新集合資料:呼叫 AppWidgetManager.notifyAppWidgetViewDataChanged敬上 ,在小工具中使集合檢視的資料失效。這項觸發條件 RemoteViewsFactory.onDataSetChanged。 在這段期間,小工具會顯示舊資料。您可以放心 透過這個方法,以同步的方式執行會耗用大量資源的工作。

    Kotlin

    val appWidgetManager = AppWidgetManager.getInstance(context)
    appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.widget_listview)
    

    Java

    AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
    appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.widget_listview);
    

只要應用程式具有 相同的 UID AppWidgetProvider 類別。

決定小工具的更新頻率

系統會根據為 updatePeriodMillis敬上 屬性。小工具可根據使用者互動、廣播訊息進行更新 更新或/或更新。

定期更新

您可以指定定期更新的頻率, appwidget-provider XML 中的 AppWidgetProviderInfo.updatePeriodMillis。每項 update 會觸發 AppWidgetProvider.onUpdate() 方法,也就是 即可放置用來更新小工具的程式碼但是,請考慮 廣播接收器更新,詳情請參閱 如果小工具需要非同步載入資料,或是 更新時間超過 10 秒,因為系統會在 10 秒後將 BroadcastReceiver 無回應。

updatePeriodMillis 不支援小於 30 分鐘的值。不過, 如要停用定期更新,請指定 0。

您可以讓使用者調整設定中的更新頻率。適用對象 例如,他們可能希望每 15 分鐘更新一次股票代號,或只更新四分鐘 次曝光。在本例中,請將 updatePeriodMillis 設為 0,然後使用 WorkManager

更新以回應使用者互動

以下提供根據使用者互動來更新小工具的幾種建議方法:

  • 從應用程式活動:直接呼叫 AppWidgetManager.updateAppWidget 來回應使用者互動,例如 使用者輕觸畫面

  • 透過遠端互動,例如通知或應用程式小工具: 建構 PendingIntent,然後更新所叫用的小工具 ActivityBroadcastService。您可以自行選擇優先順序。適用對象 舉例來說,如果您為 PendingIntent 選取 Broadcast,則可選擇 前景廣播表示 優先順序為 BroadcastReceiver

更新以回應廣播活動

以下為需要小工具更新的廣播事件範例: 使用者拍攝相片。在這個範例中,您想要在新相片時更新小工具 。

您可以使用 JobScheduler 來安排工作,並將廣播設為 透過 JobInfo.Builder.addTriggerContentUri 方法。

您也可以為廣播註冊 BroadcastReceiver,例如: 正在聆聽 ACTION_LOCALE_CHANGED。 不過,由於這麼做會耗用裝置資源,因此請謹慎使用 只能存取特定廣播訊息。推出廣播訊息後 限制 7.0 (API 級別 24) 和 Android 8.0 (API 級別 26) 版本的應用程式無法註冊隱含式 在資訊清單中提供廣播功能 例外狀況

從 BroadcastReceiver 更新小工具的注意事項

如果小工具是從 BroadcastReceiver 更新,包括 AppWidgetProvider,請注意下列關於 更新間隔的時間和優先順序

更新作業持續時間

原則上,系統允許廣播接收器,這類接收器通常在應用程式的 主執行緒執行 10 秒以上,然後視為無回應 觸發應用程式未 回應 (ANR) 錯誤。如果處理時間 更新小工具,請考慮以下替代方案:

  • 使用 WorkManager 安排工作。

  • 透過以下功能讓接收者有更多時間: goAsync 方法,增加圍繞地圖邊緣的邊框間距。 這可讓接收器執行 30 秒。

請參閱安全性考量和最佳做法 最佳做法 可能不準確或不適當

更新的優先順序

根據預設,廣播內容,包括使用 AppWidgetProvider.onUpdate:以背景程序的形式執行。也就是說 超載系統資源可能會造成廣播叫用延遲 接收器。如要優先處理廣播訊息,請將廣播設為前景程序。

舉例來說,將 Intent.FLAG_RECEIVER_FOREGROUND敬上 當使用者觸發並傳遞至 PendingIntent.getBroadcastIntent 標記時 請輕觸小工具的特定部分

建立包含動態項目的準確預覽

圖 1: 小工具預覽畫面未顯示任何清單項目。

本節將說明在下列時間範圍內顯示多個項目的建議做法: 包含集合的小工具預覽 view,也就是使用 ListViewGridViewStackView

如果您的小工具使用上述其中一種檢視,請直接建立可縮放的預覽 實際提供的小工具 版面配置降低 小工具預覽畫面不會顯示任何項目。這是因為 收集檢視資料是在執行階段動態設定,看起來類似於 如圖 1 所示

讓含有集合檢視的小工具預覽在小工具中正確顯示 選擇器,建議您維護專為 預覽。這個獨立的版面配置檔案包含實際的小工具版面配置, 含有假商品的預留位置集合檢視畫面例如,您可以模擬 ListView,方法是提供包含數個假清單的預留位置 LinearLayout 項目。

如需 ListView 的範例,請從獨立的版面配置檔案開始:

// res/layout/widget_preview.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:background="@drawable/widget_background"
   android:orientation="vertical">

    // Include the actual widget layout that contains ListView.
    <include
        layout="@layout/widget_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    // The number of fake items you include depends on the values you provide
    // for minHeight or targetCellHeight in the AppWidgetProviderInfo
    // definition.

    <TextView android:text="@string/fake_item1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginVertical="?attr/appWidgetInternalPadding" />

    <TextView android:text="@string/fake_item2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginVertical="?attr/appWidgetInternalPadding" />

</LinearLayout>

提供 previewLayout 屬性時,指定預覽版面配置檔案 AppWidgetProviderInfo 中繼資料。您仍然指定實際的小工具版面配置 並在 initialLayout 屬性中使用實際的小工具版面配置 建構 RemoteViews

<appwidget-provider
    previewLayout="@layout/widget_previe"
    initialLayout="@layout/widget_view" />

複雜的清單項目

上一節的範例提供假的清單項目,因為清單 項目是 TextView 物件。可以 如果項目的版面配置較複雜,則較難提供假項目。

假設有一個清單項目是在 widget_list_item.xml 中定義的,由 兩個 TextView 物件:

<LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

    <TextView android:id="@id/title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/fake_title" />

    <TextView android:id="@id/content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/fake_content" />
</LinearLayout>

如果想提供假的清單項目,可以多次加入版面配置,但這 會導致每個清單項目相同。如要提供專屬清單項目,請遵照以下指示: 步驟如下:

  1. 為文字值建立一組屬性:

    <resources>
        <attr name="widgetTitle" format="string" />
        <attr name="widgetContent" format="string" />
    </resources>
    
  2. 請使用下列屬性設定文字:

    <LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
    
        <TextView android:id="@id/title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="?widgetTitle" />
    
        <TextView android:id="@id/content"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="?widgetContent" />
    </LinearLayout>
    
  3. 盡量建立預覽所需的樣式。重新定義 每種樣式:

    <resources>
    
        <style name="Theme.Widget.ListItem">
            <item name="widgetTitle"></item>
            <item name="widgetContent"></item>
        </style>
        <style name="Theme.Widget.ListItem.Preview1">
            <item name="widgetTitle">Fake Title 1</item>
            <item name="widgetContent">Fake content 1</item>
        </style>
        <style name="Theme.Widget.ListItem.Preview2">
            <item name="widgetTitle">Fake title 2</item>
            <item name="widgetContent">Fake content 2</item>
        </style>
    
    </resources>
    
  4. 在預覽版面配置中,為假項目套用樣式:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
       android:layout_width="match_parent"
       android:layout_height="wrap_content" ...>
    
        <include layout="@layout/widget_view" ... />
    
        <include layout="@layout/widget_list_item"
            android:theme="@style/Theme.Widget.ListItem.Preview1" />
    
        <include layout="@layout/widget_list_item"
            android:theme="@style/Theme.Widget.ListItem.Preview2" />
    
    </LinearLayout>