Android 应用小部件实例发送相同的待处理意图

Posted

技术标签:

【中文标题】Android 应用小部件实例发送相同的待处理意图【英文标题】:Android app widget instances send the same pending intent 【发布时间】:2017-05-20 11:33:08 【问题描述】:

我在使用小部件时看到了奇怪的行为。我确信它以前有效,但无论如何,现在不行。不知道我改变了什么或者它是否真的有效。下面的代码创建了我正在尝试做的简化版本。一个应用程序提供一个小部件。当它出现在屏幕上时,会提示用户输入一个数字。然后该数字会显示在小部件上。单击小部件会将数字打印到日志中。可以使用不同的编号创建此小部件的多个实例。单击每一个应生成与为每个小部件选择的编号相对应的日志条目。

但是,我看到了某种缓存行为。假设我创建了小部件35。单击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_CURRENTFLAG_UPDATE_CURRENT

【讨论】:

正如@Vadym 所说,Android 执行了一些重用逻辑,并且似乎涉及方法 Intent.filterEquals ,它不检查 Intent 额外内容。您需要做的是填写该方法比较的一些字段,例如:myIntent.setData(Uri.parse(myIntent.toUri(Intent.URI_INTENT_SCHEME)));

以上是关于Android 应用小部件实例发送相同的待处理意图的主要内容,如果未能解决你的问题,请参考以下文章

Android - 带有标志的待处理意图

多个 Android Widget 实例仅更新最后一个小部件

Android 小部件通过意图将数据发送到 onRecive

缺少可变性标志:带有 NavDeepLinkBuilder 的 Android 12 待定意图

Android:配置小部件(不同小部件实例的相同 setOnClickPendingIntent 附加功能)

android小部件-重启后并不总是注册待处理的意图