Tạo tiện ích nâng cao

Trang này giải thích các phương pháp được đề xuất để tạo tiện ích nâng cao hơn cho trải nghiệm người dùng tốt hơn.

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ể tiêu tốn tài nguyên tính toán. Để tiết kiệm pin mức tiêu thụ dữ liệu, tối ưu hoá loại cập nhật, tần suất và thời gian.

Các loại bản cập nhật tiện ích

Có ba 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à trong trường hợp tiện ích thu thập, làm mới dữ liệu. Mỗi loại có những cách riêng phân nhánh và chi phí tính toán.

Phần sau đây mô tả từng loại bản cập nhật và cung cấp đoạn mã cho từng loại.

  • Toàn bộ bản cập nhật: gọi AppWidgetManager.updateAppWidget(int, android.widget.RemoteViews) để cập nhật đầy đủ tiện ích này. Tham số này thay thế cho URL được cung cấp trước đó RemoteViews với một RemoteViews Đây là bản cập nhật tiêu tốn 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: cuộc gọi AppWidgetManager.partiallyUpdateAppWidget để cập nhật các phần của tiện ích. Thao tác này sẽ hợp nhất RemoteViews mới với RemoteViews được cung cấp trước đó. Phương thức này sẽ 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 đủ qua 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);
    
  • Làm mới dữ liệu thu thập: lệnh gọi AppWidgetManager.notifyAppWidgetViewDataChanged để vô hiệu hoá dữ liệu của chế độ xem bộ sưu tập trong tiện ích của bạn. Thao tác này sẽ kích hoạt RemoteViewsFactory.onDataSetChanged. Trong thời gian chờ đợi, dữ liệu cũ sẽ hiển thị trong tiện ích. Bạn có thể yên tâm thực hiện các tác vụ tốn kém một cách đồng bộ 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 cứ đâu trong ứng dụng, miễn là ứng dụng có cùng một UID với UID tương ứng Lớp AppWidgetProvider.

Xác định tần suất cập nhật tiện ích

Các tiện ích được cập nhật định kỳ tuỳ thuộc vào giá trị được cung cấp cho updatePeriodMillis . Tiện ích này có thể cập nhật theo tương tác của người dùng, phát đi thông báo 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 một giá trị cho AppWidgetProviderInfo.updatePeriodMillis trong XML appwidget-provider. Một bả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 thông tin cập nhật của broadcast receiver được mô tả trong phần tiếp theo nếu tiện ích của bạn cần tải dữ liệu không đồng bộ hoặc mất nhiều thời gian hơn không quá 10 giây để cập nhật, vì sau 10 giây, hệ thống sẽ coi BroadcastReceiver không phản hồi.

updatePeriodMillis không hỗ trợ các giá trị nhỏ hơn 30 phút. Tuy nhiên, nếu bạn muốn tắt tính năng cập nhật định kỳ, bạn có thể chỉ định 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 cấu hình. Cho ví dụ: họ có thể muốn một mã cổ phiếu 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 theo tương tác của người dùng

Dưới đây là một số cách đề xuất để cập nhật tiện ích dựa trên 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 tương tác của người dùng, chẳng hạn như dưới dạng một lần nhấn của người dùng.

  • Từ 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 PendingIntent, sau đó cập nhật tiện ích từ phương thức gọi Activity, Broadcast hoặc Service. Bạn có thể chọn mức độ ưu tiên của riêng mình. Cho ví dụ: nếu chọn Broadcast cho PendingIntent, bạn có thể chọn phát sóng ở nền trước để cung cấp Mức độ ưu tiên BroadcastReceiver.

Cập nhật để phản hồi một sự kiện truyền tin

Ví dụ về sự kiện phát sóng 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 có một bức ảnh mới đã được phát hiện.

Bạn có thể lên lịch công việc bằng JobScheduler và chỉ định thông báo truyền tin làm bằng cách sử dụng JobInfo.Builder.addTriggerContentUri .

Bạn cũng có thể đăng ký BroadcastReceiver cho thông báo truyền tin – ví dụ: đang nghe ACTION_LOCALE_CHANGED. Tuy nhiên, vì phương thức này tốn tài nguyên của thiết bị, nên hãy cẩn thận khi dùng phương thức này và lắng nghe chỉ với chương trình phát sóng cụ thể. Với sự ra mắt của thông báo truyền tin hạn chế trong Android 7.0 (API cấp 24) và Android 8.0 (API cấp 26), ứng dụng không thể đăng ký ngầm ẩn thông báo truyền tin trong tệp kê khai, với một số 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 này được cập nhật từ BroadcastReceiver, bao gồm AppWidgetProvider, hãy lưu ý những điều cần cân nhắc sau đây liên quan đến thời lượng và mức độ ưu tiên của một bản cập nhật tiện ích.

Thời gian 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, chạy tối đa 10 giây trước khi xem xét chúng là luồng không phản hồi và kích hoạt trạng thái Ứng dụng không Lỗi phản hồi (ANR). Nếu mất nhiều thời gian hơn để hãy cập nhật tiện ích này, hãy cân nhắc các phương án thay thế sau:

  • Lên lịch cho một việc cần làm bằng WorkManager.

  • Cho người nhận nhiều thời gian hơn bằng Phương thức goAsync. Điều này cho phép receiver thực thi trong 30 giây.

Xem bài viết Những điều cần cân nhắc về bảo mật và để tìm hiểu thêm của bạn.

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ả những thông báo được tạo bằng AppWidgetProvider.onUpdate – chạy dưới dạng quy trình 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 sự chậm trễ trong việc gọi thông báo truyền tin người nhận. Để ưu tiên thông báo truyền tin, hãy đặt thông báo đó làm quy trình trên nền trước.

Ví dụ: thêm phương thức Intent.FLAG_RECEIVER_FOREGROUND gắn cờ cho Intent được truyền đến PendingIntent.getBroadcast khi người dùng nhấn vào một phần cụ thể của tiện ích.

Tạo bản xem trước chính xác bao gồm các mục động

Hình 1: Bản xem trước tiện ích cho thấy không có mục trong danh sách nào.

Phần này giải thích phương pháp được đề xuất để hiển thị nhiều mục trong bản xem trước tiện ích cho tiện ích có bộ sưu tập chế độ xem—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, hãy tạo bản xem trước có thể mở rộng bằng cách trực tiếp cung cấp tiện ích thực tế layout làm giảm hiệu suất 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 chế độ xem bộ sưu tập được đặt động trong thời gian chạy và trông giống như ảnh minh hoạ trong hình 1.

Để bản xem trước của tiện ích có chế độ xem bộ sưu tập hiển thị đúng cách trong tiện ích bạn nên duy trì một tệp bố cục riêng chỉ được chỉ đị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à chế độ xem bộ sưu tập phần giữ chỗ có các mục giả mạo. Ví dụ: bạn có thể bắt chước ListView bằng cách cung cấp một phần giữ chỗ LinearLayout có một số danh sách giả mạo mục.

Để 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ụ ở phần trước cung cấp các mục danh sách giả mạo vì danh sách là các đối tượng TextView. Có thể phức tạp hơn để cung cấp các mục giả nếu các mục đó có 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ả mạo, bạn có thể bao gồm bố cục này nhiều lần, nhưng điều này khiến từng mục danh sách đều giống nhau. Để cung cấp các mục riêng biệt trong danh sách, hãy làm theo các bước sau:

  1. Tạo một tập hợp các 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>
    
  2. Sử dụng các thuộc tính sau để thiết lập 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>
    
  3. Tạo bao nhiêu kiểu tuỳ thích 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>
    
  4. Áp dụng kiểu cho các mục giả mạo 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>