Notifications not received on Android

2019-02-09 12:46发布

问题:

I am developing an android-application which main usage is displaying notifications at a set time (some specific calendar app). One of the main complaints is that users do not (always) receive notifications, and I am at wits end.

We have internally tested the code below against Android 4.4, 6, 7.0, 7.1, 8.0, 8.1 on emulators and used about 10 real devices (6 to 8.1), and all devices received their notifications on time. Even across reboots, the notifications were all received on time.

One of the things we have ran into was the SecurityException on Samsung devices (>500 registered alarms), which we had previously triggered due to unproper cancelling. It looks like that no longer is an issue.

So, what could be the cause for these missing notifications? Is it a device specific setting, is it a simple bug? Or are there other factors at play here?

This is the code we are using:

private void cancelAlarm(String notificationId, Class<? extends AbstractReceiver> receiverClass)
        throws BroadcastException {
    /*
     * Create an intent that looks similar, to the one that was registered using add. Making sure the notification id in the action is the same. Now we can search for
     * such an intent using the 'getService' method and cancel it.
     */
    final Intent intent = new Intent(this.context, receiverClass);
    intent.setAction(notificationId);

    final PendingIntent pi = PendingIntent.getBroadcast(this.context, 0, intent, 0);
    final AlarmManager am = getAlarmManager();

    try {
        am.cancel(pi);
    } catch (Exception e) {
        Log.e(this.getClass().getSimpleName(), e.getMessage());
        throw new BroadcastException(e.getMessage());
    }
}

private void addOrUpdateAlarm(...){
    try {
        cancelAlarm(notificationId, OurReceiver.class);
    } catch (BroadcastException e) {
        Log.e(AlarmHelper.class.getSimpleName(), "addOrUpdateAlarm: Can't cancel current alarm before reinserting.", e);
    }

    Intent intent = new Intent(this.context, receiverClass);
    intent.setAction(notificationId);
    // some intent.setExtra() calls.
    PendingIntent sender = PendingIntent.getBroadcast(this.context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

    /* Get the AlarmManager service */
    final AlarmManager am = getAlarmManager();

    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), sender);
    }else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
        am.setExact(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), sender);
    }else{
        am.set(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), sender);
    }

}

and then in OurReceiver we create a notificationchannel:

    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        NotificationManager mNotificationManager =
                (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

        int importance = NotificationManager.IMPORTANCE_HIGH;
        NotificationChannel mChannel = new NotificationChannel(id, name, importance);
        // Configure the notification channel.
        mChannel.setDescription(description);
        mChannel.enableLights(true);
        // Sets the notification light color for notifications posted to this
        // channel, if the device supports this feature.
        mChannel.setLightColor(Color.RED);
        mChannel.enableVibration(true);
        mChannel.setVibrationPattern(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400});
        mNotificationManager.createNotificationChannel(mChannel);
    }

and finally send a notification:

    PendingIntent pIntent = PendingIntent.getActivity(context, (int) System.currentTimeMillis(), intent, 0);

    Notification n = new NotificationCompat.Builder(context, channel)
            .setContentTitle(notificationTitle)
            .setContentText(notificationSubText)
            .setSmallIcon(R.drawable.logo)
            .setContentIntent(pIntent)
            .setDefaults(Notification.DEFAULT_SOUND|Notification.DEFAULT_VIBRATE)
            .setAutoCancel(true).build();

    NotificationManager notificationManager = 
              (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

            notificationManager.notify(0, n); 

回答1:

About notifications, I can tell you the following:

After a long time of developing an app which uses a lot of Alarms from AlarmManager I discovered a lot of things about some conflictive devices. (I didn't tried JobScheduler, but in most of cases this technology will also fail).

There are some manufacturers (well known, Huawei, Xiaomi, Samsung, etc) that interfers in the life cycle of AlarmManager.

Huawei and Xiaomi

Huawei by default kills all non protected apps when locking the screen. That's it, kills all resources of the app, including alarms, boradcast receivers and services. The app is completly killed after the screen is locked so the alarms are not received and the notifications are not shown logically. To avoid this situation, Huawei provides a way to put the apps in protected mode, which means that when the screen is locked, these protected apps are not killed. Protected apps, then, still receive alarms and broadcast receivers.

Samsung

Samsung has a "feature" (unwanted feature for developers) that do the "same" than Huawei and Xiaomi devices with a little difference. Samsung doesn't kills non protected apps when locking the scree, but when the app is not opened in 3 days. After 3 days of user inactivity the app is killed like Huawei and Xiaomi, so the notifications (alarms) are not received. Samsung also provides a way to protect the apps and avoid this situation.

Conclusion

There are other manufacturers with the same behavior, but I don't know all of them. I can tell you Huawei, Xiaomi and Samsung are the most known of their.

So, first try to know if all failing devices are manufactured by these conflictive manufacturers.

Then, there are some ways to let the user know that notifications may not fire in this devices, and invite them to put your application as a protected app.

Programmatically, you can do something like that (source):

if("huawei".equalsIgnoreCase(android.os.Build.MANUFACTURER)) { 
    AlertDialog.Builder builder  = new AlertDialog.Builder(this);
    builder.setTitle(R.string.huawei_headline).setMessage(R.string.huawei_text)
            .setPositiveButton(R.string.go_to_protected, new DialogInterface.OnClickListener() {
                @Override 
                public void onClick(DialogInterface dialogInterface, int i) {
                    Intent intent = new Intent();
                    intent.setComponent(new ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.optimize.process.ProtectActivity"));
                    startActivity(intent);
                } 
            }).create().show(); 
}

And you can do the same with other conflictive manufacturers. This is how I handle this situation in these devices, and notifications works well, in most of cases.

Hope this helps.



回答2:

The problem why you do not get any notifications after a while is because of DOZE.

You have 2 options for solving your issue:

  • "Remove" Doze from your app (https://www.greenbot.com/article/2993199/android/how-to-turn-off-doze-mode-for-specific-apps-in-android-marshmallow.html) (not recommended, each user has to do it, you cannot set it as default )

  • Use FCM High Priority as per documentation:

    *Doze checklist

    If possible, use FCM for downstream messaging. If your users must see a notification right away, make sure to use an FCM high priority message. Provide sufficient information within the initial message payload, so subsequent network access is unnecessary. Set critical alarms with setAndAllowWhileIdle() and setExactAndAllowWhileIdle(). Test your app in Doze.*



回答3:

The cause of this might be Doze. It works, when the device is steady and is not used, android makes background calls together with other application to save the wake up time of phone and save battery. I am not exactly sure if this is your case, but you might look into that.

This answer might help you in doing so.



回答4:

Some docs says app can handle 50 notification maximum.(not sure)

But If you try to give different notification id:

for (int i...>500)
 notificationManager.notify(i, n);

Hope this works



回答5:

@Mars Estrada is right, the problem is due to the phone implementation. Huawei have a software call powergenie which can kill (in linux term) your service. So the implementation clearly do not respect the doze Mode. Somme application just inform user that "since you have a huawei, be sure to set up the power management corectly". THe same goes for Xiaomi and Samsung. AS these implementation does not follow Android SDK there is no way to manage a background application. For My application for different Huawei ( HOnor 8x, Huawei P20, P10, P9 , P9 lite ), if the application is not protected for background execution in the setting, It is even impossible to start a service using Context.startService ( or startFroegroundService when available). regards Charles