Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Android

วิธีลบ Notification อัตโนมัติหลังเวลาที่กำหนดใน Android

ภาพรวม

การลบ Notification อัตโนมัติหลังจากเวลาที่กำหนด (Auto-dismiss) เหมาะสำหรับ Notification แบบชั่วคราว เช่น Toast-style, Promo, หรือ Reminder ที่ไม่ต้องการให้ค้างอยู่ใน Shade ใช้ Handler.postDelayed() หรือ AlarmManager / WorkManager ร่วมกับ NotificationManager.cancel()

ตัวอย่าง: ใช้ Handler.postDelayed() (Simple, ใน Process เดียว)

ขั้นตอนที่ 1: สร้างโปรเจกต์ใหม่

Android Studio → New Project → Empty Activity (Package: app.tutorialspoint.com.notifyme)

ขั้นตอนที่ 2: Layout (res/layout/activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:app="https://schemas.android.com/apk/res-auto"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btnCreateNotification"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="Create Auto-dismiss Notification (5 sec)"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

ขั้นตอนที่ 3: MainActivity.java

package app.tutorialspoint.com.notifyme;

import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import androidx.core.app.NotificationCompat;
import androidx.appcompat.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {
    public static final String NOTIFICATION_CHANNEL_ID = "10001";
    private static final String DEFAULT_CHANNEL_ID = "default";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button btnCreateNotification = findViewById(R.id.btnCreateNotification);
        btnCreateNotification.setOnClickListener(v -> createAutoDismissNotification());
    }

    private void createAutoDismissNotification() {
        // Intent สำหรับ Content Tap
        Intent notificationIntent = new Intent(this, MainActivity.class);
        notificationIntent.addCategory(Intent.CATEGORY_LAUNCHER);
        notificationIntent.setAction(Intent.ACTION_MAIN);
        notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
        
        PendingIntent resultIntent = PendingIntent.getActivity(
                this, 0, notificationIntent, 
                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
        );

        // สร้าง Notification
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, DEFAULT_CHANNEL_ID)
                .setSmallIcon(R.drawable.ic_launcher_foreground)
                .setContentTitle("Auto-dismiss Notification")
                .setContentText("This will disappear in 5 seconds")
                .setContentIntent(resultIntent)
                .setStyle(new NotificationCompat.InboxStyle()
                        .addLine("Line 1: Auto-dismiss demo")
                        .addLine("Line 2: Using Handler.postDelayed")
                        .setBigContentTitle("Details")
                        .setSummaryText("Expires in 5s"))
                .setAutoCancel(true)
                .setPriority(NotificationCompat.PRIORITY_DEFAULT);

        NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

        // สร้าง Channel (Android 8.0+)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            int importance = NotificationManager.IMPORTANCE_DEFAULT;
            NotificationChannel channel = new NotificationChannel(
                    NOTIFICATION_CHANNEL_ID, 
                    "Auto-dismiss Notifications", 
                    importance
            );
            channel.setDescription("Notifications that auto-dismiss after timeout");
            manager.createNotificationChannel(channel);
            builder.setChannelId(NOTIFICATION_CHANNEL_ID);
        }

        // ID สำหรับ Notification นี้ (ต้องจำไว้เพื่อ Cancel)
        final int notificationId = (int) System.currentTimeMillis();
        
        // แสดง Notification
        manager.notify(notificationId, builder.build());

        // ตั้งเวลาลบอัตโนมัติ 5 วินาที (5000 ms)
        long delayInMilliseconds = 5000;
        new Handler(Looper.getMainLooper()).postDelayed(() -> {
            manager.cancel(notificationId);
            // Optional: Log หรือ Callback
            // Log.d("AutoDismiss", "Notification " + notificationId + " cancelled");
        }, delayInMilliseconds);
    }
}

ขั้นตอนที่ 4: AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
    package="app.tutorialspoint.com.notifyme">
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

วิธีการทำงาน

  1. สร้าง Notification พร้อม ID ที่ Unique (ใช้ timestamp)
  2. เรียก manager.notify(id, builder.build()) แสดง Notification
  3. ใช้ Handler.postDelayed(() -> manager.cancel(id), delay) ตั้งเวลาลบ
  4. หลังจาก Delay (5 วินาที) ระบบจะเรียก cancel(id) → Notification หายจาก Shade

ข้อจำกัดของ Handler.postDelayed()

ข้อจำกัดคำอธิบาย
Process Deathหากแอปถูก Kill (Swipe from Recent, System kill) Handler จะหยุดทำงาน → Notification ไม่ถูกลบ
Doze Modeใน Doze Mode Handler อาจล่าช้า
Boot/Rebootไม่รองรับการตั้งเวลาหลัง Reboot

วิธีที่ทนทานกว่า: WorkManager (แนะนำสำหรับ Production)

WorkManager 保证工作即使在应用被杀死或设备重启后也会执行

1. เพิ่ม Dependency (build.gradle)

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

2. สร้าง Worker

package app.tutorialspoint.com.notifyme;

import android.app.NotificationManager;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;

public class CancelNotificationWorker extends Worker {
    public static final String KEY_NOTIFICATION_ID = "notification_id";

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

    @NonNull
    @Override
    public Result doWork() {
        int notificationId = getInputData().getInt(KEY_NOTIFICATION_ID, -1);
        if (notificationId != -1) {
            NotificationManager manager = (NotificationManager) 
                    getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
            manager.cancel(notificationId);
            return Result.success();
        }
        return Result.failure();
    }
}

3. ใช้งานใน MainActivity

import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
import java.util.concurrent.TimeUnit;

// แทนที่ Handler.postDelayed
OneTimeWorkRequest cancelWork = new OneTimeWorkRequest.Builder(CancelNotificationWorker.class)
        .setInputData(new androidx.work.Data.Builder()
                .putInt(CancelNotificationWorker.KEY_NOTIFICATION_ID, notificationId)
                .build())
        .setInitialDelay(5, TimeUnit.SECONDS)
        .build();

WorkManager.getInstance(this).enqueue(cancelWork);

วิธีอื่น: AlarmManager (Exact Timing)

เหมาะสำหรับเวลาที่แม่นยำ (Exact) และรองรับ Boot/Doze

AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
Intent cancelIntent = new Intent(this, CancelNotificationReceiver.class);
cancelIntent.putExtra("notification_id", notificationId);
PendingIntent pendingIntent = PendingIntent.getBroadcast(
        this, notificationId, cancelIntent, 
        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
);

// ตั้งเวลา Exact (API 19+)
alarmManager.setExact(AlarmManager.RTC_WAKEUP, 
        System.currentTimeMillis() + 5000, pendingIntent);

BroadcastReceiver:

public class CancelNotificationReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        int notificationId = intent.getIntExtra("notification_id", -1);
        if (notificationId != -1) {
            NotificationManager manager = (NotificationManager) 
                    context.getSystemService(Context.NOTIFICATION_SERVICE);
            manager.cancel(notificationId);
        }
    }
}

การเลือกวิธีที่เหมาะสม

วิธีเหมาะสำหรับข้อดีข้อเสีย
Handler.postDelayedสั้น (< 1 นาที), Foreground Appง่าย, ไม่ต้อง Permission, เร็วตายกับ Process, ไม่ Exact
WorkManagerปานกลาง (นาที-ชั่วโมง), ต้องความน่าเชื่อถือSurvive Process Death, Doze, Reboot, Battery Optimizedไม่ Exact (มี Window), Overhead เล็กน้อย
AlarmManager (setExact)Exact Time, Alarm, Reminderแม่นยำ, Wake Device, Survive Rebootต้อง Permission SCHEDULE_EXACT_ALARM (API 31+), ใช้ Battery มาก
Notification Timeout (API 26+)Auto-dismiss โดยระบบไม่ต้องเขียนโค้ดจัดการ, ระบบจัดการให้ไม่สามารถควบคุมเวลาได้เอง (System Decides)

Notification Timeout (API 26+) - วิธีง่ายที่สุด

// ตั้งค่า Timeout ให้ระบบจัดการลบเอง (หน่วย: milliseconds)
builder.setTimeoutAfter(5000); // 5 วินาที
// หรือใน Channel
channel.setTimeoutAfter(5000); // ใช้ได้ API 26+

การทดสอบ

  1. รันแอป กดปุ่มสร้าง Notification
  2. นับ 5 วินาที → Notification จะหายจาก Shade เอง
  3. ลองกด Home → เปิดแอปอื่น → รอ 5 วินาที → กลับมา Shade จะหาย
  4. ทดสอง Kill App (Swipe Recent) → รอ 5 วินาที → Notification คงอยู่ (Handler ตาย) → ทดสอบ WorkManager จะหาย
แนะนำ: ใช้ builder.setTimeoutAfter(5000) สำหรับกรณีง่าย (API 26+) ใช้ WorkManager สำหรับ Production ที่ต้องการความน่าเชื่อถือ ใช้ AlarmManager สำหรับ Exact Time Alarm/Reminder