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

วิธีล้าง Notification เมื่อแอป Crash ใน Android

ภาพรวม

ตัวอย่างนี้สาธิตวิจิธีล้างการแจ้งเตือนอัตโนมัติเมื่อแอป Crash ด้วยการใช้ Thread.UncaughtExceptionHandler เพื่อจับ Exception ที่ไม่ได้จับ และยกเลิก Notification ก่อนที่แอปจะปิด

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

Android Studio → File → New Project

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

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="https://schemas.android.com/apk/res/android"
    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="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentStart="true"
        android:layout_alignParentEnd="true"
        android:layout_centerInParent="true"
        android:text="Create Notification" />

</RelativeLayout>

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

package app.tutorialspoint.com.notifyme;

import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.NotificationCompat;

public class MainActivity extends AppCompatActivity {
    public static final String NOTIFICATION_CHANNEL_ID = "10001";
    private final static String default_notification_channel_id = "default";
    // ใช้ ID คงที่เพื่อให้ CrashHandler หาและยกเลิกได้
    private static final int NOTIFICATION_ID = 12345;

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

        // ติดตั้ง Crash Handler ทันทีที่แอปเริ่ม
        new CrashHandler(this);

        findViewById(R.id.btnCreateNotification).setOnClickListener(this::createNotification);
    }

    public void createNotification(View view) {
        Intent intent = new Intent(this, MainActivity.class);
        intent.putExtra("fromNotification", true);
        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);

        PendingIntent pendingIntent = PendingIntent.getActivity(
                this, 0, intent, PendingIntent.FLAG_IMMUTABLE);

        NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, default_notification_channel_id)
                .setContentTitle("Crash Test Notification")
                .setContentText("Will be cleared if app crashes")
                .setSmallIcon(R.drawable.ic_launcher_foreground)
                .setContentIntent(pendingIntent)
                .setAutoCancel(true)
                .setColor(getColor(android.R.color.holo_blue_dark));

        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(
                    NOTIFICATION_CHANNEL_ID, "Crash Test", NotificationManager.IMPORTANCE_HIGH);
            builder.setChannelId(NOTIFICATION_CHANNEL_ID);
            manager.createNotificationChannel(channel);
        }

        manager.notify(NOTIFICATION_ID, builder.build());
    }
}

ขั้นตอนที่ 4: CrashHandler.java

package app.tutorialspoint.com.notifyme;

import android.app.NotificationManager;
import android.content.Context;

public class CrashHandler implements Thread.UncaughtExceptionHandler {
    private static final int NOTIFICATION_ID = 12345; // ต้องตรงกับ MainActivity
    private final Thread.UncaughtExceptionHandler defaultHandler;
    private final NotificationManager notificationManager;

    public CrashHandler(Context context) {
        this.defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
        this.notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        Thread.setDefaultUncaughtExceptionHandler(this);
    }

    @Override
    public void uncaughtException(Thread thread, Throwable throwable) {
        try {
            // ล้าง Notification ก่อนที่แอปจะตาย
            if (notificationManager != null) {
                notificationManager.cancel(NOTIFICATION_ID);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // เรียก Default Handler เพื่อให้ระบบจัดการ Crash ต่อไป (แสดง Dialog, Restart, ฯลฯ)
            if (defaultHandler != null) {
                defaultHandler.uncaughtException(thread, throwable);
            }
        }
    }
}

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

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
    package="app.tutorialspoint.com.notifyme">

    <uses-permission android:name="android.permission.VIBRATE" />

    <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. CrashHandler implement Thread.UncaughtExceptionHandler
  2. ใน Constructor: เก็บ Default Handler ไว้ แล้วตั้งตัวเองเป็น Default Handler ใหม่
  3. เมื่อ Crash เกิดขึ้น uncaughtException() จะถูกเรียก
  4. ยกเลิก Notification ด้วย ID ที่รู้จักล่วงหน้า
  5. เรียก Default Handler ต่อ เพื่อให้ระบบทำงานต่อไป (แสดง Crash Dialog, Restart App)

ข้อจำกัด

  • ทำงานได้เฉพาะ Crash บน Main Thread (UI Thread)
  • Native Crash (C/C++ NDK) จะไม่ผ่าน UncaughtExceptionHandler
  • ANR (Application Not Responding) ไม่ถือว่าเป็น Exception
  • ควรใช้ร่วมกับ Crash Reporting Library (Firebase Crashlytics, Sentry) เพื่อรายงาน Crash

แนวทางที่ดีกว่า (Modern Approach)

ใช้ ProcessLifecycleOwner หรือ Application.ActivityLifecycleCallbacks ร่วมกับ WorkManager เพื่อทำความสะอาดเมื่อ Process ตาย