Membuat widget lanjutan

Halaman ini menjelaskan praktik yang direkomendasikan untuk membuat widget yang lebih canggih demi pengalaman pengguna yang lebih baik.

Pengoptimalan untuk memperbarui konten widget

Memperbarui konten widget bisa menjadi mahal komputasinya. Untuk menghemat konsumsi baterai, optimalkan jenis, frekuensi, dan pengaturan waktu update.

Jenis update widget

Ada tiga cara untuk mengupdate widget: update penuh, update sebagian, dan refresh data untuk widget koleksi. Masing-masing memiliki biaya dan konsekuensi komputasi yang berbeda.

Berikut ini penjelasan untuk setiap jenis update dan cuplikan kode untuk setiap jenis update.

  • Update penuh: panggil AppWidgetManager.updateAppWidget(int, android.widget.RemoteViews) untuk mengupdate widget sepenuhnya. Tindakan ini menggantikan RemoteViews yang disediakan sebelumnya dengan RemoteViews baru. Ini adalah update yang paling mahal secara komputasi.

    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);
    
  • Update sebagian: panggil AppWidgetManager.partiallyUpdateAppWidget untuk mengupdate bagian-bagian widget. Tindakan ini akan menggabungkan RemoteViews baru dengan RemoteViews yang disediakan sebelumnya. Metode ini diabaikan jika widget tidak menerima setidaknya satu update penuh melalui 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);
    
  • Muat ulang data koleksi: panggil AppWidgetManager.notifyAppWidgetViewDataChanged untuk membatalkan data tampilan koleksi di widget Anda. Tindakan ini akan memicu RemoteViewsFactory.onDataSetChanged. Untuk sementara, data lama ditampilkan di widget. Anda dapat dengan aman melakukan tugas-tugas mahal secara sinkron dengan metode ini.

    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);
    

Anda dapat memanggil metode ini dari mana saja di aplikasi, asalkan aplikasi tersebut memiliki UID yang sama dengan class AppWidgetProvider terkait.

Menentukan frekuensi update widget

Widget diupdate secara berkala, bergantung pada nilai yang diberikan untuk atribut updatePeriodMillis. Widget dapat diupdate sebagai respons terhadap interaksi pengguna, update siaran, atau keduanya.

Perbarui secara berkala

Anda dapat mengontrol frekuensi update berkala dengan menentukan nilai untuk AppWidgetProviderInfo.updatePeriodMillis dalam XML appwidget-provider. Setiap update akan memicu metode AppWidgetProvider.onUpdate(), yang merupakan tempat Anda dapat menempatkan kode untuk mengupdate widget. Namun, pertimbangkan alternatif untuk update penerima siaran yang dijelaskan di bagian berikut jika widget Anda perlu memuat data secara asinkron atau membutuhkan waktu lebih dari 10 detik untuk diupdate, karena setelah 10 detik, sistem menganggap BroadcastReceiver sebagai tidak responsif.

updatePeriodMillis tidak mendukung nilai kurang dari 30 menit. Namun, jika ingin menonaktifkan update berkala, Anda dapat menentukan 0.

Anda dapat mengizinkan pengguna menyesuaikan frekuensi pembaruan dalam konfigurasi. Misalnya, mereka mungkin ingin ticker saham diperbarui setiap 15 menit atau hanya empat kali sehari. Dalam hal ini, tetapkan updatePeriodMillis ke 0 dan gunakan WorkManager sebagai gantinya.

Pembaruan sebagai respons terhadap interaksi pengguna

Berikut adalah beberapa cara yang direkomendasikan untuk mengupdate widget berdasarkan interaksi pengguna:

  • Dari aktivitas aplikasi: langsung panggil AppWidgetManager.updateAppWidget sebagai respons terhadap interaksi pengguna, seperti ketukan pengguna.

  • Dari interaksi jarak jauh, seperti notifikasi atau widget aplikasi: buat PendingIntent, lalu update widget dari Activity, Broadcast, atau Service yang dipanggil. Anda dapat memilih prioritas Anda sendiri. Misalnya, jika memilih Broadcast untuk PendingIntent, Anda dapat memilih siaran latar depan untuk memberikan prioritas BroadcastReceiver.

Update sebagai respons terhadap acara siaran

Contoh peristiwa siaran yang memerlukan pembaruan widget adalah saat pengguna mengambil foto. Dalam hal ini, Anda perlu mengupdate widget saat foto baru terdeteksi.

Anda dapat menjadwalkan tugas dengan JobScheduler dan menentukan siaran sebagai pemicu menggunakan metode JobInfo.Builder.addTriggerContentUri.

Anda juga dapat mendaftarkan BroadcastReceiver untuk siaran tersebut—misalnya, memproses ACTION_LOCALE_CHANGED. Namun, karena tindakan ini menghabiskan resource perangkat, gunakan ini dengan hati-hati dan hanya proses untuk siaran tertentu. Dengan diperkenalkannya batasan siaran di Android 7.0 (API level 24) dan Android 8.0 (API level 26), aplikasi tidak dapat mendaftarkan siaran implisit dalam manifesnya, dengan pengecualian tertentu.

Pertimbangan saat mengupdate widget dari BroadcastReceiver

Jika widget diupdate dari BroadcastReceiver, termasuk AppWidgetProvider, perhatikan pertimbangan berikut terkait durasi dan prioritas update widget.

Durasi update

Sebagai aturan, sistem mengizinkan penerima siaran, yang biasanya berjalan di thread utama aplikasi, berjalan hingga 10 detik sebelum menganggapnya tidak responsif dan memicu error Aplikasi Tidak Merespons (ANR). Jika perlu waktu lebih lama untuk mengupdate widget, pertimbangkan alternatif berikut:

  • Menjadwalkan tugas menggunakan WorkManager.

  • Beri penerima lebih banyak waktu dengan metode goAsync. Hal ini memungkinkan penerima mengeksekusi selama 30 detik.

Lihat Pertimbangan keamanan dan praktik terbaik untuk mengetahui informasi selengkapnya.

Prioritas update

Secara default, siaran—termasuk yang dibuat menggunakan AppWidgetProvider.onUpdate—berjalan sebagai proses latar belakang. Ini berarti resource sistem yang kelebihan beban dapat menyebabkan penundaan dalam pemanggilan penerima siaran. Untuk memprioritaskan siaran, jadikan siaran tersebut sebagai proses latar depan.

Misalnya, tambahkan flag Intent.FLAG_RECEIVER_FOREGROUND ke Intent yang diteruskan ke PendingIntent.getBroadcast saat pengguna mengetuk bagian widget tertentu.

Membuat pratinjau akurat yang menyertakan item dinamis

Gambar 1: Pratinjau widget yang tidak menampilkan item daftar.

Bagian ini menjelaskan pendekatan yang direkomendasikan untuk menampilkan beberapa item dalam pratinjau widget untuk widget dengan tampilan koleksi, yaitu widget yang menggunakan ListView, GridView, atau StackView.

Jika widget Anda menggunakan salah satu tampilan tersebut, membuat pratinjau skalabel dengan secara langsung menyediakan tata letak widget yang sebenarnya akan menurunkan pengalaman saat pratinjau widget tidak menampilkan item. Hal ini terjadi karena data tampilan koleksi ditetapkan secara dinamis saat runtime, dan terlihat mirip dengan gambar yang ditunjukkan pada gambar 1.

Agar pratinjau widget dengan tampilan koleksi ditampilkan dengan benar di alat pilih widget, sebaiknya pertahankan file tata letak terpisah yang ditetapkan hanya untuk pratinjau. File tata letak terpisah ini mencakup tata letak widget yang sebenarnya dan tampilan koleksi placeholder dengan item palsu. Misalnya, Anda dapat meniru ListView dengan memberikan placeholder LinearLayout dengan beberapa item daftar palsu.

Untuk menggambarkan contoh ListView, mulai dengan file tata letak terpisah:

// 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>

Tentukan file tata letak pratinjau saat memberikan atribut previewLayout metadata AppWidgetProviderInfo. Anda tetap menentukan tata letak widget yang sebenarnya untuk atribut initialLayout dan menggunakan tata letak widget yang sebenarnya saat membuat RemoteViews saat runtime.

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

Item daftar kompleks

Contoh di bagian sebelumnya memberikan item daftar palsu, karena item daftar adalah objek TextView. Menyediakan item palsu mungkin akan lebih rumit jika item tersebut memiliki tata letak yang kompleks.

Pertimbangkan item daftar yang ditentukan dalam widget_list_item.xml dan terdiri dari dua objek 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>

Untuk memberikan item daftar palsu, Anda dapat menyertakan tata letak beberapa kali, tetapi hal ini menyebabkan setiap item daftar menjadi identik. Untuk menyediakan item daftar yang unik, ikuti langkah-langkah berikut:

  1. Buat kumpulan atribut untuk nilai teks:

    <resources>
        <attr name="widgetTitle" format="string" />
        <attr name="widgetContent" format="string" />
    </resources>
    
  2. Gunakan atribut berikut untuk menetapkan teks:

    <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. Buat gaya sebanyak yang diperlukan untuk pratinjau. Definisikan ulang nilai di setiap gaya:

    <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. Terapkan gaya pada item palsu di tata letak pratinjau:

    <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>