Я пытался запланировать уведомления на определенную дату и время, но на большинстве устройств кажется, что уведомления не отображаются. До 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");
}
}
}
Что я делаю неправильно? есть лучший и более эффективный способ сделать это?
Обновлено: Я хотел бы увидеть пример того, как запланировать уведомление на определенную дату + время, это действительно работает.
Я говорил с некоторыми пользователями об этой проблеме. Они предоставили все разрешения и не отключили уведомления.
это все еще довольно теоретический вопрос, без определения того, что такое «некоторые устройства» ... эти рабочие задания гарантированно будут выполнены, хотя не гарантируется, что выполняемое задание будет работать на любом устройстве. эта проверка для Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
также избыточна, потому что WorkManager сам обрабатывает планирование, внутренне.
Один небольшой вопрос: вы сделали свой NotificationIntentService
сервисом намерений, но инициализируете его в doWork
из NotifyWorker
.
Это означает, что Android понятия не имеет, что вы запустили новую службу, и, поскольку ваше приложение может быть пустым (означает отсутствие активности/службы), Android может убить его, не беспокоясь ни о чем.
NotificationIntentService объявлен в AndroidManifest.xml как service <service android:name = ".NotificationIntentService" android:exported = "false" /> Как вы видите, Notifyworker реализует метод doWork.
под инициализацией я имел в виду, что вы не запускаете службу. Прямой вызов конструктора для создания объекта не означает, что он будет запущен как служба.
тогда как его инициализировать?
в NotifyWorker
у вас есть исх. к контексту. поэтому вы можете использовать его для выполнения context.startService(intentToStartService)
по какой причине вы запускаете новую службу для уведомлений? Вы можете поместить код уведомления прямо туда. Worker работает в фоновом режиме. @Кодер
добавлен код запуска службы ниже, пожалуйста, проверьте @Coder
здесь есть много возможностей. пожалуйста, попробуйте гораздо более простое уведомление и попробуйте какую-нибудь более легкую задачу в работе, чтобы увидеть, работает ли эта часть. и дайте нам знать
Попробуйте приведенный ниже код, я использовал его в одном из своих приложений, которое отлично работает для меня.
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())
}
}
будет ли это работать для уведомлений, которые нужно откладывать на месяцы?
Определенно, просто измените TimeUnit.MILLISECONDS на TimeUnit.DAYS и передайте дни задержки .setInitialDelay(30, TimeUnit.DAYS)
а после перезагрузки устройства? :П
Работает после перезагрузки устройства. Он поддерживает свою задачу со своей собственной базой данных developer.android.com/jetpack/androidx/releases/archive/….
диспетчер работ, работающий во время работы приложения, запланированная работа будет отложена до следующего запуска приложения.
Если я хочу удалить или обновить запланированное уведомление, как я могу это сделать, используя этот код выше?
@OumaimaAbouElMawahib нравится этот WorkManager.getInstance(context).cancelAllWorkByTag("Ваш пользовательский тег")
«на некоторых устройствах» канал уведомлений может быть отключен.