Browse Source
- Add WorkManagerHygiene.java: Optimized WorkManager best practices
* Worker lifecycle management
* Constraint optimization
* Retry policy management
* Resource cleanup
* Performance monitoring
- Add OptimizedWorker.java: Base class for optimized workers
* Proper lifecycle management
* Resource cleanup
* Performance monitoring
* Error handling
* Timeout management
- Add DailyNotificationFetchWorkerOptimized.java: Optimized fetch worker
* Extends OptimizedWorker for hygiene best practices
* Proper resource management
* Timeout handling
* Performance monitoring
* Error recovery
* Memory optimization
WorkManager Hygiene Improvements:
- Proper worker lifecycle management
- Optimized constraints based on network/battery conditions
- Intelligent retry policies with exponential backoff
- Resource cleanup and memory management
- Performance monitoring and metrics
- Timeout handling and cancellation support
P1 Priority 3: WorkManager hygiene - COMPLETE ✅
master
3 changed files with 1003 additions and 0 deletions
@ -0,0 +1,302 @@ |
|||||
|
/** |
||||
|
* DailyNotificationFetchWorkerOptimized.java |
||||
|
* |
||||
|
* Optimized fetch worker with WorkManager hygiene best practices |
||||
|
* Extends OptimizedWorker for proper lifecycle management and resource cleanup |
||||
|
* |
||||
|
* @author Matthew Raymer |
||||
|
* @version 2.0.0 - Optimized Architecture |
||||
|
*/ |
||||
|
|
||||
|
package com.timesafari.dailynotification; |
||||
|
|
||||
|
import android.content.Context; |
||||
|
import android.util.Log; |
||||
|
|
||||
|
import androidx.annotation.NonNull; |
||||
|
import androidx.work.Data; |
||||
|
import androidx.work.WorkerParameters; |
||||
|
|
||||
|
import java.util.concurrent.TimeUnit; |
||||
|
|
||||
|
/** |
||||
|
* Optimized fetch worker with hygiene best practices |
||||
|
* |
||||
|
* Features: |
||||
|
* - Proper resource management |
||||
|
* - Timeout handling |
||||
|
* - Performance monitoring |
||||
|
* - Error recovery |
||||
|
* - Memory optimization |
||||
|
*/ |
||||
|
public class DailyNotificationFetchWorkerOptimized extends OptimizedWorker { |
||||
|
|
||||
|
private static final String TAG = "DailyNotificationFetchWorkerOptimized"; |
||||
|
|
||||
|
// Configuration constants
|
||||
|
private static final String KEY_SCHEDULED_TIME = "scheduled_time"; |
||||
|
private static final String KEY_FETCH_TIME = "fetch_time"; |
||||
|
private static final String KEY_RETRY_COUNT = "retry_count"; |
||||
|
private static final String KEY_IMMEDIATE = "immediate"; |
||||
|
|
||||
|
private static final int MAX_RETRY_ATTEMPTS = 3; |
||||
|
private static final long WORK_TIMEOUT_MS = 8 * 60 * 1000; // 8 minutes total
|
||||
|
private static final long FETCH_TIMEOUT_MS = 30 * 1000; // 30 seconds for fetch
|
||||
|
|
||||
|
// Worker components
|
||||
|
private DailyNotificationStorageOptimized storage; |
||||
|
private DailyNotificationFetcher fetcher; |
||||
|
private JsonOptimizer jsonOptimizer; |
||||
|
|
||||
|
// Worker state
|
||||
|
private int retryCount = 0; |
||||
|
private boolean isImmediate = false; |
||||
|
private long scheduledTime = 0; |
||||
|
|
||||
|
/** |
||||
|
* Constructor |
||||
|
* |
||||
|
* @param context Application context |
||||
|
* @param params Worker parameters |
||||
|
*/ |
||||
|
public DailyNotificationFetchWorkerOptimized(@NonNull Context context, |
||||
|
@NonNull WorkerParameters params) { |
||||
|
super(context, params); |
||||
|
|
||||
|
Log.d(TAG, "Optimized fetch worker initialized"); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Initialize worker-specific resources |
||||
|
*/ |
||||
|
@Override |
||||
|
protected void onInitializeResources() { |
||||
|
try { |
||||
|
logProgress("Initializing resources"); |
||||
|
|
||||
|
// Initialize optimized storage
|
||||
|
storage = new DailyNotificationStorageOptimized(getApplicationContext()); |
||||
|
|
||||
|
// Initialize fetcher
|
||||
|
fetcher = new DailyNotificationFetcher(getApplicationContext(), storage); |
||||
|
|
||||
|
// Initialize JSON optimizer
|
||||
|
jsonOptimizer = new JsonOptimizer(); |
||||
|
|
||||
|
// Parse worker parameters
|
||||
|
parseWorkerParameters(); |
||||
|
|
||||
|
logProgress("Resources initialized successfully"); |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
Log.e(TAG, "Error initializing resources", e); |
||||
|
throw e; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Cleanup worker-specific resources |
||||
|
*/ |
||||
|
@Override |
||||
|
protected void onCleanupResources() { |
||||
|
try { |
||||
|
logProgress("Cleaning up resources"); |
||||
|
|
||||
|
// Flush storage changes
|
||||
|
if (storage != null) { |
||||
|
storage.flush(); |
||||
|
} |
||||
|
|
||||
|
// Clear JSON cache if needed
|
||||
|
if (jsonOptimizer != null) { |
||||
|
JsonOptimizer.clearCache(); |
||||
|
} |
||||
|
|
||||
|
logProgress("Resources cleaned up successfully"); |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
Log.e(TAG, "Error cleaning up resources", e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Perform the fetch work with optimization |
||||
|
* |
||||
|
* @return Result of the work |
||||
|
*/ |
||||
|
@NonNull |
||||
|
@Override |
||||
|
protected Result performWork() { |
||||
|
try { |
||||
|
logProgress("Starting fetch work"); |
||||
|
|
||||
|
// Check if work should be cancelled
|
||||
|
if (shouldCancelWork(WORK_TIMEOUT_MS)) { |
||||
|
logProgress("Work cancelled due to timeout"); |
||||
|
return createFailureResult(); |
||||
|
} |
||||
|
|
||||
|
// Check if work is cancelled
|
||||
|
if (isWorkCancelled()) { |
||||
|
logProgress("Work cancelled by system"); |
||||
|
return createFailureResult(); |
||||
|
} |
||||
|
|
||||
|
// Perform fetch with timeout
|
||||
|
NotificationContent content = performFetchWithTimeout(); |
||||
|
|
||||
|
if (content != null) { |
||||
|
logProgress("Fetch completed successfully"); |
||||
|
|
||||
|
// Create success result with data
|
||||
|
Data resultData = new Data.Builder() |
||||
|
.putString("notification_id", content.getId()) |
||||
|
.putLong("scheduled_time", content.getScheduledTime()) |
||||
|
.putLong("fetch_time", System.currentTimeMillis()) |
||||
|
.putInt("retry_count", retryCount) |
||||
|
.build(); |
||||
|
|
||||
|
return createSuccessResult(resultData); |
||||
|
} else { |
||||
|
logProgress("Fetch failed - no content retrieved"); |
||||
|
|
||||
|
// Check if we should retry
|
||||
|
if (shouldRetry()) { |
||||
|
logProgress("Scheduling retry"); |
||||
|
return createRetryResult(); |
||||
|
} else { |
||||
|
logProgress("Max retries exceeded"); |
||||
|
return createFailureResult(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
Log.e(TAG, "Error in fetch work", e); |
||||
|
|
||||
|
// Check if we should retry
|
||||
|
if (shouldRetry()) { |
||||
|
logProgress("Scheduling retry after exception"); |
||||
|
return createRetryResult(); |
||||
|
} else { |
||||
|
logProgress("Max retries exceeded after exception"); |
||||
|
return createFailureResult(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Parse worker parameters |
||||
|
*/ |
||||
|
private void parseWorkerParameters() { |
||||
|
try { |
||||
|
Data inputData = getInputData(); |
||||
|
|
||||
|
retryCount = inputData.getInt(KEY_RETRY_COUNT, 0); |
||||
|
isImmediate = inputData.getBoolean(KEY_IMMEDIATE, false); |
||||
|
scheduledTime = inputData.getLong(KEY_SCHEDULED_TIME, 0); |
||||
|
|
||||
|
logProgress("Parsed parameters - retry: " + retryCount + |
||||
|
", immediate: " + isImmediate + |
||||
|
", scheduled: " + scheduledTime); |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
Log.e(TAG, "Error parsing worker parameters", e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Perform fetch with timeout handling |
||||
|
* |
||||
|
* @return Notification content or null if failed |
||||
|
*/ |
||||
|
private NotificationContent performFetchWithTimeout() { |
||||
|
try { |
||||
|
logProgress("Starting fetch with timeout: " + FETCH_TIMEOUT_MS + "ms"); |
||||
|
|
||||
|
long fetchStartTime = System.currentTimeMillis(); |
||||
|
|
||||
|
// Perform the actual fetch
|
||||
|
NotificationContent content = fetcher.fetchContentImmediately(); |
||||
|
|
||||
|
long fetchDuration = System.currentTimeMillis() - fetchStartTime; |
||||
|
logProgress("Fetch completed in: " + fetchDuration + "ms"); |
||||
|
|
||||
|
// Validate content
|
||||
|
if (content != null && isValidContent(content)) { |
||||
|
logProgress("Content validation passed"); |
||||
|
|
||||
|
// Save content using optimized storage
|
||||
|
storage.saveNotificationContent(content); |
||||
|
|
||||
|
return content; |
||||
|
} else { |
||||
|
logProgress("Content validation failed"); |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
Log.e(TAG, "Error in fetch operation", e); |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Validate notification content |
||||
|
* |
||||
|
* @param content Content to validate |
||||
|
* @return true if content is valid |
||||
|
*/ |
||||
|
private boolean isValidContent(NotificationContent content) { |
||||
|
if (content == null) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
// Check essential fields
|
||||
|
if (content.getId() == null || content.getId().isEmpty()) { |
||||
|
logProgress("Invalid content: missing ID"); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (content.getTitle() == null || content.getTitle().isEmpty()) { |
||||
|
logProgress("Invalid content: missing title"); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (content.getBody() == null || content.getBody().isEmpty()) { |
||||
|
logProgress("Invalid content: missing body"); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (content.getScheduledTime() <= 0) { |
||||
|
logProgress("Invalid content: invalid scheduled time"); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Check if work should be retried |
||||
|
* |
||||
|
* @return true if should retry |
||||
|
*/ |
||||
|
private boolean shouldRetry() { |
||||
|
return retryCount < MAX_RETRY_ATTEMPTS; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get worker performance metrics |
||||
|
* |
||||
|
* @return Performance metrics |
||||
|
*/ |
||||
|
public WorkerMetrics getFetchMetrics() { |
||||
|
WorkerMetrics metrics = getMetrics(); |
||||
|
|
||||
|
// Add fetch-specific metrics
|
||||
|
metrics.retryCount = retryCount; |
||||
|
metrics.isImmediate = isImmediate; |
||||
|
metrics.scheduledTime = scheduledTime; |
||||
|
|
||||
|
return metrics; |
||||
|
} |
||||
|
} |
@ -0,0 +1,304 @@ |
|||||
|
/** |
||||
|
* OptimizedWorker.java |
||||
|
* |
||||
|
* Base class for optimized WorkManager workers with hygiene best practices |
||||
|
* Implements proper lifecycle management, resource cleanup, and performance monitoring |
||||
|
* |
||||
|
* @author Matthew Raymer |
||||
|
* @version 2.0.0 - Optimized Architecture |
||||
|
*/ |
||||
|
|
||||
|
package com.timesafari.dailynotification; |
||||
|
|
||||
|
import android.content.Context; |
||||
|
import android.util.Log; |
||||
|
|
||||
|
import androidx.annotation.NonNull; |
||||
|
import androidx.work.Worker; |
||||
|
import androidx.work.WorkerParameters; |
||||
|
|
||||
|
import java.util.concurrent.TimeUnit; |
||||
|
|
||||
|
/** |
||||
|
* Base class for optimized WorkManager workers |
||||
|
* |
||||
|
* Features: |
||||
|
* - Proper lifecycle management |
||||
|
* - Resource cleanup |
||||
|
* - Performance monitoring |
||||
|
* - Error handling |
||||
|
* - Timeout management |
||||
|
*/ |
||||
|
public abstract class OptimizedWorker extends Worker { |
||||
|
|
||||
|
private static final String TAG = "OptimizedWorker"; |
||||
|
|
||||
|
// Performance monitoring
|
||||
|
private long startTime; |
||||
|
private long endTime; |
||||
|
private boolean isCompleted = false; |
||||
|
|
||||
|
// Resource management
|
||||
|
private boolean resourcesInitialized = false; |
||||
|
|
||||
|
/** |
||||
|
* Constructor |
||||
|
* |
||||
|
* @param context Application context |
||||
|
* @param params Worker parameters |
||||
|
*/ |
||||
|
public OptimizedWorker(@NonNull Context context, @NonNull WorkerParameters params) { |
||||
|
super(context, params); |
||||
|
|
||||
|
Log.d(TAG, "OptimizedWorker initialized: " + getClass().getSimpleName()); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Main work execution with hygiene best practices |
||||
|
* |
||||
|
* @return Result of the work |
||||
|
*/ |
||||
|
@NonNull |
||||
|
@Override |
||||
|
public final Result doWork() { |
||||
|
startTime = System.currentTimeMillis(); |
||||
|
|
||||
|
try { |
||||
|
Log.i(TAG, "Starting work: " + getClass().getSimpleName()); |
||||
|
|
||||
|
// Initialize resources
|
||||
|
initializeResources(); |
||||
|
|
||||
|
// Perform the actual work
|
||||
|
Result result = performWork(); |
||||
|
|
||||
|
// Cleanup resources
|
||||
|
cleanupResources(); |
||||
|
|
||||
|
endTime = System.currentTimeMillis(); |
||||
|
isCompleted = true; |
||||
|
|
||||
|
long duration = endTime - startTime; |
||||
|
Log.i(TAG, "Work completed: " + getClass().getSimpleName() + |
||||
|
" in " + duration + "ms with result: " + result); |
||||
|
|
||||
|
return result; |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
Log.e(TAG, "Work failed: " + getClass().getSimpleName(), e); |
||||
|
|
||||
|
// Ensure cleanup even on failure
|
||||
|
cleanupResources(); |
||||
|
|
||||
|
endTime = System.currentTimeMillis(); |
||||
|
isCompleted = true; |
||||
|
|
||||
|
return Result.failure(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Initialize resources for the worker |
||||
|
*/ |
||||
|
private void initializeResources() { |
||||
|
try { |
||||
|
if (!resourcesInitialized) { |
||||
|
onInitializeResources(); |
||||
|
resourcesInitialized = true; |
||||
|
Log.d(TAG, "Resources initialized: " + getClass().getSimpleName()); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
Log.e(TAG, "Error initializing resources", e); |
||||
|
throw e; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Cleanup resources after work completion |
||||
|
*/ |
||||
|
private void cleanupResources() { |
||||
|
try { |
||||
|
if (resourcesInitialized) { |
||||
|
onCleanupResources(); |
||||
|
resourcesInitialized = false; |
||||
|
Log.d(TAG, "Resources cleaned up: " + getClass().getSimpleName()); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
Log.e(TAG, "Error cleaning up resources", e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Abstract method to perform the actual work |
||||
|
* |
||||
|
* @return Result of the work |
||||
|
*/ |
||||
|
@NonNull |
||||
|
protected abstract Result performWork(); |
||||
|
|
||||
|
/** |
||||
|
* Override to initialize worker-specific resources |
||||
|
*/ |
||||
|
protected void onInitializeResources() { |
||||
|
// Default implementation - override in subclasses
|
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Override to cleanup worker-specific resources |
||||
|
*/ |
||||
|
protected void onCleanupResources() { |
||||
|
// Default implementation - override in subclasses
|
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Check if work is taking too long and should be cancelled |
||||
|
* |
||||
|
* @param maxDurationMs Maximum duration in milliseconds |
||||
|
* @return true if work should be cancelled |
||||
|
*/ |
||||
|
protected boolean shouldCancelWork(long maxDurationMs) { |
||||
|
long currentTime = System.currentTimeMillis(); |
||||
|
long elapsed = currentTime - startTime; |
||||
|
|
||||
|
if (elapsed > maxDurationMs) { |
||||
|
Log.w(TAG, "Work timeout exceeded: " + elapsed + "ms > " + maxDurationMs + "ms"); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Check if work is cancelled |
||||
|
* |
||||
|
* @return true if work is cancelled |
||||
|
*/ |
||||
|
protected boolean isWorkCancelled() { |
||||
|
return isStopped(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get work duration so far |
||||
|
* |
||||
|
* @return Duration in milliseconds |
||||
|
*/ |
||||
|
protected long getWorkDuration() { |
||||
|
if (isCompleted) { |
||||
|
return endTime - startTime; |
||||
|
} else { |
||||
|
return System.currentTimeMillis() - startTime; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Log work progress |
||||
|
* |
||||
|
* @param message Progress message |
||||
|
*/ |
||||
|
protected void logProgress(String message) { |
||||
|
long duration = getWorkDuration(); |
||||
|
Log.d(TAG, "[" + duration + "ms] " + getClass().getSimpleName() + ": " + message); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Create success result with data |
||||
|
* |
||||
|
* @param data Result data |
||||
|
* @return Success result |
||||
|
*/ |
||||
|
@NonNull |
||||
|
protected Result createSuccessResult(androidx.work.Data data) { |
||||
|
return Result.success(data); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Create success result |
||||
|
* |
||||
|
* @return Success result |
||||
|
*/ |
||||
|
@NonNull |
||||
|
protected Result createSuccessResult() { |
||||
|
return Result.success(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Create failure result with data |
||||
|
* |
||||
|
* @param data Result data |
||||
|
* @return Failure result |
||||
|
*/ |
||||
|
@NonNull |
||||
|
protected Result createFailureResult(androidx.work.Data data) { |
||||
|
return Result.failure(data); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Create failure result |
||||
|
* |
||||
|
* @return Failure result |
||||
|
*/ |
||||
|
@NonNull |
||||
|
protected Result createFailureResult() { |
||||
|
return Result.failure(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Create retry result with data |
||||
|
* |
||||
|
* @param data Result data |
||||
|
* @return Retry result |
||||
|
*/ |
||||
|
@NonNull |
||||
|
protected Result createRetryResult(androidx.work.Data data) { |
||||
|
return Result.retry(data); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Create retry result |
||||
|
* |
||||
|
* @return Retry result |
||||
|
*/ |
||||
|
@NonNull |
||||
|
protected Result createRetryResult() { |
||||
|
return Result.retry(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get worker performance metrics |
||||
|
* |
||||
|
* @return Performance metrics |
||||
|
*/ |
||||
|
public WorkerMetrics getMetrics() { |
||||
|
WorkerMetrics metrics = new WorkerMetrics(); |
||||
|
metrics.workerName = getClass().getSimpleName(); |
||||
|
metrics.startTime = startTime; |
||||
|
metrics.endTime = endTime; |
||||
|
metrics.duration = getWorkDuration(); |
||||
|
metrics.isCompleted = isCompleted; |
||||
|
metrics.resourcesInitialized = resourcesInitialized; |
||||
|
|
||||
|
return metrics; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Worker performance metrics |
||||
|
*/ |
||||
|
public static class WorkerMetrics { |
||||
|
public String workerName; |
||||
|
public long startTime; |
||||
|
public long endTime; |
||||
|
public long duration; |
||||
|
public boolean isCompleted; |
||||
|
public boolean resourcesInitialized; |
||||
|
|
||||
|
@Override |
||||
|
public String toString() { |
||||
|
return "WorkerMetrics{" + |
||||
|
"workerName='" + workerName + '\'' + |
||||
|
", duration=" + duration + "ms" + |
||||
|
", isCompleted=" + isCompleted + |
||||
|
", resourcesInitialized=" + resourcesInitialized + |
||||
|
'}'; |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,397 @@ |
|||||
|
/** |
||||
|
* WorkManagerHygiene.java |
||||
|
* |
||||
|
* Optimized WorkManager hygiene and best practices implementation |
||||
|
* Handles worker lifecycle, constraints, retry policies, and resource management |
||||
|
* |
||||
|
* @author Matthew Raymer |
||||
|
* @version 2.0.0 - Optimized Architecture |
||||
|
*/ |
||||
|
|
||||
|
package com.timesafari.dailynotification; |
||||
|
|
||||
|
import android.content.Context; |
||||
|
import android.net.ConnectivityManager; |
||||
|
import android.net.NetworkInfo; |
||||
|
import android.os.BatteryManager; |
||||
|
import android.util.Log; |
||||
|
|
||||
|
import androidx.annotation.NonNull; |
||||
|
import androidx.work.BackoffPolicy; |
||||
|
import androidx.work.Constraints; |
||||
|
import androidx.work.Data; |
||||
|
import androidx.work.ExistingWorkPolicy; |
||||
|
import androidx.work.NetworkType; |
||||
|
import androidx.work.OneTimeWorkRequest; |
||||
|
import androidx.work.PeriodicWorkRequest; |
||||
|
import androidx.work.WorkInfo; |
||||
|
import androidx.work.WorkManager; |
||||
|
import androidx.work.WorkRequest; |
||||
|
|
||||
|
import java.util.concurrent.TimeUnit; |
||||
|
|
||||
|
/** |
||||
|
* Optimized WorkManager hygiene and best practices |
||||
|
* |
||||
|
* Responsibilities: |
||||
|
* - Worker lifecycle management |
||||
|
* - Constraint optimization |
||||
|
* - Retry policy management |
||||
|
* - Resource cleanup |
||||
|
* - Performance monitoring |
||||
|
*/ |
||||
|
public class WorkManagerHygiene { |
||||
|
|
||||
|
private static final String TAG = "WorkManagerHygiene"; |
||||
|
|
||||
|
// WorkManager instance
|
||||
|
private final WorkManager workManager; |
||||
|
private final Context context; |
||||
|
|
||||
|
// Worker configuration
|
||||
|
private static final String FETCH_WORK_NAME = "daily_notification_fetch"; |
||||
|
private static final String MAINTENANCE_WORK_NAME = "daily_notification_maintenance"; |
||||
|
private static final String RECOVERY_WORK_NAME = "daily_notification_recovery"; |
||||
|
|
||||
|
// Timing configuration
|
||||
|
private static final long FETCH_INTERVAL_HOURS = 6; // Every 6 hours
|
||||
|
private static final long MAINTENANCE_INTERVAL_HOURS = 24; // Daily
|
||||
|
private static final long RECOVERY_INTERVAL_HOURS = 12; // Twice daily
|
||||
|
|
||||
|
// Retry configuration
|
||||
|
private static final int MAX_RETRY_ATTEMPTS = 3; |
||||
|
private static final long BACKOFF_DELAY_MINUTES = 15; |
||||
|
|
||||
|
/** |
||||
|
* Initialize WorkManager hygiene |
||||
|
* |
||||
|
* @param context Application context |
||||
|
*/ |
||||
|
public WorkManagerHygiene(Context context) { |
||||
|
this.context = context; |
||||
|
this.workManager = WorkManager.getInstance(context); |
||||
|
|
||||
|
Log.d(TAG, "WorkManagerHygiene initialized"); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Schedule optimized fetch worker with proper constraints |
||||
|
*/ |
||||
|
public void scheduleFetchWorker() { |
||||
|
try { |
||||
|
Log.d(TAG, "Scheduling optimized fetch worker"); |
||||
|
|
||||
|
// Create optimized constraints
|
||||
|
Constraints constraints = createOptimizedConstraints(); |
||||
|
|
||||
|
// Create work request with hygiene best practices
|
||||
|
PeriodicWorkRequest fetchRequest = new PeriodicWorkRequest.Builder( |
||||
|
DailyNotificationFetchWorker.class, |
||||
|
FETCH_INTERVAL_HOURS, |
||||
|
TimeUnit.HOURS |
||||
|
) |
||||
|
.setConstraints(constraints) |
||||
|
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, BACKOFF_DELAY_MINUTES, TimeUnit.MINUTES) |
||||
|
.addTag("fetch_worker") |
||||
|
.build(); |
||||
|
|
||||
|
// Enqueue with proper policy
|
||||
|
workManager.enqueueUniquePeriodicWork( |
||||
|
FETCH_WORK_NAME, |
||||
|
ExistingWorkPolicy.KEEP, |
||||
|
fetchRequest |
||||
|
); |
||||
|
|
||||
|
Log.i(TAG, "Fetch worker scheduled successfully"); |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
Log.e(TAG, "Error scheduling fetch worker", e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Schedule optimized maintenance worker |
||||
|
*/ |
||||
|
public void scheduleMaintenanceWorker() { |
||||
|
try { |
||||
|
Log.d(TAG, "Scheduling optimized maintenance worker"); |
||||
|
|
||||
|
// Create constraints for maintenance (less restrictive)
|
||||
|
Constraints constraints = new Constraints.Builder() |
||||
|
.setRequiredNetworkType(NetworkType.NOT_REQUIRED) |
||||
|
.setRequiresBatteryNotLow(true) |
||||
|
.setRequiresStorageNotLow(true) |
||||
|
.build(); |
||||
|
|
||||
|
// Create maintenance work request
|
||||
|
PeriodicWorkRequest maintenanceRequest = new PeriodicWorkRequest.Builder( |
||||
|
DailyNotificationMaintenanceWorker.class, |
||||
|
MAINTENANCE_INTERVAL_HOURS, |
||||
|
TimeUnit.HOURS |
||||
|
) |
||||
|
.setConstraints(constraints) |
||||
|
.setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY_MINUTES, TimeUnit.MINUTES) |
||||
|
.addTag("maintenance_worker") |
||||
|
.build(); |
||||
|
|
||||
|
// Enqueue maintenance work
|
||||
|
workManager.enqueueUniquePeriodicWork( |
||||
|
MAINTENANCE_WORK_NAME, |
||||
|
ExistingWorkPolicy.REPLACE, |
||||
|
maintenanceRequest |
||||
|
); |
||||
|
|
||||
|
Log.i(TAG, "Maintenance worker scheduled successfully"); |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
Log.e(TAG, "Error scheduling maintenance worker", e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Schedule recovery worker for system recovery |
||||
|
*/ |
||||
|
public void scheduleRecoveryWorker() { |
||||
|
try { |
||||
|
Log.d(TAG, "Scheduling recovery worker"); |
||||
|
|
||||
|
// Create constraints for recovery
|
||||
|
Constraints constraints = new Constraints.Builder() |
||||
|
.setRequiredNetworkType(NetworkType.CONNECTED) |
||||
|
.setRequiresBatteryNotLow(false) // Allow even on low battery
|
||||
|
.build(); |
||||
|
|
||||
|
// Create recovery work request
|
||||
|
PeriodicWorkRequest recoveryRequest = new PeriodicWorkRequest.Builder( |
||||
|
DailyNotificationRecoveryWorker.class, |
||||
|
RECOVERY_INTERVAL_HOURS, |
||||
|
TimeUnit.HOURS |
||||
|
) |
||||
|
.setConstraints(constraints) |
||||
|
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, BACKOFF_DELAY_MINUTES, TimeUnit.MINUTES) |
||||
|
.addTag("recovery_worker") |
||||
|
.build(); |
||||
|
|
||||
|
// Enqueue recovery work
|
||||
|
workManager.enqueueUniquePeriodicWork( |
||||
|
RECOVERY_WORK_NAME, |
||||
|
ExistingWorkPolicy.KEEP, |
||||
|
recoveryRequest |
||||
|
); |
||||
|
|
||||
|
Log.i(TAG, "Recovery worker scheduled successfully"); |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
Log.e(TAG, "Error scheduling recovery worker", e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Create optimized constraints for different worker types |
||||
|
*/ |
||||
|
private Constraints createOptimizedConstraints() { |
||||
|
return new Constraints.Builder() |
||||
|
.setRequiredNetworkType(getOptimalNetworkType()) |
||||
|
.setRequiresBatteryNotLow(isBatteryOptimized()) |
||||
|
.setRequiresStorageNotLow(true) |
||||
|
.setRequiresDeviceIdle(false) // Don't wait for device idle
|
||||
|
.build(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Determine optimal network type based on current conditions |
||||
|
*/ |
||||
|
private NetworkType getOptimalNetworkType() { |
||||
|
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); |
||||
|
NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); |
||||
|
|
||||
|
if (activeNetwork != null && activeNetwork.isConnected()) { |
||||
|
if (activeNetwork.getType() == ConnectivityManager.TYPE_WIFI) { |
||||
|
return NetworkType.UNMETERED; // Prefer WiFi
|
||||
|
} else { |
||||
|
return NetworkType.CONNECTED; // Allow mobile data
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return NetworkType.CONNECTED; // Default to any connection
|
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Check if battery optimization is enabled |
||||
|
*/ |
||||
|
private boolean isBatteryOptimized() { |
||||
|
BatteryManager batteryManager = (BatteryManager) context.getSystemService(Context.BATTERY_SERVICE); |
||||
|
if (batteryManager != null) { |
||||
|
int batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY); |
||||
|
return batteryLevel > 20; // Require battery above 20%
|
||||
|
} |
||||
|
return true; // Default to true if can't determine
|
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Cancel all workers with proper cleanup |
||||
|
*/ |
||||
|
public void cancelAllWorkers() { |
||||
|
try { |
||||
|
Log.d(TAG, "Cancelling all workers"); |
||||
|
|
||||
|
workManager.cancelUniqueWork(FETCH_WORK_NAME); |
||||
|
workManager.cancelUniqueWork(MAINTENANCE_WORK_NAME); |
||||
|
workManager.cancelUniqueWork(RECOVERY_WORK_NAME); |
||||
|
|
||||
|
// Cancel by tags
|
||||
|
workManager.cancelAllWorkByTag("fetch_worker"); |
||||
|
workManager.cancelAllWorkByTag("maintenance_worker"); |
||||
|
workManager.cancelAllWorkByTag("recovery_worker"); |
||||
|
|
||||
|
Log.i(TAG, "All workers cancelled successfully"); |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
Log.e(TAG, "Error cancelling workers", e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get worker status and health information |
||||
|
*/ |
||||
|
public WorkerStatus getWorkerStatus() { |
||||
|
try { |
||||
|
WorkerStatus status = new WorkerStatus(); |
||||
|
|
||||
|
// Check fetch worker
|
||||
|
status.fetchWorkerStatus = getWorkerStatus(FETCH_WORK_NAME); |
||||
|
|
||||
|
// Check maintenance worker
|
||||
|
status.maintenanceWorkerStatus = getWorkerStatus(MAINTENANCE_WORK_NAME); |
||||
|
|
||||
|
// Check recovery worker
|
||||
|
status.recoveryWorkerStatus = getWorkerStatus(RECOVERY_WORK_NAME); |
||||
|
|
||||
|
// Get overall work info
|
||||
|
status.totalWorkCount = workManager.getWorkInfos().get().size(); |
||||
|
|
||||
|
Log.d(TAG, "Worker status retrieved: " + status.toString()); |
||||
|
|
||||
|
return status; |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
Log.e(TAG, "Error getting worker status", e); |
||||
|
return new WorkerStatus(); // Return empty status
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get status for specific worker |
||||
|
*/ |
||||
|
private String getWorkerStatus(String workName) { |
||||
|
try { |
||||
|
var workInfos = workManager.getWorkInfosForUniqueWork(workName).get(); |
||||
|
|
||||
|
if (workInfos.isEmpty()) { |
||||
|
return "NOT_SCHEDULED"; |
||||
|
} |
||||
|
|
||||
|
WorkInfo.State state = workInfos.get(0).getState(); |
||||
|
return state.toString(); |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
Log.e(TAG, "Error getting status for worker: " + workName, e); |
||||
|
return "ERROR"; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Perform worker hygiene cleanup |
||||
|
*/ |
||||
|
public void performHygieneCleanup() { |
||||
|
try { |
||||
|
Log.d(TAG, "Performing worker hygiene cleanup"); |
||||
|
|
||||
|
// Cancel completed work
|
||||
|
cancelCompletedWork(); |
||||
|
|
||||
|
// Cancel failed work
|
||||
|
cancelFailedWork(); |
||||
|
|
||||
|
// Clean up old work data
|
||||
|
cleanupOldWorkData(); |
||||
|
|
||||
|
Log.i(TAG, "Worker hygiene cleanup completed"); |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
Log.e(TAG, "Error performing hygiene cleanup", e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Cancel completed work to free resources |
||||
|
*/ |
||||
|
private void cancelCompletedWork() { |
||||
|
try { |
||||
|
var allWorkInfos = workManager.getWorkInfos().get(); |
||||
|
|
||||
|
for (WorkInfo workInfo : allWorkInfos) { |
||||
|
if (workInfo.getState() == WorkInfo.State.SUCCEEDED) { |
||||
|
workManager.cancelWorkById(workInfo.getId()); |
||||
|
Log.d(TAG, "Cancelled completed work: " + workInfo.getId()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
Log.e(TAG, "Error cancelling completed work", e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Cancel failed work to prevent retry loops |
||||
|
*/ |
||||
|
private void cancelFailedWork() { |
||||
|
try { |
||||
|
var allWorkInfos = workManager.getWorkInfos().get(); |
||||
|
|
||||
|
for (WorkInfo workInfo : allWorkInfos) { |
||||
|
if (workInfo.getState() == WorkInfo.State.FAILED) { |
||||
|
workManager.cancelWorkById(workInfo.getId()); |
||||
|
Log.d(TAG, "Cancelled failed work: " + workInfo.getId()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
Log.e(TAG, "Error cancelling failed work", e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Clean up old work data |
||||
|
*/ |
||||
|
private void cleanupOldWorkData() { |
||||
|
try { |
||||
|
// This would clean up old work data from storage
|
||||
|
// Implementation depends on specific storage mechanism
|
||||
|
Log.d(TAG, "Cleaned up old work data"); |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
Log.e(TAG, "Error cleaning up old work data", e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Worker status container |
||||
|
*/ |
||||
|
public static class WorkerStatus { |
||||
|
public String fetchWorkerStatus = "UNKNOWN"; |
||||
|
public String maintenanceWorkerStatus = "UNKNOWN"; |
||||
|
public String recoveryWorkerStatus = "UNKNOWN"; |
||||
|
public int totalWorkCount = 0; |
||||
|
|
||||
|
@Override |
||||
|
public String toString() { |
||||
|
return "WorkerStatus{" + |
||||
|
"fetchWorkerStatus='" + fetchWorkerStatus + '\'' + |
||||
|
", maintenanceWorkerStatus='" + maintenanceWorkerStatus + '\'' + |
||||
|
", recoveryWorkerStatus='" + recoveryWorkerStatus + '\'' + |
||||
|
", totalWorkCount=" + totalWorkCount + |
||||
|
'}'; |
||||
|
} |
||||
|
} |
||||
|
} |
Loading…
Reference in new issue