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

Попробуйте способ «Композиции»
Jetpack Compose — рекомендуемый набор инструментов для разработки пользовательского интерфейса для Android. Узнайте, как создавать виджеты с помощью API в стиле Compose.

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

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

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

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

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

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

  • Полное обновление: вызовите 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 секунд, прежде чем они будут считаться неотвечающими и выдавать ошибку « Application Not Responding» (ANR). Чтобы избежать блокировки основного потока при обработке широковещательной рассылки, используйте метод goAsync . Если обновление виджета занимает больше времени, рассмотрите возможность планирования задачи с помощью WorkManager .

Caution: Any work you do here blocks further broadcasts until it completes,
so it can slow the receiving of later events.

Более подробную информацию см. в разделе «Меры безопасности и передовой опыт» .

Приоритет обновления

По умолчанию широковещательные рассылки, в том числе с помощью 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>