Планирование уведомлений Android 8/9

Я пытался запланировать уведомления на определенную дату и время, но на большинстве устройств кажется, что уведомления не отображаются. До Android 9/8 я использовал AlarmManager, который был довольно прост в использовании и работал, но последние 2 версии Android изменили это ... (спасибо Google за то, что все стало проще ...)
Итак, вот мой код, который я использую для планирования уведомления. Я использую OneTimeWorkRequest

tag = new AlertsManager(this).getCarId(nrInmatriculare);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

    Data inputData = new Data.Builder().putString("type", alarmType).putString("nrInmatriculare", nrInmatriculare).build();

    OneTimeWorkRequest notificationWork = new OneTimeWorkRequest.Builder(NotifyWorker.class)
            .setInitialDelay(calculateDelay(when), TimeUnit.MILLISECONDS)
            .setInputData(inputData)
            .addTag(String.valueOf(tag))
            .build();
    WorkManager.getInstance().enqueue(notificationWork);
}
else {
    AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
    alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, when, (24 * 60 * 60 * 1000), pendingIntent);
}

Тогда класс, который я использую для отображения уведомления, таков:

public class NotifyWorker extends Worker {

    public NotifyWorker(@NonNull Context context, @NonNull WorkerParameters params) {
        super(context, params);
    }

    @NonNull
    @Override
    public Worker.Result doWork() {
        // Method to trigger an instant notification


        new NotificationIntentService().showNotification(getInputData().getString("type"),getInputData().getString("nrInmatriculare"), getApplicationContext());

        return Worker.Result.SUCCESS;
        // (Returning RETRY tells WorkManager to try this task again
        // later; FAILURE says not to try again.)
    }
}

а этот:

public class NotificationIntentService extends IntentService {
    final Uri notificationSound = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
    int alarmId = 0;


    public NotificationIntentService() {
        super("NotificationIntentService");
    }


    @Override
    protected void onHandleIntent(Intent intent) {
            showNotification(intent);
    }

    //show notification with workmanager
    public void showNotification(String type, String nrInmatriculare, Context context){
        try
        {
            if (!TextUtils.isEmpty(type) && !TextUtils.isEmpty(nrInmatriculare)) {

                AlertsManager alertsManager = new AlertsManager(context);
                Notifications notification = alertsManager.getAlertForCar(nrInmatriculare);
                String text  = "";
                Calendar endDate = null;
                String date = notification.EndDate.get(Calendar.YEAR) + "-" + (notification.EndDate.get(Calendar.MONTH)/10==0 ? "0"+(notification.EndDate.get(Calendar.MONTH)+1) : (notification.EndDate.get(Calendar.MONTH))+1) + "-" + (notification.EndDate.get(Calendar.DAY_OF_MONTH)/10==0 ? "0"+notification.EndDate.get(Calendar.DAY_OF_MONTH) : notification.EndDate.get(Calendar.DAY_OF_MONTH));
                text = context.getString(R.string.notificationText).toString().replace("#type#", type.toUpperCase()).replace("#nrInmatriculare#", nrInmatriculare).replace("#date#", date ).replace("#days#", String.valueOf(new Utils().getDateDifferenceInDays(Calendar.getInstance(), notification.EndDate)));
                alarmId = alertsManager.getCarId(nrInmatriculare);
                endDate = (Calendar)notification.EndDate.clone();

                if (Calendar.getInstance().getTimeInMillis() > endDate.getTimeInMillis()){ //current time is after the end date (somehow the alarm is fired)
                }
                else {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                        //define the importance level of the notification
                        int importance = NotificationManager.IMPORTANCE_HIGH;
                        //build the actual notification channel, giving it a unique ID and name
                        NotificationChannel channel = new NotificationChannel("AppName", "AppName", importance);
                        //we can optionally add a description for the channel
                        String description = "A channel which shows notifications about events at Masina";
                        channel.setDescription(description);
                        //we can optionally set notification LED colour
                        channel.setLightColor(Color.MAGENTA);

                        // Register the channel with the system
                        NotificationManager notificationManager = (NotificationManager)context.
                                getSystemService(Context.NOTIFICATION_SERVICE);
                        if (notificationManager != null) {
                            notificationManager.createNotificationChannel(channel);
                        }

                        //---------

                        NotificationCompat.Builder builder = new NotificationCompat.Builder(context, "AppName");
                        builder.setContentTitle(context.getString(R.string.app_name));
                        builder.setContentText(text);
                        builder.setSmallIcon(R.mipmap.ic_launcher);
                        builder.setAutoCancel(true);
                        builder.setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE | Notification.DEFAULT_LIGHTS);
                        builder.setLights(Color.CYAN, 1000, 2000);
                        builder.setPriority(NotificationCompat.PRIORITY_HIGH);
                        builder.setSound(notificationSound);
                        builder.setStyle(new NotificationCompat.BigTextStyle().bigText(text));
                        Intent notifyIntent = null;
                        if (type.equals("CarteDeIdentitate") || type.equals("PermisDeConducere"))
                            notifyIntent = new Intent(context, PersonalDataActivity.class);
                        else
                            notifyIntent = new Intent(context, DetailActivity.class);
                        notifyIntent.putExtra("car", new SharedPreference(context).getCarDetailString(nrInmatriculare));
                        // Create the TaskStackBuilder and add the intent, which inflates the back stack
                        TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
                        stackBuilder.addNextIntentWithParentStack(notifyIntent);
                        //PendingIntent pendingIntent = PendingIntent.getActivity(context, alarmId, notifyIntent, PendingIntent.FLAG_UPDATE_CURRENT);
                        PendingIntent pendingIntent = stackBuilder.getPendingIntent(alarmId, PendingIntent.FLAG_UPDATE_CURRENT);
                        //to be able to launch your activity from the notification
                        builder.setContentIntent(pendingIntent);

                        //trigger the notification
                        NotificationManagerCompat notificationAlert = NotificationManagerCompat.from(context);
                        notificationManager.notify(alarmId, builder.build());
                    }
                }
            }
            else
            {
                Log.d("here","No extra");
            }

        }
        catch(Exception ex)
        {
            Log.d("here","Error");
        }
    }

}

Что я делаю неправильно? есть лучший и более эффективный способ сделать это?

Обновлено: Я хотел бы увидеть пример того, как запланировать уведомление на определенную дату + время, это действительно работает.

«на некоторых устройствах» канал уведомлений может быть отключен.

Martin Zeitler 13.05.2019 07:28

Я говорил с некоторыми пользователями об этой проблеме. Они предоставили все разрешения и не отключили уведомления.

Coder 13.05.2019 08:00

это все еще довольно теоретический вопрос, без определения того, что такое «некоторые устройства» ... эти рабочие задания гарантированно будут выполнены, хотя не гарантируется, что выполняемое задание будет работать на любом устройстве. эта проверка для Build.VERSION.SDK_INT >= Build.VERSION_CODES.O также избыточна, потому что WorkManager сам обрабатывает планирование, внутренне.

Martin Zeitler 13.05.2019 08:03

Один небольшой вопрос: вы сделали свой NotificationIntentService сервисом намерений, но инициализируете его в doWork из NotifyWorker.

Rahul Sharma 15.05.2019 09:41

Это означает, что Android понятия не имеет, что вы запустили новую службу, и, поскольку ваше приложение может быть пустым (означает отсутствие активности/службы), Android может убить его, не беспокоясь ни о чем.

Rahul Sharma 15.05.2019 09:42

NotificationIntentService объявлен в AndroidManifest.xml как service <service android:name = ".NotificationIntentService" android:exported = "false" /> Как вы видите, Notifyworker реализует метод doWork.

Coder 15.05.2019 09:59

под инициализацией я имел в виду, что вы не запускаете службу. Прямой вызов конструктора для создания объекта не означает, что он будет запущен как служба.

Rahul Sharma 15.05.2019 15:46

тогда как его инициализировать?

Coder 16.05.2019 06:49

в NotifyWorker у вас есть исх. к контексту. поэтому вы можете использовать его для выполнения context.startService(intentToStartService)

Rahul Sharma 16.05.2019 09:08

по какой причине вы запускаете новую службу для уведомлений? Вы можете поместить код уведомления прямо туда. Worker работает в фоновом режиме. @Кодер

patel dhaval r 16.05.2019 14:10

добавлен код запуска службы ниже, пожалуйста, проверьте @Coder

patel dhaval r 16.05.2019 14:16

здесь есть много возможностей. пожалуйста, попробуйте гораздо более простое уведомление и попробуйте какую-нибудь более легкую задачу в работе, чтобы увидеть, работает ли эта часть. и дайте нам знать

Pouya Danesh 19.05.2019 14:53
7
12
3 146
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Попробуйте приведенный ниже код, я использовал его в одном из своих приложений, которое отлично работает для меня.

private void startWorkForWeekNotification() {
    OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder(
        OpenAppNotifyWorker.class)
        .setInitialDelay(OpenAppNotifyWorker.NOTIFY_TO_OPEN_APP_IN_DAYS, TimeUnit.DAYS)
        .build();

    WorkManager.getInstance().beginUniqueWork(
        OpenAppNotifyWorker.WORKER_NAME,
        ExistingWorkPolicy.REPLACE,
        oneTimeWorkRequest).enqueue();
}

Рабочий класс

public class OpenAppNotifyWorker extends Worker {

public static final String WORKER_NAME = "OpenAppNotifyWorker";

public static final int NOTIFY_TO_OPEN_APP_IN_DAYS = 7;

public OpenAppNotifyWorker(@NonNull Context context,
    @NonNull WorkerParameters workerParams) {
    super(context, workerParams);
}

@NonNull
@Override
public Result doWork() {
    NotificationUtils
        .showNotification(getApplicationContext(), NotificationUtils.UPDATE_CHANNEL_ID,
            NotificationUtils.UPDATE_NOTIFICATION_ID);
    return Result.success();
}

}

Класс NotificationUtils

public class NotificationUtils {

public static final String UPDATE_CHANNEL_ID = "updates";

public static final int UPDATE_NOTIFICATION_ID = 1;

public static final int NOTIFICATION_REQUEST_CODE = 50;

public static void showNotification(Context context, String channelId, int notificationId) {
    createNotificationChannel(context, channelId);
    NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(
        context, channelId)
        .setSmallIcon(R.drawable.ic_splash_logo)
        .setContentTitle(context.getString(R.string.title_notification))
        .setContentText(context.getString(R.string.msg_body_notification))
        .setPriority(NotificationCompat.PRIORITY_DEFAULT);
    NotificationManagerCompat notificationManager = NotificationManagerCompat
        .from(context);
    notificationManager.notify(notificationId, mBuilder.build());
}

public static void createNotificationChannel(Context context, String channelId) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        //TODO change channel name and description
        CharSequence name = context.getString(R.string.notification_channel_updates);
        String description = context.getString(R.string.desc_notification_channel);
        int importance = NotificationManager.IMPORTANCE_DEFAULT;
        NotificationChannel channel = new NotificationChannel(
            channelId, name, importance);
        channel.setDescription(description);
        NotificationManager notificationManager = context
            .getSystemService(NotificationManager.class);
        notificationManager.createNotificationChannel(channel);
    }
}

public static void createPushNotification(Context context, String message) {
    NotificationUtils
        .createNotificationChannel(context, NotificationUtils.UPDATE_CHANNEL_ID);
    Intent notificationIntent = new Intent(context, SplashActivity.class);
    notificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    notificationIntent.setAction(Intent.ACTION_MAIN);
    notificationIntent.addCategory(Intent.CATEGORY_LAUNCHER);
    notificationIntent.putExtra(FcmPushListenerService.EXTRAS_NOTIFICATION_DATA, message);
    PendingIntent contentIntent = PendingIntent
        .getActivity(context, NOTIFICATION_REQUEST_CODE, notificationIntent,
            PendingIntent.FLAG_UPDATE_CURRENT);

    NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(
        context, NotificationUtils.UPDATE_CHANNEL_ID)
        .setSmallIcon(R.drawable.ic_notification_app_logo)
        .setContentTitle(context.getString(R.string.app_name))
        .setContentText(message)
        .setAutoCancel(true)
        .setContentIntent(contentIntent)
        .setPriority(NotificationCompat.PRIORITY_DEFAULT);
    NotificationManagerCompat notificationManager = NotificationManagerCompat
        .from(context);
    notificationManager.notify(NotificationUtils.UPDATE_NOTIFICATION_ID, mBuilder.build());
}

public static void cancelAllNotification(Context context) {
    NotificationManagerCompat notificationManager = NotificationManagerCompat
        .from(context);
    notificationManager.cancelAll();
}
}

если вы хотите запустить службу, вы можете сделать это, выполнив следующие действия:

public class OpenAppNotifyWorker extends Worker {

public static final String WORKER_NAME = "OpenAppNotifyWorker";

public static final int NOTIFY_TO_OPEN_APP_IN_DAYS = 7;

public Context context;

public OpenAppNotifyWorker(@NonNull Context context,
@NonNull WorkerParameters workerParams) {
    this.context = context
    super(context, workerParams);
}

@NonNull
@Override
public Result doWork() {

    context.startService(new NotificationIntentService())
    return Result.success();
}

}
Ответ принят как подходящий

Вот одна рабочая реализация, которую я использовал в одном из своих проектов.

Добавьте это в свой build.gradle (app) (так как это в Котлине)

//android-jet pack
implementation 'android.arch.work:work-runtime-ktx:1.0.1'

или с помощью Android X:

implementation "androidx.work:work-runtime-ktx:2.5.0"

https://developer.android.com/jetpack/androidx/релизы/работа

Создайте метод scheduleNotification. Передайте необходимые данные

fun scheduleNotification(timeDelay: Long, tag: String, body: String) {

    val data = Data.Builder().putString("body", body)

    val work = OneTimeWorkRequestBuilder<NotificationSchedule>()
                .setInitialDelay(timeDelay, TimeUnit.MILLISECONDS)
                .setConstraints(Constraints.Builder().setTriggerContentMaxDelay(Constant.ONE_SECOND, TimeUnit.MILLISECONDS).build()) // API Level 24
                .setInputData(data.build())
                .addTag(tag)
                .build()

    WorkManager.getInstance().enqueue(work)
}

NotificationSchedule Класс

class NotificationSchedule (var context: Context, var params: WorkerParameters) : Worker(context, params) {

    override fun doWork(): Result {
        val data = params.inputData
        val title = "Title"
        val body = data.getString("body")

        TriggerNotification(context, title, body)

        return Result.success()
    }
}

TriggerNotification Класс. Настройте этот класс в соответствии с вашими потребностями

class TriggerNotification(context: Context, title: String, body: String) {

init {
    sendNotification(context, title, body)
}

private fun createNotificationChannel(context: Context, name: String, description: String): String {
    // Create the NotificationChannel, but only on API 26+ because
    // the NotificationChannel class is new and not in the support library
    val chanelId = UUID.randomUUID().toString()
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val importance = NotificationManager.IMPORTANCE_HIGH
        val channel = NotificationChannel(chanelId, name, importance)
        channel.enableLights(true)
        channel.enableVibration(true)
        channel.description = description
        channel.lightColor = Color.BLUE
        channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
       
        val notificationManager = context.getSystemService(NotificationManager::class.java)
        notificationManager?.createNotificationChannel(channel)
    }

    return chanelId
}

private fun sendNotification(context: Context, title: String, body: String) {

    val notificationManager = NotificationManagerCompat.from(context)
    val mBuilder = NotificationCompat.Builder(context, createNotificationChannel(context, title, body))
    val notificationId = (System.currentTimeMillis() and 0xfffffff).toInt()

    mBuilder.setDefaults(Notification.DEFAULT_ALL)
            .setTicker("Hearty365")
            .setContentTitle(title)
            .setContentText(body)
            .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
            .setPriority(NotificationCompat.PRIORITY_HIGH)
            .setSmallIcon(R.drawable.icon)
            .setContentInfo("Content Info")
            .setAutoCancel(true)

    notificationManager.notify(notificationId, mBuilder.build())
}


}

будет ли это работать для уведомлений, которые нужно откладывать на месяцы?

Coder 21.05.2019 09:20

Определенно, просто измените TimeUnit.MILLISECONDS на TimeUnit.DAYS и передайте дни задержки .setInitialDelay(30, TimeUnit.DAYS)

shb 21.05.2019 09:41

а после перезагрузки устройства? :П

Coder 21.05.2019 09:57

Работает после перезагрузки устройства. Он поддерживает свою задачу со своей собственной базой данных developer.android.com/jetpack/androidx/releases/archive/….

shb 21.05.2019 10:04

диспетчер работ, работающий во время работы приложения, запланированная работа будет отложена до следующего запуска приложения.

Yuri Misyac 14.07.2020 11:44

Если я хочу удалить или обновить запланированное уведомление, как я могу это сделать, используя этот код выше?

Oumaima Abou El Mawahib 01.02.2021 19:28

@OumaimaAbouElMawahib нравится этот WorkManager.getInstance(context).cancelAllWorkByTag("Ваш пользовательский тег")

Jakub Kostka 28.04.2021 16:45

Другие вопросы по теме