创建高级 widget

本页面介绍了针对 从而提供更好的用户体验

更新微件内容的优化建议

更新微件内容的计算开销可能很大。为了节省电量,请优化更新类型、频率和时间。

微件更新的类型

更新 widget 的方式有三种:完整更新、部分更新以及 对于集合微件,则是数据刷新。每种方法的计算开销和影响各不相同。

以下部分介绍了每种更新类型,并为每种类型提供了代码段。

  • 完整更新:调用 AppWidgetManager.updateAppWidget(int, android.widget.RemoteViews) 完全更新该 widget。这会将之前提供的 RemoteViews 替换为新的 RemoteViews。这是计算成本最高的更新。

    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);
  • 部分更新:调用 AppWidgetManager.partiallyUpdateAppWidget 以更新微件的部分内容。这会将新的 RemoteViews 与 之前提供的 RemoteViews。如果微件 未通过 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);
  • 集合数据刷新:调用 AppWidgetManager.notifyAppWidgetViewDataChanged 使 widget 中集合视图的数据失效。这会触发 RemoteViewsFactory.onDataSetChanged。 在此期间,旧数据会显示在 widget 中。您可以使用此方法安全地同步执行耗时任务。

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

您可以从应用中的任何位置调用这些方法,只要应用具有 与对应的 AppWidgetProvider 类。

确定微件更新的频率

微件会定期更新,具体取决于为 updatePeriodMillis 属性。该 widget 可以更新来响应用户互动、广播 更新,或者两者兼有。

定期更新

您可以通过在 appwidget-provider XML 中为 AppWidgetProviderInfo.updatePeriodMillis 指定值来控制定期更新的频率。每次更新都会触发 AppWidgetProvider.onUpdate() 方法,您可以在该方法中放置用于更新 widget 的代码。不过,如果您的 widget 需要异步加载数据或更新时间超过 10 秒,请考虑使用下一部分中介绍的广播接收器更新替代方案,因为 10 秒后,系统会将 BroadcastReceiver 视为无响应。

updatePeriodMillis 不支持小于 30 分钟的值。不过,如果您想停用定期更新,可以指定 0。

您可以允许用户调整配置中的更新频率。例如,他们可能希望股票行情自动收录器每 15 分钟更新一次,或者一天只更新 4 次。在这种情况下,请将 updatePeriodMillis 设为 0,并改用 WorkManager

更新以响应用户互动

下面是根据用户互动更新微件的几种推荐方法:

  • 从应用的 activity:直接调用 AppWidgetManager.updateAppWidget 来响应用户互动,如 触发。

  • 通过远程互动(例如通知或应用微件):构建 PendingIntent,然后通过调用的 ActivityBroadcastService 更新微件。您可以自行选择优先级。例如,如果您为 PendingIntent 选择了 Broadcast,则可以选择前台广播,以便为 BroadcastReceiver 设置优先级。

更新以响应广播活动

需要微件更新的广播事件示例包括用户拍摄照片时。在本例中,您想在上传新照片时更新微件 。

您可以使用 JobScheduler 安排作业,并指定广播作为 和 JobInfo.Builder.addTriggerContentUri 方法。

您还可以为广播注册 BroadcastReceiver,例如监听 ACTION_LOCALE_CHANGED。不过,由于这会消耗设备资源,因此请谨慎使用,并仅监听特定广播。随着广播的引入 限制 7.0(API 级别 24)和 Android 8.0(API 级别 26),则应用无法进行隐式注册 广播,其中某些 例外情况

从 BroadcastReceiver 更新 widget 时的注意事项

如果 widget 是从 BroadcastReceiver 更新的,包括 AppWidgetProvider,请注意以下有关 widget 更新的持续时间和优先级。

更新时长

一般来说,系统允许广播接收器,这些接收器通常在应用 主线程运行 10 秒后再将其视为无响应和 触发了 Application Not 响应 (ANR) 错误。如果超过这个时间 更新 widget,请考虑采用以下替代方案:

  • 使用 WorkManager 安排任务。

  • 使用 goAsync 方法为接收器提供更多时间。这样一来,接收器便可执行 30 秒。

请参阅安全注意事项和最佳 最佳做法 信息。

更新的优先级

默认情况下,广播(包括使用 AppWidgetProvider.onUpdate - 作为后台进程运行。这意味着 过载的系统资源可能导致广播调用延迟 接收器。要确定广播的优先级,请将其设为前台进程。

例如,将 Intent.FLAG_RECEIVER_FOREGROUND 标记传递给 Intent 传递至 PendingIntent.getBroadcast,当用户 。

构建包含动态项的准确预览

<ph type="x-smartling-placeholder">
</ph>
图 1 :未显示列表项的 widget 预览。

本部分介绍了在包含集合视图(即使用 ListViewGridViewStackView 的 widget)的 widget 预览中显示多个项的推荐方法。

如果您的微件使用其中一种视图,通过直接提供实际的微件布局创建可缩放的预览会降低微件预览不显示任何项时的体验。之所以会出现这种情况,是因为集合视图数据是在运行时动态设置的,并且看起来与图 1 中显示的图片类似。

为了让包含集合视图的 widget 的预览在 widget 选择器中正常显示,我们建议您维护一个单独的布局文件,仅用于预览。此单独的布局文件包含实际的 widget 布局和包含虚构项的占位符集合视图。例如,您可以模仿 通过提供包含多个虚构列表的占位符 LinearLayoutListView 项。

为了说明 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>

提供 AppWidgetProviderInfo 元数据的 previewLayout 属性时,请指定预览布局文件。您仍然需要指定实际的 widget 布局 为 initialLayout 属性指定样式,并在发生以下情况时使用实际的 widget 布局: 在运行时构建 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>