应用程序小部件被 Google 声音搜索劫持?

App Widget getting hijacked by Google Sound Search?

所以我在我编写的应用程序小部件中看到了一些奇怪的行为。

小部件本身非常简单 - 它从持久存储中读取一些值,然后将它们显示为文本。布局中没有什么特别的 - 只是一个 FrameLayout 根元素和一些 Linear LayoutTextView 个子元素。

小部件有一个简单的配置 activity 与之关联。

奇怪的是,在用户关闭配置 activity 后,小部件最初会显示 "Problem Loading Widget",然后在几秒钟后显示 "Google Sound Search" 按钮(并单击按钮上确实会启动 Google 声音搜索)。然后,再过几秒钟,它终于显示了预期的显示。

我现在离开我的代码,所以我必须等到今晚才能看到 post 代码片段。但是,与此同时,任何人都可以提供一些见解来了解这种事情是如何发生的吗?有没有其他人经历过这个?另一个小部件 "hijack" 怎么可能是我的?

谢谢, -罗恩

以下是部分截图:

好的,我明白了。在这里张贴以防其他人遇到这个问题。我认为 Android 开发人员文档在这里有点误导。

问题是在我的配置 Activity 中,我在最后有这段代码:

    Intent intent = new Intent();
    intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
    intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] {mAppWidgetId});
    intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
    setResult(RESULT_OK, intent);
    sendBroadcast(intent);
    finish();

documentation provided by google 建议使用额外的 EXTRA_APPWIDGET_ID 提供意图。

但是,同一个文档说您必须通过创建 RemoteView 并像这样调用 AppWidgetManager.updateAppWidget() 来更新小部件的视图:

RemoteViews views = new RemoteViews(context.getPackageName(),
R.layout.example_appwidget);
appWidgetManager.updateAppWidget(mAppWidgetId, views);

我不喜欢将表示逻辑同时放在配置 activity 和小部件 class 中的想法,因此我决定在配置结束时广播一个意图 activity 告诉小部件重绘自己。这就是为什么我在 activity 末尾有 setResult()sendBroadcast()。文档进一步说明 onUpdate() 回调在使用配置 activity 时不会被调用。所以这似乎是必要的。我将 ACTION_APPWIDGET_UPDATEEXTRA_APPWIDGET_IDS 添加到意图中,以便它会触发 onUpdate() 方法。此 SO answer 推荐了这种做法(尽管未包含在 activity 结果意图中 - 但我尝试将两者分开但没有效果)。

现在我不确定 "Google Sound Search" 小部件是如何进入其中的,我也不完全理解意图如何相互作用以产生观察到的结果的机制。但是,一旦我将上面的代码替换为文档中所述的代码,小部件就会正确更新。

    Intent resultIntent = new Intent();
    resultIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
    setResult(RESULT_OK, resultIntent);
    finish();

这似乎与文档的声明相矛盾,即配置 activity 必须更新小部件的视图。只需提供如下配置 activity 结果即可触发小部件中的 onUpdate() 方法,从而允许小部件重绘自身。我在模拟器 运行 API 23 以及三星设备 运行 三星的 android flavor.

上确认了该行为

你的小部件有几个问题,所有问题都有答案(虽然你没有 post 任何代码,所以我的一些陈述是基于假设的):

"Problem loading widget":这是 Android 在小部件初始化和布局更新之前使用的默认视图。只需将以下行添加到您的小部件 xml 配置(以显示加载消息而不是问题消息):

android:initialLayout="@layout/my_cool_widget_loading_message"

如果小部件显示错误的布局,那么您可能在小部件的 onReceive 方法中遇到了问题。 onReceive 会为所有小部件调用,无论广播是否针对该特定小部件。 Android 的 AppWidgetProvider 通过 appwidget Id 过滤广播并分派给其他方法(如 onUpdate)。

另请参阅:https://developer.android.com/reference/android/appwidget/AppWidgetProvider.html#onReceive(android.content.Context、android.content.Intent)。

如果您重写 onReceive(我假设您这样做),您需要调用 super.onReceive(Context, Intent) 以确保您的其他方法不会收到针对其他小部件的调用。

现在进行小部件的配置。如果您遵循 Google 文档,那么它会很好地工作。我要做的唯一改进是您引用的我的其他答案所建议的 ()。这不会发出两个广播。 setResult()/finish() 部分只终止配置 Activity 并让 Android 知道是否实际添加小部件(取决于结果是 RESULT_CANCELED 还是 RESULT_OK.

从您自己的回答中我可以看出为什么您的代码无法运行。代码是:

Intent intent = new Intent();
    intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
    intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] {mAppWidgetId});
    intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
    setResult(RESULT_OK, intent);
    sendBroadcast(intent);
    finish();

首先不需要两次添加appWidgetId,使用AppWidgetManager.EXTRA_APPWIDGET_IDS版本就可以了。其次,您使用与 return 相同的 Intent 作为 Activity 的结果。 AFAIK 没有记录当您对该 Intent 设置操作时会发生什么,但我对 Android 小部件的经验是您需要严格遵守文档,否则您最终会遇到奇怪的问题(比如您遇到的问题).所以请使用两个不同的 Intent。 Activity 结果:

Intent resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
setResult(RESULT_OK, resultValue);
finish();

广播:

Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE, null, this, MyWidget.class);
        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] {mAppWidgetId});
        sendBroadcast(intent);