Android - 服务发送多个本地通知时遇到问题

时间:2015-12-15 15:18:29

标签: android android-intent notifications android-service android-broadcastreceiver

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

我们的想法是为将来安排的每个活动发送通知,同时考虑用户希望收到通知的事件前几分钟的提醒偏好。

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

我已经看过thisthis而没有运气。 我已经读过使用服务可能会导致这个问题,有些人建议删除它,但我认为这是必需的,因为在关闭应用程序时也必须抛出通知。

目前代码的结构如下:

修改 - TabBarActivity的更新说明

Inside 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,title =“something”和text =“something else”。< / p>

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但没有接受的答案,而this帖子提示我认为已经使用if(alarmTime&lt; currentTime){return;在scheduleTravelNotification中

4 个答案:

答案 0 :(得分:0)

这可能不是您的确切问题,但一目了然,您在onStartCommand()中发送通知,该通知本身可以在服务的生命周期内多次运行 - 例如,如果您发出服务启动命令&#34;盲目&#34;在活动的onCreate中,每次(重新)创建活动时都会发生。

您可以选择处理此问题。

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

另一种方法是检查并查看服务是否已在运行,如果是,请不要首先发送服务启动命令。这可能在任何地方都很繁琐,并且违反DRY,所以如果你采用这条路线,你可能想在服务类中创建一个静态方法,检查服务是否正在运行,然后启动它,如果没有,则调用它显式启动服务。

答案 1 :(得分:0)

类似于user3137702,您可以简单地使用APPISINFORGROUND的静态布尔值,每次发送通知方法时都会检查该布尔值,并从您的应用程序/活动代码进行管理。

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

或者检查您的接收器是否未在您的代码中的其他位置被调用。

答案 2 :(得分:0)

可能是您的NotificationHelper类导致了问题。请分享此课程的代码。

有人可能认为您的通知未设置为自动取消,请检查您是否在通知构建器中包含setAutoCancel()方法。

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

答案 3 :(得分:0)

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

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

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

2)在您的活动或逻辑中,您希望安排与对象相关的通知

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

此时它应该在正确的时间工作并安排/触发通知,并且当通知打开时,它将仅在启动通知待处理意图中声明的活动时出现。

仍有问题,AlarmManager在用户设备上有一个“易失性”存储空间,因此如果用户重新启动或关闭手机,您将失去之前安排的所有意图。 但幸运的是,还有一个解决方案:

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
    }
}

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

希望这个解决方案可以帮助别人!