IT272 Mobile Application Development

Mobile Application DevelopmentUnit 915 min read

Notifications, Background Services & WorkManager in Android

Unit 9 of Mobile Application Development covers Android’s notification system (channels, types, and priority levels), background execution models (WorkManager, Foreground Services, and JobScheduler), and best practices for battery efficiency and user experience. It includes hands-on examples of scheduling tasks, handli

TAKEAWAYS:

  • Android notifications use channels (Oreo+) to categorize alerts (e.g., messages, alarms) and require explicit user permission for critical notifications.
  • WorkManager is the modern API for deferred and periodic background tasks, handling constraints like network availability or battery optimization.
  • Foreground Services are visible to users (e.g., music players) and require a persistent notification to avoid being killed by the system.
  • JobScheduler (deprecated in favor of WorkManager) was used for task scheduling with flexible constraints but lacked WorkManager’s reliability.
  • Battery optimization tools (e.g., Android’s "Battery Saver") can restrict background tasks; developers must design for such constraints.
  • Doze Mode and App Standby reduce background activity to save battery, requiring explicit wake locks or alarms for critical tasks.

Core Concepts: Notifications in Android

1. What Are Notifications?

Notifications are temporary messages displayed outside an app to inform users of events (e.g., new messages, updates). They appear in the status bar and can be expanded into a notification panel.

Key Components:

  • Notification Channel (API 26+): Groups notifications by type (e.g., "Messages," "Reminders"). Users can customize channel settings (sound, vibration, priority).
  • Notification Builder: Constructs notifications with icons, text, actions (e.g., "Reply," "Dismiss"), and priority levels (PRIORITY_HIGH, PRIORITY_LOW).
  • Notification Manager: System service to post and manage notifications.

Example: Sending a Basic Notification

NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
    .setSmallIcon(R.drawable.ic_notification)
    .setContentTitle("New Message")
    .setContentText("You have a new chat from John")
    .setPriority(NotificationCompat.PRIORITY_HIGH)
    .setAutoCancel(true); // Removes notification on tap

NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
notificationManager.notify(1, builder.build());

Visual: Notification Channel Setup

flowchart TD
    A["Notification Channel\n(Created via NotificationManager)"] --> B["Set Channel Name\n(e.g., 'Alerts')"]
    B --> C["Set Channel Importance\n(PRIORITY_HIGH/LOW/DEFAULT)"]
    C --> D["Set Channel Description\n(e.g., 'Critical alerts')"]
    D --> E["Enable Channel\n(Required for notifications to appear)"]
    E --> F["Notifications\nPosted to Channel"]

2. Notification Channels (API 26+)

Before Android 8.0 (Oreo), notifications could be sent without channels. Now, all notifications must belong to a channel, and channels must be defined before use.

Steps to Create a Channel:

  1. Define a channel ID (e.g., "reminders").
  2. Set importance (IMPORTANCE_HIGH, IMPORTANCE_LOW, etc.).
  3. Enable the channel via NotificationManager.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    NotificationChannel channel = new NotificationChannel(
        "reminders",
        "Reminders",
        NotificationManager.IMPORTANCE_HIGH
    );
    channel.setDescription("Important reminders from the app");
    NotificationManager manager = getSystemService(NotificationManager.class);
    manager.createNotificationChannel(channel);
}

Visual: Notification Priority Levels

3. Notification Actions and Styles

Notifications can include actions (buttons) and styles for richer content:

  • BigTextStyle: Expands text into a large view.
  • BigPictureStyle: Displays an image.
  • MessagingStyle: Shows a conversation thread.
  • InboxStyle: Lists multiple items (e.g., emails).

Example: BigTextStyle Notification

NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
    .setContentTitle("Long Message")
    .setContentText("Preview...")
    .setStyle(new NotificationCompat.BigTextStyle()
        .bigText("This is a very long message that will be expanded when the user pulls down the notification panel. It can include multiple lines of text to provide detailed information without taking up too much space in the compact view."))
    .setSmallIcon(R.drawable.ic_notification);

Visual: Notification Expansion

User pulls downCompact ViewExpanded View
Notification expansion: User pulls down status bar to reveal BigTextStyle

Background Services and Task Scheduling

1. Why Background Services?

Mobile apps often need to perform tasks without a user actively interacting with them:

  • Syncing data (e.g., WhatsApp messages).
  • Processing uploads/downloads (e.g., Pathao ride updates).
  • Running periodic maintenance (e.g., Daraz inventory checks).

2. Background Execution Models

Android provides multiple ways to run tasks in the background, each with trade-offs:

Model Use Case Battery Impact User Visibility API Stability
WorkManager Deferred/periodic tasks Low No Recommended
Foreground Service Long-running tasks (e.g., music) High Yes (notification) Required for long tasks
JobScheduler Task scheduling with constraints Medium No Deprecated
AlarmManager Precise time-based tasks High No Limited use

Visual: Background Execution Models

classDiagram
    class WorkManager {
        + schedule tasks with constraints
        + handles Doze Mode
        + replaces JobScheduler
    }
    class ForegroundService {
        + requires notification
        + runs indefinitely
        + used for long tasks
    }
    class JobScheduler {
        + deprecated
        + scheduled with network/battery constraints
    }
    class AlarmManager {
        + set exact alarms
        + battery-intensive
    }
    WorkManager --> ForegroundService : "Use for long tasks"
    ForegroundService --> WorkManager : "Alternative for constraints"

3. WorkManager: The Modern Approach

WorkManager is the recommended API for background tasks. It:

  • Handles constraints (e.g., network availability, battery not low).
  • Survives app restarts and device reboots.
  • Works around Doze Mode and App Standby.

Key Classes:

  • WorkRequest: Defines a task (one-time or periodic).
  • WorkManager: Enqueues and manages work.

Example: One-Time Work Request

WorkRequest uploadWorkRequest = new OneTimeWorkRequest.Builder(MyWorker.class)
    .setConstraints(new Constraints.Builder()
        .setRequiredNetworkType(NetworkType.CONNECTED)
        .build())
    .build();

WorkManager.getInstance(context).enqueue(uploadWorkRequest);

Worker Class (MyWorker.java):

public class MyWorker extends Worker {
    public MyWorker(@NonNull Context context, @NonNull WorkerParameters params) {
        super(context, params);
    }

    @NonNull
    @Override
    public Result doWork() {
        // Perform background task (e.g., upload data)
        uploadDataToServer();
        return Result.success();
    }
}

Visual: WorkManager Execution Flow

flowchart TD
    A["WorkRequest\n(Enqueued)"] --> B["WorkManager\n(Checks constraints)"]
    B -->|"Constraints met"| C["Worker\n(doWork() executed)"]
    B -->|"Constraints not met"| D["Retry or cancel"]
    C --> E["Result\n(success/failure)"]

Trace: WorkManager Execution

Step Action Constraints Check Result
1 enqueue(uploadWorkRequest) Network required Queued
2 Device connects to Wi-Fi Network available Worker starts
3 doWork() executed - Uploads data
4 Result.success() returned - Task completed

4. Foreground Services: For Visible Background Tasks

Some tasks (e.g., music playback, file downloads) must run in the foreground to avoid being killed by the system. These require:

  • A persistent notification in the status bar.
  • The START_FOREGROUND_SERVICE flag in the intent.

Example: Starting a Foreground Service

Intent serviceIntent = new Intent(context, MyForegroundService.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    context.startForegroundService(serviceIntent);
} else {
    context.startService(serviceIntent);
}

Service Class (MyForegroundService.java):

public class MyForegroundService extends Service {
    private NotificationChannel channel;
    private Notification notification;

    @Override
    public void onCreate() {
        createNotificationChannel();
        createNotification();
        startForeground(1, notification);
    }

    private void createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            channel = new NotificationChannel(
                "service_channel",
                "Foreground Service",
                NotificationManager.IMPORTANCE_LOW
            );
            NotificationManager manager = getSystemService(NotificationManager.class);
            manager.createNotificationChannel(channel);
        }
    }

    private void createNotification() {
        notification = new NotificationCompat.Builder(this, "service_channel")
            .setContentTitle("Download in progress")
            .setContentText("Downloading file...")
            .setSmallIcon(R.drawable.ic_download)
            .build();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // Perform long-running task (e.g., download)
        downloadFile();
        return START_STICKY;
    }
}

Visual: Foreground Service Lifecycle

sequenceDiagram
    participant App as App
    participant Service as ForegroundService
    participant System as Android System

    App->>Service: startForegroundService()
    Service->>System: createNotificationChannel()
    System-->>Service: Channel created
    Service->>System: startForeground(1, notification)
    System-->>Service: Service now foreground
    Service->>Service: Perform long task
    Service-->>App: Task completed (optional)

Real-World Applications

1. eSewa: Transaction Notifications

  • Idea Used: Notification Channels and WorkManager.
  • How:
    • eSewa sends transaction confirmation notifications via a high-priority channel ("payments").
    • Background tasks (e.g., retrying failed payments) use WorkManager with constraints for network availability.
    • Visual:
      flowchart TD
          A["User initiates\npayment"] --> B["eSewa app\nposts notification"]
          B --> C["Notification Channel:\n'Payments' (PRIORITY_HIGH)"]
          C --> D["User sees\nconfirmation"]
          D --> E["WorkManager\nretries failed transaction"]

2. Pathao: Ride Updates via Foreground Service

  • Idea Used: Foreground Service for real-time tracking.
  • How:
    • Pathao’s driver app uses a ForegroundService to continuously update the rider’s location.
    • A persistent notification shows "Ride in progress" to avoid being killed.
    • Visual:
      flowchart LR
          A["Rider requests\nride"] --> B["Pathao starts\nForegroundService"]
          B --> C["Service updates\nlocation every 10s"]
          C --> D["Notification:\n'Ride ETA: 5 min'"]
          D --> E["Service stops\non ride completion"]

3. Ncell: Battery-Optimized Background Sync

  • Idea Used: WorkManager with constraints.
  • How:
    • Ncell’s app syncs call logs and messages in the background.
    • Uses WorkManager with constraints:
      • setRequiredNetworkType(NetworkType.CONNECTED)
      • setRequiresBatteryNotLow(true)
    • Visual:
      flowchart TD
          A["User opens app"] --> B["WorkManager\nchecks constraints"]
          B -->|"Wi-Fi available"| C["Sync call logs"]
          B -->|"Battery low"| D["Delay sync"]

Common Pitfalls and Best Practices

1. Avoiding Battery Drain

  • Use WorkManager instead of raw AlarmManager or Service.
  • Minimize wake locks: Only use WakeLock for critical sections.
  • Leverage Doze Mode: Design tasks to work within Android’s battery-saving modes.

2. Handling User Restrictions

  • Request ignore battery optimizations (for critical apps like banking):
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        Intent intent = new Intent();
        String packageName = context.getPackageName();
        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        if (!pm.isIgnoringBatteryOptimizations(packageName)) {
            intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
            intent.setData(Uri.parse("package:" + packageName));
            context.startActivity(intent);
        }
    }
    

3. Notification Best Practices

  • Group related notifications (e.g., chat messages from the same contact).
  • Use setAutoCancel(true) for notifications that should disappear on tap.
  • Avoid spamming: Limit high-priority notifications to critical events.

Exam Tip

What to Expect in TU/PU Exams:

  1. Short Questions (2-5 marks):

    • Define notification channels, WorkManager, or Foreground Service.
    • Difference between WorkManager and JobScheduler.
    • When to use setAutoCancel(true) in notifications.
  2. Programming Questions (10-15 marks):

    • Write code to:
      • Create a notification channel.
      • Schedule a WorkRequest with constraints.
      • Start a ForegroundService with a notification.
    • Trace execution: Given a WorkManager setup, predict when a task runs (e.g., "If the device is in Doze Mode, will the task execute?").
  3. Scenario-Based (5-10 marks):

    • Design a background task for:
      • A banking app (sync transactions hourly).
      • A food delivery app (update rider location in real-time).
    • Explain how you’d handle battery optimization or Doze Mode.
  4. Diagrams (3-5 marks):

    • Draw the lifecycle of a ForegroundService.
    • Show the flow of a WorkRequest from enqueue to execution.

Key Formulas/Concepts to Memorize:

  • Notification Priority Levels: PRIORITY_HIGH, PRIORITY_DEFAULT, PRIORITY_LOW.
  • WorkManager Constraints: Constraints.Builder() methods (setRequiredNetworkType(), setRequiresBatteryNotLow()).
  • Foreground Service Requirement: Must call startForeground() within 5 seconds of start.

Sample Exam Question and Answer:

Question: "Explain how you would implement a background task in an Android app that syncs user data every 6 hours, even if the device is in Doze Mode. Include code snippets and constraints."

Answer:

  1. Use WorkManager for reliability and Doze Mode compatibility.
  2. Set constraints to ensure the task runs only when:
    • The device is charged or on AC power (to save battery).
    • Wi-Fi is available (to avoid mobile data costs).
  3. Schedule a periodic task with a fixed delay of 6 hours.
// Schedule a periodic sync task
PeriodicWorkRequest syncWork = new PeriodicWorkRequest.Builder(
    SyncWorker.class,
    6, TimeUnit.HOURS
).setConstraints(new Constraints.Builder()
    .setRequiredNetworkType(NetworkType.CONNECTED)
    .setRequiresCharging(true) // Optional: run only when charged
    .build())
.build();

WorkManager.getInstance(context).enqueue(syncWork);

Visual: WorkManager Constraints Flow

flowchart TD
    A["PeriodicWorkRequest\n(6-hour interval)"] --> B["Constraints:\nWi-Fi + Charging"]
    B -->|"Conditions met"| C["Worker executes"]
    B -->|"Conditions not met"| D["Task delayed"]

Trace Table:

Time Device State Constraints Met? Action
10:00 AM Wi-Fi, Charging Yes SyncWorker runs
4:00 PM Mobile Data, 20% No Task delayed
10:00 PM Wi-Fi, Charging Yes SyncWorker runs

Final Tip: Always test your background tasks on real devices (emulators may not fully simulate Doze Mode). Use Android’s Battery Historian to analyze battery impact.

Based on the TU BIM syllabus for Mobile Application Development (IT272), unit 9.

Discussion

Loading…