Android - Trouble with service sending multiple local notifications

喜欢而已 提交于 2019-12-20 04:08:15

问题


I've inherited a code base for an Android app and I'm facing a particularly though problem with local notifications.

The idea is to send a notification for each event which is scheduled in the future, considering also the reminder preference on how many minutes before the event the user wants to be notified.

Everything works just fine, except that after the notification is thrown for the first time, if the user opens the app before the event starts, the notification gets thrown another time. This happens every time the app is opened between (event start date - reminder) and event start date.

I've already gave a look at this and also this with no luck. I've read that using a service may cause exactly this problem and some suggest to remove it but I think this is needed since the notification must be thrown also when the app is closed.

Currently the structure of the code is the following:

Edit - updated description of TabBarActivity

Inside TabBarActivity I have the method scheduleTravelNotification that schedules the AlarmManager. This method is executed everytime there is a new event to be added on local database, or if an existing event have been updated. The TabBarActivity runs this method inside the onCreate and onResume methods. TabBarActivity is also the target of the notification - onclick event.

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()));
}

This is TravelNotificationReceiver.java (should I use LocalBroadcastReceiver instead of 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 extends NotificationService.java setting as type = "Travel", flags = 0, title = "something" and text = "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");
    }
}

Edit - Here's the code for 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();
    }

Thank you for the answers!

Edit I've seen also this but has no accepted answers, while this post suggest something that I think it's already managed with if(alarmTime < currentTime){ return; } in scheduleTravelNotification.


回答1:


This may not be your exact problem, but at a glance, you're sending the notification in onStartCommand() which can itself be run many times during the lifetime of the service -- for example, if you issue the service start command "blindly" in an onCreate of an activity, it will happen every time the activity is (re)created.

You have a few options for handling this.

One is to create a boolean flag as a property of the service, default to false, and check it before sending the notification. If it's false, send the notification and set it to true, and if it's already true you do not send a notification.

Another is to check and see if the service is already running, and if it is, don't send the service start command in the first place. This can be tedious to do everywhere, and violates DRY, so if you take this route you may want to create a static method in your service class which checks to see if the service is running and then starts it if not, and call that instead of explicitly starting the service.




回答2:


Similar to user3137702 answer you could simple have a static boolean of APPISINFORGROUND which is checked everytime the send notification method is hit, and managed from your application/activities code.

As User said it is likely that your onStartCommand method is being called at odd times due to the app / service lifecycle.

Alternatively check your receiver is not being called somewhere else from your code.




回答3:


It may be your NotificationHelper class which is causing an issue. Please share the code for this class.

One thought may be that your notification is not set to be auto cancelled, check if you include the setAutoCancel() method in your Notification Builder.

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



回答4:


I've found a way to make it work, I'm posting this since it seems to be a problem of many people using the approach suggested in this and this articles. After months of testing I can say I'm pretty satisfied with the solution I've found. The key is to avoid usage of Services and rely on AlarmScheduler and Receivers.

1) Register the receiver in your manifest by adding this line:

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

2) In your activity or logic at some point you want to schedule a notification related to an object

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) Define AlarmReceiver

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) Define 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());
    }
}

At this point it should work and schedule / trigger notification at the right time, and when notification is opened it will appear only once starting the activity declared in notification pending intent.

There is still a problem, AlarmManager have a "volatile" storage on user device, so if user reboots or switch off the phone you will lose all intents that you previously scheduled. But fortunately there is also a solution for that:

5) Add at top of your manifest this uses permission

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

6) Right below the line added at step 1 register the boot receiver

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

7) Define the 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
    }
}

At this point your app should be able to schedule / trigger notification and restores those reminders even if the phone is switched off or rebooted.

Hope this solution will help someone!



来源:https://stackoverflow.com/questions/34292858/android-trouble-with-service-sending-multiple-local-notifications

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!