feat(android): add WorkManager deduplication for notification workers
Implement unique work names to prevent duplicate WorkManager tasks
from being enqueued when multiple notifications are scheduled for
the same time or when the receiver is triggered multiple times.
Changes:
- DailyNotificationReceiver: Use enqueueUniqueWork with unique names
("display_{id}", "dismiss_{id}") and ExistingWorkPolicy.KEEP/REPLACE
- DailyNotificationFetcher: Use unique work names based on scheduled
time rounded to minutes ("fetch_{minutes}") with ExistingWorkPolicy.REPLACE
This resolves the issue where ~25+ concurrent workers were being
enqueued for the same notification, leading to race conditions and
resource waste. Now only one worker processes each notification/fetch
at a time.
Verified in logcat: Worker count reduced from 25+ to 1 per notification.
This commit is contained in:
@@ -14,6 +14,7 @@ import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.work.Data;
|
||||
import androidx.work.ExistingWorkPolicy;
|
||||
import androidx.work.OneTimeWorkRequest;
|
||||
import androidx.work.WorkManager;
|
||||
|
||||
@@ -95,6 +96,12 @@ public class DailyNotificationFetcher {
|
||||
.putInt("retry_count", 0)
|
||||
.build();
|
||||
|
||||
// Create unique work name based on scheduled time to prevent duplicate fetches
|
||||
// Use scheduled time rounded to nearest minute to handle multiple notifications
|
||||
// scheduled close together
|
||||
long scheduledTimeMinutes = scheduledTime / (60 * 1000);
|
||||
String workName = "fetch_" + scheduledTimeMinutes;
|
||||
|
||||
// Create one-time work request
|
||||
OneTimeWorkRequest fetchWork = new OneTimeWorkRequest.Builder(
|
||||
DailyNotificationFetchWorker.class)
|
||||
@@ -103,11 +110,17 @@ public class DailyNotificationFetcher {
|
||||
.setInitialDelay(delayMs, TimeUnit.MILLISECONDS)
|
||||
.build();
|
||||
|
||||
// Enqueue the work
|
||||
workManager.enqueue(fetchWork);
|
||||
// Use unique work name with REPLACE policy (newer fetch replaces older)
|
||||
// This prevents duplicate fetch workers for the same scheduled time
|
||||
workManager.enqueueUniqueWork(
|
||||
workName,
|
||||
ExistingWorkPolicy.REPLACE,
|
||||
fetchWork
|
||||
);
|
||||
|
||||
Log.i(TAG, "DN|WORK_ENQUEUED work_id=" + fetchWork.getId().toString() +
|
||||
" fetch_at=" + fetchTime +
|
||||
" work_name=" + workName +
|
||||
" delay_ms=" + delayMs +
|
||||
" delay_minutes=" + (delayMs / 60000.0));
|
||||
Log.i(TAG, "Background fetch scheduled successfully");
|
||||
|
||||
@@ -21,6 +21,7 @@ import android.util.Log;
|
||||
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.work.Data;
|
||||
import androidx.work.ExistingWorkPolicy;
|
||||
import androidx.work.OneTimeWorkRequest;
|
||||
import androidx.work.WorkManager;
|
||||
|
||||
@@ -89,13 +90,22 @@ public class DailyNotificationReceiver extends BroadcastReceiver {
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue notification processing work to WorkManager
|
||||
* Enqueue notification processing work to WorkManager with deduplication
|
||||
*
|
||||
* Uses unique work name based on notification ID to prevent duplicate
|
||||
* work items from being enqueued for the same notification. WorkManager's
|
||||
* enqueueUniqueWork automatically prevents duplicates when using the same
|
||||
* work name.
|
||||
*
|
||||
* @param context Application context
|
||||
* @param notificationId ID of notification to process
|
||||
*/
|
||||
private void enqueueNotificationWork(Context context, String notificationId) {
|
||||
try {
|
||||
// Create unique work name based on notification ID to prevent duplicates
|
||||
// WorkManager will automatically skip if work with this name already exists
|
||||
String workName = "display_" + notificationId;
|
||||
|
||||
Data inputData = new Data.Builder()
|
||||
.putString("notification_id", notificationId)
|
||||
.putString("action", "display")
|
||||
@@ -106,8 +116,16 @@ public class DailyNotificationReceiver extends BroadcastReceiver {
|
||||
.addTag("daily_notification_display")
|
||||
.build();
|
||||
|
||||
WorkManager.getInstance(context).enqueue(workRequest);
|
||||
Log.d(TAG, "DN|WORK_ENQUEUE display=" + notificationId);
|
||||
// Use unique work name with KEEP policy (don't replace if exists)
|
||||
// This prevents duplicate work items from being enqueued even if
|
||||
// the receiver is triggered multiple times for the same notification
|
||||
WorkManager.getInstance(context).enqueueUniqueWork(
|
||||
workName,
|
||||
ExistingWorkPolicy.KEEP,
|
||||
workRequest
|
||||
);
|
||||
|
||||
Log.d(TAG, "DN|WORK_ENQUEUE display=" + notificationId + " work_name=" + workName);
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "DN|WORK_ENQUEUE_ERR display=" + notificationId + " err=" + e.getMessage(), e);
|
||||
@@ -115,13 +133,19 @@ public class DailyNotificationReceiver extends BroadcastReceiver {
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue notification dismissal work to WorkManager
|
||||
* Enqueue notification dismissal work to WorkManager with deduplication
|
||||
*
|
||||
* Uses unique work name based on notification ID to prevent duplicate
|
||||
* dismissal work items.
|
||||
*
|
||||
* @param context Application context
|
||||
* @param notificationId ID of notification to dismiss
|
||||
*/
|
||||
private void enqueueDismissalWork(Context context, String notificationId) {
|
||||
try {
|
||||
// Create unique work name based on notification ID to prevent duplicates
|
||||
String workName = "dismiss_" + notificationId;
|
||||
|
||||
Data inputData = new Data.Builder()
|
||||
.putString("notification_id", notificationId)
|
||||
.putString("action", "dismiss")
|
||||
@@ -132,8 +156,14 @@ public class DailyNotificationReceiver extends BroadcastReceiver {
|
||||
.addTag("daily_notification_dismiss")
|
||||
.build();
|
||||
|
||||
WorkManager.getInstance(context).enqueue(workRequest);
|
||||
Log.d(TAG, "DN|WORK_ENQUEUE dismiss=" + notificationId);
|
||||
// Use unique work name with REPLACE policy (allow new dismissal to replace pending)
|
||||
WorkManager.getInstance(context).enqueueUniqueWork(
|
||||
workName,
|
||||
ExistingWorkPolicy.REPLACE,
|
||||
workRequest
|
||||
);
|
||||
|
||||
Log.d(TAG, "DN|WORK_ENQUEUE dismiss=" + notificationId + " work_name=" + workName);
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "DN|WORK_ENQUEUE_ERR dismiss=" + notificationId + " err=" + e.getMessage(), e);
|
||||
|
||||
Reference in New Issue
Block a user