如何创建 Activity 的意图但具有更新的内容(不是初始的)?

How to create an intent to an Activity but with updated content (not initial)?

我是 Android SDK 的新手,我开发了一个使用 AlarmManager 每 30 分钟发送一次通知的应用程序。每个通知都包含一个操作按钮。在 MainActivity 中,我有一个 TextView,其中显示了一个数字,用于计算操作按钮被单击的次数。

有一个用例显示的计数不正确。为了重现该问题,我执行了以下步骤:

  1. 应用程序已最小化,我正在等待通知。
  2. 单击操作按钮
  3. 点击通知进入应用程序

应用程序自行打开,但 TextView 仍显示“0”(与初始状态一样)。但是当我点击设备上的 BACK 按钮时,计数变成了正确的。

当我点击通知时,我收到一条警告,这可能是问题的原因: W/ActivityThread: handleWindowVisibility: no activity for token android.os.BinderProxy@c8e60ce

我试图在 MainActivity 的 onCreate() 方法中用这一行来解决这个问题: mCount.setText(String.valueOf(mCorrectPostureReceiver.getCount())); 但是没用。

我还应该提到 当我单击操作按钮然后使用启动器转到应用程序(而不是通过点击通知)时,一切正常。

MainActivity.java

//
//imports
//

public class MainActivity extends AppCompatActivity {
    private static final String LOG_TAG = MainActivity.class.getSimpleName();
    private static String CORRECT_POSTURE_COUNT;
    private NotificationManager mNotifyManager;
    private static final int NOTIFICATION_ID = 0;
    private static final String PRIMARY_CHANNEL_ID = "primary_notification_channel";
    private CorrectPostureReceiver mCorrectPostureReceiver = new CorrectPostureReceiver(this);
    static final String POSTURE_YES_ACTION = BuildConfig.APPLICATION_ID + ".POSTURE_YES_ACTION";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        createNotificationChannel();

        ToggleButton mNotifToggle = findViewById(R.id.notify_toggle);
        TextView mCount = findViewById(R.id.txt_count);
        mCount.setText(String.valueOf(mCorrectPostureReceiver.getCount())); //doesn't work

        //set alarmPendingIntent to deliver repeating notifications
        final AlarmManager mAlarmManager = (AlarmManager) this.getSystemService(ALARM_SERVICE);
        Intent alarmIntent = new Intent(this, AlarmReceiver.class);
        mNotifToggle.setChecked(PendingIntent.getBroadcast(this, NOTIFICATION_ID, alarmIntent, PendingIntent.FLAG_NO_CREATE) != null); // check if notifications were turned on before the new MainActivity was stopped
        final PendingIntent alarmPendingIntent = PendingIntent.getBroadcast(this, NOTIFICATION_ID, alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT);

        registerReceiver(mCorrectPostureReceiver, new IntentFilter(POSTURE_YES_ACTION));

        mNotifToggle.setOnCheckedChangeListener(
                new CompoundButton.OnCheckedChangeListener() {
                    @Override
                    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                        String toastMessage = "error: AlarmManager is null";
                        if(isChecked) {
                            long repeatInterval;

                            //is emulator or device
                            if(Build.FINGERPRINT.startsWith("google/sdk_gphone_x86/generic")) {
                                repeatInterval = 30000; //short interval only for debug }                  
                            else { repeatInterval = AlarmManager.INTERVAL_HALF_HOUR; }

                            long triggerTime = SystemClock.elapsedRealtime() + repeatInterval;
                            if(mAlarmManager!=null) {
                                mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerTime, repeatInterval, alarmPendingIntent);
                                toastMessage = "Upright notifications on.";
                            }
                        }
                        else {
                            mNotifyManager.cancelAll();
                            if (mAlarmManager!=null) {
                                mAlarmManager.cancel(alarmPendingIntent);
                            }
                            toastMessage = "Upright notifications off.";
                        }
                        Toast.makeText(MainActivity.this, toastMessage, Toast.LENGTH_SHORT).show();
                    }
                }
        );
        Log.d(LOG_TAG, "A: created");
    }


    @Override
    protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
//        super.onRestoreInstanceState(savedInstanceState);  //should be commmented?
        mCorrectPostureReceiver.getTxtCount().setText(savedInstanceState.getString(CORRECT_POSTURE_COUNT));
    }

    @Override
    protected void onSaveInstanceState(@NonNull Bundle outState) {
        outState.putString(CORRECT_POSTURE_COUNT, String.valueOf(mCorrectPostureReceiver.getCount()));
        super.onSaveInstanceState(outState);
    }

    private void createNotificationChannel() {
        mNotifyManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        long[] vibPattern = {0, 200, 200, 200, 200, 200}; //{delay1, vibDuration1, delay2, vibDuration2...}
        if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            NotificationChannel notificationChannel = new NotificationChannel(
                    PRIMARY_CHANNEL_ID,
                    "Check posture notification",
                    NotificationManager.IMPORTANCE_HIGH);
            notificationChannel.setDescription("notification check posture");
            notificationChannel.enableVibration(true);
            notificationChannel.setVibrationPattern(vibPattern);
            mNotifyManager.createNotificationChannel(notificationChannel);
        }
    }


}

AlarmReceiver.java - 从 AlarmManager 接收重复的意图消息并传递通知

//
//imports
//

public class AlarmReceiver extends BroadcastReceiver {

    private NotificationManager mNotificationManager;
    private static final int NOTIFICATION_ID = 0;
    private static final String PRIMARY_CHANNEL_ID = "primary_notification_channel";
    private static final String LOG_TAG = AlarmReceiver.class.getSimpleName();

    @Override
    public void onReceive(Context context, Intent intent) {
        mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        deliverNotification(context);
        Log.d(LOG_TAG, "notification fired!");
    }

    private void deliverNotification(Context context) {

        //this intent causes that when the notification is clicked, the MainActivity is launched
        Intent notifClickIntent = new Intent(context, MainActivity.class); //when the notification is clicked, the MainActivity is launched
        PendingIntent notifClickPendingIntent = PendingIntent.getActivity(
                context,
                NOTIFICATION_ID,
                notifClickIntent,
                PendingIntent.FLAG_UPDATE_CURRENT
        );
     
        // after you click the action button on notification, this intent will be sent
        Intent postureYesIntent = new Intent(MainActivity.POSTURE_YES_ACTION);
        PendingIntent yesPendingIntent = PendingIntent.getBroadcast(context, NOTIFICATION_ID, postureYesIntent, PendingIntent.FLAG_UPDATE_CURRENT);

        NotificationCompat.Builder builder = new NotificationCompat.Builder(context, PRIMARY_CHANNEL_ID)
                .setContentTitle("Are you straighten up?")
                .setSmallIcon(R.drawable.ic_notify)
                .setAutoCancel(true)
                .setContentIntent(notifClickPendingIntent)
                .addAction(R.drawable.ic_posture_yes, "yes", yesPendingIntent)
                .setPriority(NotificationCompat.PRIORITY_HIGH)
                .setDefaults(NotificationCompat.DEFAULT_ALL);
        mNotificationManager.notify(NOTIFICATION_ID, builder.build());
    }
}

CorrectPostureReceiver.java - 当点击通知上的操作按钮时收到一条意向消息。

//
//imports
//

class CorrectPostureReceiver extends BroadcastReceiver {

    private int mCount = 0;
    private TextView mTxtCount;
    private final String LOG_TAG = CorrectPostureReceiver.class.getSimpleName();
    private MainActivity mainActivity;
    public CorrectPostureReceiver(MainActivity mainActivity) {
        this.mainActivity = mainActivity;
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(LOG_TAG, "yes option clicked");
        mTxtCount = mainActivity.findViewById(R.id.txt_count);
        if (mTxtCount != null) {
            mCount++;
            mTxtCount.setText(Integer.toString(mCount));
        }
    }

    public int getCount() {
        return mCount;
    }
    public TextView getTxtCount() {
        return mTxtCount;
    }
}

AndroidManifest.xml片段

<activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
</activity>

如何解决? 如果您对我的代码有任何其他建议,请写信给我。

当您单击 Notification 时,Android 正在启动 MainActivity 的新实例。这是一个与 MainActivity 的原始实例完全不同的对象,它仍然存在并且正在被新实例完全覆盖。这就是计数显示为零的原因,也是为什么当您按下 BACK 时,计数显示正确的原因(按下 BACK 后,MainActivity 的新实例被销毁,显示了先前的原始实例 MainActivity 在它下面)。

您应该在创建 Notification 时将 Intent.FLAG_ACTIVITY_SINGLE_TOP 添加到 Intent:

    //this intent causes that when the notification is clicked, the MainActivity is launched
    Intent notifClickIntent = new Intent(context, MainActivity.class); //when the notification is clicked, the MainActivity is launched
    notifClickIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
    PendingIntent notifClickPendingIntent = PendingIntent.getActivity(
            context,
            NOTIFICATION_ID,
            notifClickIntent,
            PendingIntent.FLAG_UPDATE_CURRENT
    );

另一种解决方案是在 AndroidManifest.xml 中设置 android:launchMode="singleInstance"(在本例中为 MainActivity)。

这比设置标志 FLAG_ACTIVITY_SINGLE_TOP(如 )更好,因为当您有多个 Activity 而另一个 Activity 位于顶部时Activity 堆栈,然后单击通知,问题中的问题仍然会发生。