Создайте расширенный виджет

На этой странице описаны рекомендуемые методы создания более продвинутого виджета для лучшего взаимодействия с пользователем.

Оптимизации для обновления содержимого виджета.

Обновление содержимого виджета может потребовать больших вычислительных затрат. Чтобы сэкономить заряд батареи, оптимизируйте тип, частоту и время обновления.

Типы обновлений виджетов

Существует три способа обновления виджета: полное обновление, частичное обновление и, в случае виджета-коллекции, обновление данных. Каждый из них имеет разные вычислительные затраты и последствия.

Ниже описан каждый тип обновления и приведены фрагменты кода для каждого.

  • Полное обновление: вызовите AppWidgetManager.updateAppWidget(int, android.widget.RemoteViews) чтобы полностью обновить виджет. Это заменяет ранее предоставленные RemoteViews на новые RemoteViews . Это самое вычислительно затратное обновление.

    Котлин

    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)

    Ява

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

    Котлин

    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)

    Ява

    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 . Тем временем в виджете отображаются старые данные. С помощью этого метода вы можете безопасно выполнять дорогостоящие задачи синхронно.

    Котлин

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

    Ява

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

Вы можете вызывать эти методы из любого места вашего приложения, если приложение имеет тот же UID, что и соответствующий класс AppWidgetProvider .

Определите, как часто обновлять виджет

Виджеты периодически обновляются в зависимости от значения атрибута updatePeriodMillis . Виджет может обновляться в ответ на взаимодействие с пользователем, широковещательные обновления или и то, и другое.

Периодически обновляйте

Вы можете контролировать частоту периодического обновления, указав значение AppWidgetProviderInfo.updatePeriodMillis в XML-коде appwidget-provider . Каждое обновление запускает метод AppWidgetProvider.onUpdate() , в котором вы можете разместить код для обновления виджета. Однако рассмотрите альтернативы для обновлений приемника широковещания, описанные в следующем разделе, если вашему виджету необходимо загружать данные асинхронно или обновление занимает более 10 секунд, поскольку по истечении 10 секунд система считает, что BroadcastReceiver не отвечает.

updatePeriodMillis не поддерживает значения менее 30 минут. Однако если вы хотите отключить периодические обновления, вы можете указать 0.

Вы можете разрешить пользователям настраивать частоту обновлений в конфигурации. Например, они могут захотеть, чтобы биржевой тикер обновлялся каждые 15 минут или только четыре раза в день. В этом случае установите для updatePeriodMillis значение 0 и вместо этого используйте WorkManager .

Обновление в ответ на взаимодействие с пользователем

Вот несколько рекомендуемых способов обновления виджета на основе взаимодействия с пользователем:

  • Из действия приложения: напрямую вызывайте AppWidgetManager.updateAppWidget в ответ на взаимодействие с пользователем, например на касание пользователя.

  • Из удаленных взаимодействий, таких как уведомление или виджет приложения: создайте PendingIntent , затем обновите виджет из вызванного Activity , Broadcast или Service . Вы можете выбрать свой собственный приоритет. Например, если вы выберете Broadcast для PendingIntent , вы можете выбрать трансляцию на переднем плане, чтобы задать приоритет 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 к Intent передаваемому в PendingIntent.getBroadcast , когда пользователь нажимает на определенную часть виджета.

Создавайте точные превью, включающие динамические элементы.

Рис. 1. Предварительный просмотр виджета, в котором нет элементов списка.

В этом разделе объясняется рекомендуемый подход к отображению нескольких элементов в предварительном просмотре виджета для виджета с представлением коллекции , то есть виджета, использующего ListView , GridView или StackView .

Если ваш виджет использует одно из этих представлений, создание масштабируемого предварительного просмотра путем непосредственного предоставления фактического макета виджета ухудшает качество работы, когда в предварительном просмотре виджета не отображаются элементы. Это происходит потому, что данные представления коллекции задаются динамически во время выполнения и выглядят аналогично изображению, показанному на рисунке 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>