本頁說明建立進階小工具的建議做法,提升使用者體驗。
小工具內容的更新最佳化
更新小工具內容的運算成本可能相當高昂。為節省電池用量,請最佳化更新類型、頻率和時間。
小工具更新類型
更新小工具的方法有三種:完整更新、部分更新,以及集合小工具的資料重新整理。每種類型的運算費用和結果不盡相同。
以下說明各種更新類型,並提供每個更新類型的程式碼片段。
完整更新:呼叫
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);
只要應用程式擁有與對應 AppWidgetProvider
類別相同的 UID,就可以從應用程式的任何位置呼叫這些方法。
決定小工具的更新頻率
系統會根據您為 updatePeriodMillis
屬性提供的值,定期更新小工具。小工具可依據使用者互動和/或廣播更新而進行更新。
定期更新
您可以在 appwidget-provider
XML 中指定 AppWidgetProviderInfo.updatePeriodMillis
的值,藉此控制定期更新的頻率。每項更新都會觸發 AppWidgetProvider.onUpdate()
方法,您可以將程式碼加入其中,用於更新小工具。不過,如果您的小工具需要以非同步方式載入資料,或是需要超過 10 秒的更新時間,請考慮採用下一節所述的廣播接收器更新替代方法,因為 10 秒後,系統會將 BroadcastReceiver
視為非回應。
updatePeriodMillis
不支援少於 30 分鐘的值。不過,如果您想要停用定期更新,您可以指定 0。
您可以讓使用者在設定中調整更新頻率。例如,他們可能希望股票代號每 15 分鐘更新一次,或每天只更新 4 次。在此情況下,請將 updatePeriodMillis
設為 0,並改用 WorkManager
。
回應使用者互動
以下提供一些建議做法,讓您根據使用者互動來更新小工具:
從應用程式的活動:直接呼叫
AppWidgetManager.updateAppWidget
以回應使用者互動,例如使用者的輕觸。透過遠端互動,例如通知或應用程式小工具:建構
PendingIntent
,然後透過叫用的Activity
、Broadcast
或Service
更新小工具。你可以自行選擇優先順序。舉例來說,如果您為PendingIntent
選取Broadcast
,可以選擇前景廣播做為BroadcastReceiver
的優先順序。
回應廣播事件
使用者拍照時,需要更新小工具的廣播事件示例。就本例而言,您想要在偵測到新相片時更新小工具。
您可以使用 JobScheduler
為工作排程,並使用 JobInfo.Builder.addTriggerContentUri
方法將廣播指定為觸發條件。
您也可以為廣播註冊 BroadcastReceiver
,例如監聽 ACTION_LOCALE_CHANGED
。不過,由於這會消耗裝置資源,因此請謹慎使用,並僅監聽特定廣播。Android 7.0 (API 級別 24) 和 Android 8.0 (API 級別 26) 已推出廣播限制,但應用程式無法在資訊清單中註冊隱含廣播,但某些例外狀況除外。
從 BroadcastReceiver 更新小工具時的注意事項
如果小工具是透過 BroadcastReceiver
(包括 AppWidgetProvider
) 更新,請留意下列小工具更新時間長度和優先順序的注意事項。
更新時間長度
系統通常會允許廣播接收器 (通常在應用程式主執行緒中執行) 執行 10 秒,之後才會視為無回應並觸發應用程式無回應 (ANR) 錯誤。如果更新小工具的時間過長,請考慮以下替代方案:
使用「
WorkManager
」排定工作。使用
goAsync
方法為接收器提供更多時間。讓接收器執行 30 秒。
詳情請參閱安全性考量與最佳做法。
更新的優先順序
根據預設,廣播 (包括透過 AppWidgetProvider.onUpdate
建立的廣播訊息) 會以背景程序的形式執行。這表示超載的系統資源可能會導致系統在叫用廣播接收器時延遲。如要優先放送廣播,請將其設為前景程序。
舉例來說,在使用者輕觸小工具的特定部分時,將 Intent.FLAG_RECEIVER_FOREGROUND
標記新增至傳遞至 PendingIntent.getBroadcast
的 Intent
。
建立包含動態項目的準確預覽
本節說明在設有集合檢視畫面的小工具預覽畫面中,顯示多個項目的建議方法,也就是使用 ListView
、GridView
或 StackView
的小工具。
如果小工具使用上述其中一種檢視畫面,當小工具預覽畫面未顯示項目時,只要直接提供實際小工具版面配置,即可建立可擴充的預覽畫面。這是因為集合檢視資料是在執行階段動態設定,且看起來與圖 1 中的圖片類似。
如要在小工具挑選器中正確顯示包含集合檢視畫面的小工具預覽,建議您保留專為預覽設計的個別版面配置檔案。這個獨立的版面配置檔案包含實際的小工具版面配置,以及含有假項目的預留位置集合檢視畫面。舉例來說,您可以提供含有多個假清單項目的預留位置 LinearLayout
,藉此模仿 ListView
。
如想瞭解 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>
提供 AppWidgetProviderInfo
中繼資料的 previewLayout
屬性時,指定預覽版面配置檔案。您仍可以為 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>
如要提供假清單項目,可以多次加入版面配置,但這會造成每個清單項目都相同。如要提供不重複的清單項目,請按照下列步驟操作:
為文字值建立一組屬性:
<resources> <attr name="widgetTitle" format="string" /> <attr name="widgetContent" format="string" /> </resources>
請使用下列屬性設定文字:
<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>
視需要為預覽建立任意樣式。重新定義每個樣式的值:
<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>
在預覽版面配置中為假項目套用樣式:
<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>