【问题标题】:Android: How do I force the update of all widgets of a particular kindAndroid:如何强制更新特定类型的所有小部件
【发布时间】:2011-11-29 00:43:34
【问题描述】:

我已经看到了许多类似的问题,而且我一遍又一遍地看到相同的答案。我不想为我的应用拥有的每一种小部件都提供一个服务,尤其是看到我的应用已经有 2 个持久服务。

具体来说,如果我的现有服务之一发现数据已更改,我想更新我的小部件。在计时器上执行此操作很烦人,因为更新之间可能需要几天时间,或者一小时内可能会有好几次。我希望我的小部件始终显示最新信息。

Android 小部件设计似乎是基于您的小部件在需要时提取信息的基础上工作的,我认为有许多明智的场景是活动可能希望将数据推送到小部件。

阅读下面的答案,了解我是如何精确地做到这一点的。据我所知,如果处理得当,不会有任何不良影响。

【问题讨论】:

    标签: android widget


    【解决方案1】:

    要强制更新特定小部件提供程序的小部件,我们需要执行以下操作:

    • 设置我们的提供商以接收专门的广播
    • 发送专用广播:
      1. 所有当前的小部件 ID 都与提供者关联
      2. 要发送的数据
      3. 只有我们的提供商才会响应的键(重要!)
    • 让我们的小部件在点击时更新(没有服务!)

    第 1 步 - 设置我们的 AppWidgetProvider

    我不会详细介绍如何创建 info xml 文件或更改 Android Manifest - 如果您不知道如何正确创建小部件,那么您应该先阅读大量教程。

    这是一个示例AppWidgetProvider 类:

    public class MyWidgetProvider extends AppWidgetProvider {
    
    public static final String WIDGET_IDS_KEY ="mywidgetproviderwidgetids";
    public static final String WIDGET_DATA_KEY ="mywidgetproviderwidgetdata";
    
    }
    
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.hasExtra(WIDGET_IDS_KEY)) {
            int[] ids = intent.getExtras().getIntArray(WIDGET_IDS_KEY);
            if (intent.hasExtra(WIDGET_DATA_KEY)) {
               Object data = intent.getExtras().getParcelable(WIDGET_DATA_KEY);
               this.update(context, AppWidgetManager.getInstance(context), ids, data);
            } else {
                this.onUpdate(context, AppWidgetManager.getInstance(context), ids);
            }
        } else super.onReceive(context, intent);
    }
    
    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager,
            int[] appWidgetIds) {
        update(context, appWidgetManager, appWidgetIds, null);
    }
    
    //This is where we do the actual updating
    public void update(Context context, AppWidgetmanager manager, int[] ids, Object data) {
    
        //data will contain some predetermined data, but it may be null
       for (int widgetId : ids) {
            .
            .
            //Update Widget here
            .
            .
            manager.updateAppWidget(widgetId, remoteViews);
        }
    }
    

    第 2 步 - 发送广播

    在这里我们可以创建一个静态方法来更新我们的小部件。在小部件更新操作中使用我们自己的键很重要,如果我们使用 AppWidgetmanager.EXTRA_WIDGET_IDS,我们不仅会破坏我们自己的小部件,还会破坏其他小部件。

    public static void updateMyWidgets(Context context, Parcelable data) {
        AppWidgetManager man = AppWidgetManager.getInstance(context);
        int[] ids = man.getAppWidgetIds(
                new ComponentName(context,MyWidgetProvider.class));
        Intent updateIntent = new Intent();
        updateIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
        updateIntent.putExtra(MyWidgetProvider.WIDGET_ID_KEY, ids);
        updateIntent.putExtra(MyWidgetProvider.WIDGET_DATA_KEY, data);
        context.sendBroadcast(updateIntent);
    }
    

    如果将此方法与多个提供商一起使用,请确保他们使用不同的密钥。否则,您可能会发现小部件 a 的代码正在更新小部件 b,这可能会产生一些奇怪的后果。

    第 3 步 - 点击更新

    另一件好事是让我们的小部件在被点击时神奇地更新。将以下代码添加到更新方法中以实现此目的:

    RemoteViews views = 
          new RemoteViews(context.getPackageName(),R.layout.mywidget_layout);
    
    Intent updateIntent = new Intent();
    updateIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
    updateIntent.putExtra(myWidgetProvider.WIDGET_IDS_KEY, ids);
    PendingIntent pendingIntent = PendingIntent.getBroadcast(
          context, 0, updateIntent, PendingIntent.FLAG_UPDATE_CURRENT);
    
    views.setOnClickPendingIntent(R.id.view_container, pendingIntent);
    

    此代码将导致在单击小部件时调用 onUpdate 方法。

    备注

    • 对 updateWidgets() 的任何调用都会导致我们的小部件的所有实例都被更新。
    • 单击小部件将导致我们的小部件的所有实例都被更新
    • 无需服务
    • 当然,请注意不要经常更新 - 小部件更新会消耗电池电量。
    • 请记住,广播是由所有小部件提供者接收的,它是我们的特殊密钥,可确保只有我们的小部件实际更新。

    【讨论】:

    • 请注意,您不必发送数据。在我的应用中,我只想强制更新,所以我根本不发送任何数据。
    • 这个答案启发我解决了一个非常讨厌的问题,即 TouchWiz 启动器上的小部件变得疯狂。诸如闪烁随机内容,然后消失之类的东西。不过,这只发生在我们的小部件上,其他的都很好。如果您遇到这种情况:不要使用AppWidgetmanager.EXTRA_WIDGET_IDS,即使它适用于所有其他设备。这可能是 AOSP 中已修复的问题,而三星尚未引入他们的代码。
    • 我认为小部件提供者应该根据意图中的 Action 而不是其中包含的额外内容来决定要做什么。您在第 2 步中所做的是“ducktyping”,如果您有自己的操作,则可以使用任何类型的额外键(甚至是内置键)。
    • 请注意,如果您只想更新您的所有 AppWidgets 而没有任何其他数据,您可以将您的提供者名称传递给 AppWidgetManager,避免有关 AppWidget Id 的讨厌问题。见this answer
    • @ChrisNoldus 我试过了,但它仍然没有刷新你能帮我吗stackoverflow.com/questions/39388686/…
    【解决方案2】:

    很抱歉 OP 回答迟了,但对于其他任何人,您可以使用这样的方法来更新特定类型(布局和类)的所有应用小部件:

    public static void updateAllWidgets(final Context context,
                                        final int layoutResourceId,
                                        final Class< ? extends AppWidgetProvider> appWidgetClass)
    {
        RemoteViews remoteViews = new RemoteViews(context.getPackageName(), layoutResourceId);
    
        AppWidgetManager manager = AppWidgetManager.getInstance(context);
        final int[] appWidgetIds = manager.getAppWidgetIds(new ComponentName(context, appWidgetClass));
    
        for (int i = 0; i < appWidgetIds.length; ++i)
        {
            manager.updateAppWidget(appWidgetIds[i], remoteViews);
        }
    }
    

    它的作用是为您的布局创建一个RemoteViews,然后获取AppWidgetManager 并找到您提供的类的所有应用小部件,迭代它们并更新它们。

    【讨论】:

    • 我只想指出,每当您更新 RemoteViews 时,您(或至少应该将其视为)开始 RemoteViews。如果您只想更新图像,并保留所有其他属性,那么您最好(重新)设置所有其他属性,否则您将丢失它们。
    【解决方案3】:

    任何时候处理类型时,都可以很容易地使用静态。而且,如果您希望基于“触发器”发生特定的事情,我总是推荐基于事件的编程......意图本质上是 Android 中的默认“事件”(为了在事件中传递数据,请确保对象实现Parcelable 接口)。

    在各种论坛和 SDK 文档中有很多这些概念的示例。

    【讨论】:

    • Intents 是解决方案是对的,但是我在互联网上搜索并没有找到任何好的解决方案示例。我提出这个问题是因为我找到了一种方法并且认为其他人想知道 - 因此我已经回答了我自己的问题。
    猜你喜欢
    • 2011-02-14
    • 1970-01-01
    • 1970-01-01
    • 2018-11-29
    • 2022-11-11
    • 1970-01-01
    • 1970-01-01
    • 2020-08-04
    相关资源
    最近更新 更多