Android 应用小部件实例发送相同的待处理意图
Posted
技术标签:
【中文标题】Android 应用小部件实例发送相同的待处理意图【英文标题】:Android app widget instances send the same pending intent 【发布时间】:2017-05-20 11:33:08 【问题描述】:我在使用小部件时看到了奇怪的行为。我确信它以前有效,但无论如何,现在不行。不知道我改变了什么或者它是否真的有效。下面的代码创建了我正在尝试做的简化版本。一个应用程序提供一个小部件。当它出现在屏幕上时,会提示用户输入一个数字。然后该数字会显示在小部件上。单击小部件会将数字打印到日志中。可以使用不同的编号创建此小部件的多个实例。单击每一个应生成与为每个小部件选择的编号相对应的日志条目。
但是,我看到了某种缓存行为。假设我创建了小部件3
和5
。单击3
将记录3
。单击5
将记录3
。删除3
并单击5
将再次记录3
。这是如何运作的?看起来第一个小部件已经创建并且它的视图被缓存了?
Manifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.developer.app">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".AppApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".activity.DashboardActivity"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".activity.DummyWidgetConfigActivity"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
</intent-filter>
</activity>
<receiver android:name=".DummyWidgetProvider">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/dummy_widget_info" />
</receiver>
<service
android:name=".service.DummyWidgetService"
android:exported="false" />
</application>
</manifest>
DummyWidgetConfigActivity.java
package com.developer.app.activity;
import android.appwidget.AppWidgetManager;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.developer.app.DummyWidgetProvider;
import com.developer.app.R;
/**
* TODO: Add a class header comment!
*/
public class DummyWidgetConfigActivity extends AppCompatActivity
private static final String TAG = "DummyWgtConfigActivity";
private int appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
private TextView numberInput = null;
private Button submitButton = null;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setResult(RESULT_CANCELED);
setContentView(R.layout.activity_dummy_config);
Intent intent = getIntent();
Bundle extras = intent.getExtras();
if (extras != null)
appWidgetId = extras.getInt(
AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID)
finish();
numberInput = (TextView) findViewById(R.id.number_input);
submitButton = (Button) findViewById(R.id.submit);
submitButton.setOnClickListener(v ->
try
final int number = Integer.parseInt(numberInput.getText().toString());
onNumberSelect(number);
catch (Exception e)
Toast.makeText(this, "Must be a number", Toast.LENGTH_SHORT).show();
);
public void onNumberSelect(final int number)
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
SharedPreferences.Editor editor = sp.edit();
editor.putInt("widget_" + appWidgetId, number);
editor.commit();
Log.d(TAG, "Creating widget with id " + appWidgetId);
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this);
DummyWidgetProvider.updateAppWidget(this, appWidgetManager, appWidgetId);
Intent resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
setResult(RESULT_OK, resultValue);
finish();
DummyWidgetProvider.java
package com.developer.app;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;
import android.widget.RemoteViews;
import com.developer.app.service.DummyWidgetService;
/**
* TODO: Add a class header comment!
*/
public class DummyWidgetProvider extends AppWidgetProvider
private static final String TAG = "DummyWidgetProvider";
public void onUpdate(final Context context, final AppWidgetManager appWidgetManager, final int[] appWidgetIds)
for (int appWidgetId : appWidgetIds)
updateAppWidget(context, appWidgetManager, appWidgetId);
public static void updateAppWidget(final Context context, final AppWidgetManager appWidgetManager, final int appWidgetId)
final SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
final int number = sp.getInt("widget_" + appWidgetId, -1);
if (number == -1)
Log.e(TAG, "Could not find the number associated with widget " + appWidgetId);
return;
Log.d(TAG, "Updating widget with id " + appWidgetId);
Intent intent = DummyWidgetService.createShowNumberIntent(context, number, appWidgetId);
PendingIntent pendingIntent = PendingIntent.getService(context, 1, intent, 0);
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_dummy);
views.setTextViewText(R.id.widget_number, Integer.toString(number));
views.setOnClickPendingIntent(R.id.widget_number, pendingIntent);
appWidgetManager.updateAppWidget(appWidgetId, views);
DummyWidgetService.java
package com.developer.app.service;
import android.app.IntentService;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.widget.Toast;
/**
* TODO: Add a class header comment!
*/
public class DummyWidgetService extends IntentService
private static final String TAG = "DummyWidgetService";
private static final String DUMMY_WIDGET_SERVICE_WORKER_THREAD = "com.developer.app.THREAD.dummy_widget_service";
private static final String EXTRA_NUMBER_ID = "com.developer.app.service.DummyWidgetService.extra.NumberId";
private static final String EXTRA_WIDGET_ID = "com.developer.app.service.DummyWidgetService.extra.WidgetId";
public final static String EXTRA_ACTION = "com.developer.app.service.DummyWidgetService.extra.ACTION";
private final static int ACTION_UNKNOWN = -1;
private final static int ACTION_SHOW_NUMBER = 1;
public DummyWidgetService()
super(DUMMY_WIDGET_SERVICE_WORKER_THREAD);
public static Intent createShowNumberIntent(final Context context, final int number, final int widgetId)
Intent intent = new Intent(context, DummyWidgetService.class);
intent.putExtra(EXTRA_ACTION, ACTION_SHOW_NUMBER);
intent.putExtra(EXTRA_NUMBER_ID, number);
intent.putExtra(EXTRA_WIDGET_ID, widgetId);
return intent;
@Override
protected void onHandleIntent(Intent intent)
switch (intent.getIntExtra(EXTRA_ACTION, ACTION_UNKNOWN))
case ACTION_SHOW_NUMBER:
final int number = intent.getIntExtra(EXTRA_NUMBER_ID, -1);
final int widgetId = intent.getIntExtra(EXTRA_WIDGET_ID, -1);
//Toast.makeText(this, "Widget number: " + number, Toast.LENGTH_SHORT).show();
Log.i(TAG, "Widget number: " + number);
break;
default:
Log.w(TAG, "Unknown action requested!");
dummy_widget_info.xml
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="40dp"
android:minHeight="40dp"
android:configure="com.developer.app.activity.DummyWidgetConfigActivity"
android:updatePeriodMillis="86400000"
android:initialLayout="@layout/widget_dummy"
android:resizeMode="none"
android:widgetCategory="home_screen">
</appwidget-provider>
activity_dummy_config.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_
android:layout_>
<EditText
android:id="@+id/number_input"
android:layout_
android:layout_
android:inputType="number"/>
<Button
android:id="@+id/submit"
android:layout_
android:layout_
android:text="Submit"/>
</LinearLayout>
widget_dummy.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_
android:layout_>
<TextView
android:id="@+id/widget_number"
android:layout_
android:layout_
android:textAlignment="center"/>
</LinearLayout>
感谢您的任何建议!我确实看到其他应用程序能够正确处理多个实例。因此,我认为这纯粹是我的错误。
【问题讨论】:
【参考方案1】:问题在于挂起的 Intent 是由具有相同内部 ID 的同一进程(可能是 Android 操作系统应用)创建的
PendingIntent pendingIntent = PendingIntent.getService(context, 1, intent, 0);
在上面的代码中,1
用于每个小部件实例。因此,这只是重用了意图。该值可以更改为appWidgetId
。此外,标志可以从0
更改为FLAG_CANCEL_CURRENT
或FLAG_UPDATE_CURRENT
。
【讨论】:
正如@Vadym 所说,Android 执行了一些重用逻辑,并且似乎涉及方法 Intent.filterEquals ,它不检查 Intent 额外内容。您需要做的是填写该方法比较的一些字段,例如:myIntent.setData(Uri.parse(myIntent.toUri(Intent.URI_INTENT_SCHEME)));以上是关于Android 应用小部件实例发送相同的待处理意图的主要内容,如果未能解决你的问题,请参考以下文章
多个 Android Widget 实例仅更新最后一个小部件
Android 小部件通过意图将数据发送到 onRecive
缺少可变性标志:带有 NavDeepLinkBuilder 的 Android 12 待定意图