Trang này giải thích các phương pháp đề xuất để tạo tiện ích nâng cao hơn nhằm mang lại trải nghiệm tốt hơn cho người dùng.
Các biện pháp tối ưu hoá để cập nhật nội dung tiện ích
Việc cập nhật nội dung tiện ích có thể tốn nhiều tài nguyên tính toán. Để tiết kiệm mức tiêu thụ pin, hãy tối ưu hoá loại, tần suất và thời gian cập nhật.
Các loại bản cập nhật tiện ích
Có 3 cách để cập nhật tiện ích: cập nhật toàn bộ, cập nhật một phần và làm mới dữ liệu (trong trường hợp tiện ích bộ sưu tập). Mỗi phương thức lại có chi phí tính toán và hệ quả khác nhau.
Phần sau đây mô tả từng loại nội dung cập nhật và cung cấp đoạn mã cho từng loại.
Cập nhật đầy đủ: gọi
AppWidgetManager.updateAppWidget(int, android.widget.RemoteViews)
để cập nhật đầy đủ tiện ích. Thao tác này sẽ thay thếRemoteViews
đã cung cấp trước đó bằng mộtRemoteViews
mới. Đây là bản cập nhật tốn nhiều tài nguyên tính toán nhất.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);
Cập nhật một phần: gọi
AppWidgetManager.partiallyUpdateAppWidget
để cập nhật một số phần của tiện ích. Thao tác này sẽ hợp nhấtRemoteViews
mới vớiRemoteViews
đã cung cấp trước đó. Phương thức này bị bỏ qua nếu một tiện ích không nhận được ít nhất một bản cập nhật đầy đủ thông quaupdateAppWidget(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);
Làm mới dữ liệu bộ sưu tập: gọi
AppWidgetManager.notifyAppWidgetViewDataChanged
để vô hiệu hoá dữ liệu của thành phần hiển thị bộ sưu tập trong tiện ích. Thao tác này sẽ kích hoạtRemoteViewsFactory.onDataSetChanged
. Trong thời gian chờ đợi, dữ liệu cũ sẽ hiển thị trong tiện ích. Bạn có thể thực hiện đồng bộ các tác vụ tốn kém một cách an toàn bằng phương thức này.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);
Bạn có thể gọi các phương thức này từ bất kỳ đâu trong ứng dụng, miễn là ứng dụng có cùng UID với lớp AppWidgetProvider
tương ứng.
Xác định tần suất cập nhật tiện ích
Tiện ích được cập nhật định kỳ tuỳ thuộc vào giá trị được cung cấp cho thuộc tính updatePeriodMillis
. Tiện ích có thể cập nhật để phản hồi hoạt động tương tác của người dùng, thông báo cập nhật hoặc cả hai.
Cập nhật định kỳ
Bạn có thể kiểm soát tần suất cập nhật định kỳ bằng cách chỉ định giá trị cho AppWidgetProviderInfo.updatePeriodMillis
trong tệp XML appwidget-provider
. Mỗi lần cập nhật sẽ kích hoạt phương thức AppWidgetProvider.onUpdate()
. Đây là nơi bạn có thể đặt mã để cập nhật tiện ích. Tuy nhiên, hãy cân nhắc các phương án thay thế cho bản cập nhật broadcast receiver được mô tả trong phần sau nếu tiện ích của bạn cần tải dữ liệu không đồng bộ hoặc mất hơn 10 giây để cập nhật, vì sau 10 giây, hệ thống sẽ coi BroadcastReceiver
là không phản hồi.
updatePeriodMillis
không hỗ trợ các giá trị dưới 30 phút. Tuy nhiên, nếu muốn tắt tính năng cập nhật định kỳ, bạn có thể chỉ định giá trị 0.
Bạn có thể cho phép người dùng điều chỉnh tần suất cập nhật trong một cấu hình. Ví dụ: họ có thể muốn mã chứng khoán cập nhật 15 phút một lần hoặc chỉ 4 lần một ngày. Trong trường hợp này, hãy đặt updatePeriodMillis
thành 0 và sử dụng WorkManager
.
Cập nhật để phản hồi tương tác của người dùng
Sau đây là một số cách nên dùng để cập nhật tiện ích dựa trên hoạt động tương tác của người dùng:
Từ một hoạt động của ứng dụng: gọi trực tiếp
AppWidgetManager.updateAppWidget
để phản hồi một lượt tương tác của người dùng, chẳng hạn như một lượt nhấn của người dùng.Từ các hoạt động tương tác từ xa, chẳng hạn như thông báo hoặc tiện ích ứng dụng: tạo một
PendingIntent
, sau đó cập nhật tiện ích từActivity
,Broadcast
hoặcService
đã gọi. Bạn có thể tự chọn mức độ ưu tiên. Ví dụ: nếu chọnBroadcast
choPendingIntent
, bạn có thể chọn phát sóng trên nền trước để ưu tiênBroadcastReceiver
.
Cập nhật để phản hồi sự kiện truyền tin
Ví dụ về sự kiện truyền tin yêu cầu tiện ích cập nhật là khi người dùng chụp ảnh. Trong trường hợp này, bạn muốn cập nhật tiện ích khi phát hiện ảnh mới.
Bạn có thể lên lịch công việc bằng JobScheduler
và chỉ định một thông báo truyền tin làm trình kích hoạt bằng phương thức JobInfo.Builder.addTriggerContentUri
.
Bạn cũng có thể đăng ký BroadcastReceiver
cho thông báo truyền tin, ví dụ:
nghe ACTION_LOCALE_CHANGED
.
Tuy nhiên, vì việc này tiêu tốn tài nguyên thiết bị, nên hãy sử dụng tính năng này một cách thận trọng và chỉ nghe thông báo truyền tin cụ thể. Với việc giới thiệu các giới hạn về thông báo truyền tin trong Android 7.0 (API cấp 24) và Android 8.0 (API cấp 26), các ứng dụng không thể đăng ký thông báo truyền tin ngầm ẩn trong tệp kê khai, ngoại trừ một số trường hợp ngoại lệ.
Những điều cần cân nhắc khi cập nhật tiện ích từ BroadcastReceiver
Nếu tiện ích được cập nhật từ BroadcastReceiver
, bao gồm cả AppWidgetProvider
, hãy lưu ý đến những điều cần cân nhắc sau đây về thời lượng và mức độ ưu tiên của bản cập nhật tiện ích.
Thời lượng cập nhật
Theo quy tắc, hệ thống cho phép broadcast receiver (thường chạy trong luồng chính của ứng dụng) chạy tối đa 10 giây trước khi coi chúng là không phản hồi và kích hoạt lỗi Ứng dụng không phản hồi (ANR). Nếu quá trình cập nhật tiện ích mất nhiều thời gian hơn, hãy cân nhắc các giải pháp thay thế sau:
Lên lịch cho một tác vụ bằng
WorkManager
.Cung cấp thêm thời gian cho người nhận bằng phương thức
goAsync
. Điều này cho phép trình nhận thực thi trong 30 giây.
Hãy xem phần Các yếu tố cần cân nhắc về bảo mật và các phương pháp hay nhất để biết thêm thông tin.
Mức độ ưu tiên của bản cập nhật
Theo mặc định, thông báo truyền tin (bao gồm cả thông báo được tạo bằng AppWidgetProvider.onUpdate
) sẽ chạy dưới dạng quy trình trong nền. Điều này có nghĩa là tài nguyên hệ thống bị quá tải có thể gây ra độ trễ trong lệnh gọi của broadcast receiver. Để ưu tiên thông báo truyền tin, hãy đặt thông báo đó thành một quy trình trên nền trước.
Ví dụ: thêm cờ Intent.FLAG_RECEIVER_FOREGROUND
vào Intent
được truyền đến PendingIntent.getBroadcast
khi người dùng nhấn vào một phần nhất định của tiện ích.
Tạo bản xem trước chính xác bao gồm các mục động
Phần này giải thích phương pháp đề xuất để hiển thị nhiều mục trong bản xem trước tiện ích cho một tiện ích có thành phần hiển thị
bộ sưu tập – tức là một tiện ích sử dụng
ListView
, GridView
hoặc StackView
.
Nếu tiện ích của bạn sử dụng một trong các chế độ xem này, thì việc tạo bản xem trước có thể mở rộng bằng cách trực tiếp cung cấp bố cục tiện ích thực tế sẽ làm giảm trải nghiệm khi bản xem trước tiện ích không hiển thị mục nào. Điều này xảy ra vì dữ liệu thành phần hiển thị tập hợp được đặt động trong thời gian chạy và dữ liệu này trông giống như hình ảnh trong hình 1.
Để bản xem trước của các tiện ích có thành phần hiển thị bộ sưu tập hiển thị đúng cách trong bộ chọn tiện ích, bạn nên duy trì một tệp bố cục riêng chỉ dành cho bản xem trước. Tệp bố cục riêng biệt này bao gồm bố cục tiện ích thực tế và một thành phần hiển thị thu thập phần giữ chỗ có các mục giả. Ví dụ: bạn có thể mô phỏng ListView
bằng cách cung cấp phần giữ chỗ LinearLayout
với một số mục danh sách giả.
Để minh hoạ ví dụ về ListView
, hãy bắt đầu với một tệp bố cục riêng:
// 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>
Chỉ định tệp bố cục xem trước khi cung cấp thuộc tính previewLayout
của siêu dữ liệu AppWidgetProviderInfo
. Bạn vẫn chỉ định bố cục tiện ích thực tế cho thuộc tính initialLayout
và sử dụng bố cục tiện ích thực tế khi tạo RemoteViews
trong thời gian chạy.
<appwidget-provider
previewLayout="@layout/widget_previe"
initialLayout="@layout/widget_view" />
Mục danh sách phức tạp
Ví dụ trong phần trước cung cấp các mục danh sách giả mạo, vì các mục danh sách là đối tượng TextView
. Việc cung cấp các mục giả có thể phức tạp hơn nếu các mục đó là bố cục phức tạp.
Hãy xem xét một mục danh sách được xác định trong widget_list_item.xml
và bao gồm hai đối tượng 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>
Để cung cấp các mục danh sách giả, bạn có thể thêm bố cục nhiều lần, nhưng điều này sẽ khiến mỗi mục danh sách giống hệt nhau. Để cung cấp các mục danh sách riêng biệt, hãy làm theo các bước sau:
Tạo một tập hợp thuộc tính cho các giá trị văn bản:
<resources> <attr name="widgetTitle" format="string" /> <attr name="widgetContent" format="string" /> </resources>
Sử dụng các thuộc tính sau để đặt văn bản:
<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>
Tạo số lượng kiểu cần thiết cho bản xem trước. Xác định lại các giá trị trong mỗi kiểu:
<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>
Áp dụng kiểu cho các mục giả trong bố cục xem trước:
<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>