Android - 服务发送多个本地通知时出现问题

Android - Trouble with service sending multiple local notifications

我继承了一个 Android 应用程序的代码库,但我正面临一个与本地通知有关的特殊问题。

想法是为每个安排在未来的事件发送通知,同时考虑关于用户希望在事件发生前多少分钟收到通知的提醒偏好。

一切正常,除了第一次抛出通知后,如果用户在事件开始前打开应用程序,则会再次抛出通知。每次在(活动开始日期 - 提醒)和活动开始日期之间打开应用程序时都会发生这种情况。

我已经看了 this and also this 了,但运气不好。 我读过使用服务可能会导致这个问题,有些人建议删除它,但我认为这是必要的,因为当应用程序关闭时也必须抛出通知。

目前代码结构如下:

编辑 - 更新了 TabBarActivity 的描述

在 TabBarActivity 中,我有方法 scheduleTravelNotification 来安排 AlarmManager。 每当有新事件要添加到本地数据库,或者现有事件已更新时,都会执行此方法。 TabBarActivity 在 onCreate 和 onResume 方法中运行这个方法。 TabBarActivity 也是通知的目标-onclick 事件。

private static void scheduleTravelNotification(Context context, RouteItem routeItem) {

    long currentTime = System.currentTimeMillis();
    int alarmTimeBefore = routeItem.getAlarmTimeBefore();
    long alarmTime = routeItem.getStartTime() - (alarmTimeBefore * 1000 * 60);

    if(alarmTimeBefore < 0){
        return;
    }

    if(alarmTime < currentTime){
        return;
    }

    Intent actionOnClickIntent = new Intent(context, TravelNotificationReceiver.class);
    PendingIntent travelServiceIntent = PendingIntent.getBroadcast(context, System.currentTimeMillis(), actionOnClickIntent, PendingIntent.FLAG_ONE_SHOT);

    Calendar calendar = Calendar.getInstance();
    calendar.setTimeInMillis(alarmTime);
    AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
    alarmManager.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), travelServiceIntent);

    Log.e("NEXT ALARM", "Time: " + String.valueOf(calendar.getTimeInMillis()));
}

这是TravelNotificationReceiver.java(我应该使用 LocalBroadcastReceiver 而不是 BroadcastReceiver 吗?)

public class TravelNotificationReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.e("RECEIVER", "received TravelNotification request");
        Intent notificationIntent = new Intent(context, TravelNotificationService.class);
        context.startService(notificationIntent);
    }
}

TravelNotificationService.java 扩展 NotificationService.java 设置为 type = "Travel", flags = 0,标题 = "something" 和文本 = "something else".

public abstract class NotificationService extends Service {

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        sendNotification();
        return super.onStartCommand(intent, flags, startId);
    }

    public abstract String setNotificationType();
    public abstract int setNotificationFlags();
    public abstract String setNotificationTitle();
    public abstract String setNotificationText();

    /**
     * Executes all the logic to init the service, prepare and send the notification
     */
    private void sendNotification() {

        int flags = setNotificationFlags();
        String type = setNotificationType();

        NotificationHelper.logger(type, "Received request");

        // Setup notification manager, intent and pending intent
        NotificationManager manager = (NotificationManager) this.getApplicationContext().getSystemService(this.getApplicationContext().NOTIFICATION_SERVICE);
        Intent intentAction = new Intent(this.getApplicationContext(), TabBarActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this.getApplicationContext(), 0, intentAction, flags);

        // Prepares notification
        String title = setNotificationTitle();
        String text = setNotificationText();
        Notification notification = NotificationHelper.buildNotification(getApplicationContext(), title, text, pendingIntent);

        // Effectively send the notification
        manager.notify(101, notification);

        NotificationHelper.logger(type, "Notified");
    }
}

编辑 - 这是 NotificationHelper.buildNotification

的代码
    public static Notification buildNotification(Context context, String title, String text, PendingIntent pendingIntent) {

        NotificationCompat.Builder builder = new NotificationCompat.Builder(context);

        builder.setAutoCancel(true);
        builder.setContentText(text);
        builder.setContentTitle(title);
        builder.setContentIntent(pendingIntent);
        builder.setSmallIcon(R.mipmap.launcher);
        builder.setCategory(Notification.CATEGORY_MESSAGE);
        builder.setVisibility(Notification.VISIBILITY_PUBLIC);

        return builder.build();
    }

感谢您的回答!

编辑 我也看到 this but has no accepted answers, while post 建议一些我认为已经用 if(alarmTime < currentTime){ return; 管理的东西} 在 scheduleTravelNotification.

这可能不是您的确切问题,但乍一看,您是在 onStartCommand() 中发送通知,它本身在服务的生命周期内可能 运行 多次 - 例如,如果您在 activity 的 onCreate 中发出服务启动命令 "blindly",它会在每次(重新)创建 activity 时发生。

您有几种处理方法。

一种是创建一个布尔标志作为服务的属性,默认为false,并在发送通知之前检查它。如果为假,则发送通知并将其设置为真,如果已经为真,则不发送通知。

还有一个是查看服务是否已经运行ning,如果是,则首先不要发送服务启动命令。这在任何地方都可能很乏味,并且违反了 DRY,因此如果您采用这种方式,您可能需要在服务 class 中创建一个静态方法来检查服务是否 运行ning 然后如果没有启动它,并调用它而不是显式启动服务。

类似于 user3137702 的回答,您可以简单地使用 APPISINFORGROUND 的静态布尔值,每次发送通知方法时都会检查它,并通过您的 application/activities 代码进行管理。

正如用户所说,由于应用程序/服务生命周期的原因,您的 onStartCommand 方法很可能在奇数时间被调用。

或者检查您的接收器没有在您的代码的其他地方被调用。

可能是您的 NotificationHelper class 导致了问题。请分享此 class.

的代码

可能是您的通知未设置为自动取消,请检查您的通知生成器中是否包含 setAutoCancel() 方法。

Notification notification = new Notification.Builder(this).setAutoCancel(true).build();

我找到了让它工作的方法,我发布这个是因为它似乎是许多使用 this and this 文章中建议的方法的人的问题。经过几个月的测试,我可以说我对我找到的解决方案非常满意。 关键是避免使用服务并依赖 AlarmScheduler 和 Receivers。

1) 通过添加以下行在您的清单中注册接收器:

<receiver android:name="<your path to>.AlarmReceiver" />

2) 在您的 activity 或逻辑中,您希望在某个时候安排与对象相关的通知

private void scheduleNotification(MyObject myObject) {

    // Cal object to fix notification time
    Calendar cal = Calendar.getInstance();
    cal.setTimeInMillis(myObject.getTime());

    // Build intent and extras: pass id in case you need extra details in notification text
    // AlarmReceiver.class will receive the pending intent at specified time and handle in proper way
    Intent intent = new Intent(this, AlarmReceiver.class);
    intent.putExtra("OBJECT_ID", myObject.getId());

    // Schedule alarm
    // Get alarmManager system service
    AlarmManager alarmManager = (AlarmManager) getApplicationContext().getSystemService(getBaseContext().ALARM_SERVICE);

    // Build pending intent (will trigger the alarm) passing the object id (must be int), and use PendingIntent.FLAG_UPDATE_CURRENT to replace existing intents with same id
    PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), myObject.getId(), intent, PendingIntent.FLAG_UPDATE_CURRENT);

    // Finally schedule the alarm
    alarmManager.set(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), pendingIntent);
}

3) 定义报警接收器

public class AlarmReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {

        // Find object details by using objectId form intent extras (I use Realm but it can be your SQL db)
        MyObject myObject = RealmManager.MyObjectDealer.getObjectById(intent.getStringExtra("OBJECT_ID"), context);

        // Prepare notification title and text
        String title = myObject.getSubject();
        String text = myObject.getFullContent();

        // Prepare notification intent
        // HomeActivity is the class that will be opened when user clicks on notification
        Intent intentAction = new Intent(context, HomeActivity.class);

        // Same procedure for pendingNotification as in method of step2
        PendingIntent pendingNotificationIntent = PendingIntent.getActivity(context, myObject.getId(), intentAction, PendingIntent.FLAG_UPDATE_CURRENT);

        // Send notification (I have a static method in NotificationHelper)
        NotificationHelper.createAndSendNotification(context, title, text, pendingNotificationIntent);
    }
}

4) 定义 NotificationHelper

public class NotificationHelper {

    public static void createAndSendNotification(Context context, String title, String text, PendingIntent pendingNotificationIntent) {

        // Get notification system service
        NotificationManager notificationManager = (NotificationManager) context.getSystemService(context.NOTIFICATION_SERVICE);

        // Build notification defining each property like sound, icon and so on
        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context);
        notificationBuilder.setContentTitle(title);
        notificationBuilder.setContentText(text);
        notificationBuilder.setSmallIcon(R.drawable.ic_done);
        notificationBuilder.setCategory(Notification.CATEGORY_MESSAGE);
        notificationBuilder.setVisibility(Notification.VISIBILITY_PUBLIC);
        notificationBuilder.setAutoCancel(true);
        notificationBuilder.setContentIntent(pendingNotificationIntent);
        notificationBuilder.setDefaults(Notification.DEFAULT_SOUND);
        notificationManager.notify(1001, notificationBuilder.build());
    }
}

此时它应该工作并在正确的时间安排/触发通知,并且当通知打开时它只会出现一次,开始 activity 在通知未决意图中声明。

仍然存在问题,AlarmManager 在用户设备上有一个 "volatile" 存储空间,因此如果用户重新启动或关闭 phone,您将失去之前计划的所有意图。 但幸运的是,还有一个解决方案:

5) 在您的清单顶部添加此使用权限

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

6) 在第 1 步添加的行的正下方注册引导接收器

<receiver android:name="<your path to>.BootReceiver" >
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>

7) 定义 BootReceiver

public class BootReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {

        // Do something very similar to AlarmReceiver but this time (at least in my case) since you have no source of intents loop through collection of items to understand if you need to schedule an alarm or not
        // The code is pretty similar to step 3 but repeated in a loop
    }
}

此时您的应用应该能够安排/触发通知并恢复这些提醒,即使 phone 已关闭或重新启动也是如此。

希望此解决方案对某人有所帮助!