Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Android

Cách xóa thông báo Android khi Activity bị crash

Giới thiệu

Khi ứng dụng crash (uncaught exception), thông báo đang hiển thị có thể vẫn còn lại. Giải pháp: cài đặt Thread.UncaughtExceptionHandler tùy chỉnh để hủy thông báo trước khi app chết.

Code mẫu

1. CrashHandler.java

package com.example.crashnotification;

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

public class CrashHandler implements Thread.UncaughtExceptionHandler {

    private static final int NOTIFICATION_ID = 12345; // ID cố định của thông báo cần xóa
    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);
    }

    @Override
    public void uncaughtException(Thread thread, Throwable throwable) {
        try {
            if (notificationManager != null) {
                // Xóa thông báo cụ thể
                notificationManager.cancel(NOTIFICATION_ID);
                // Hoặc xóa tất cả: notificationManager.cancelAll();
            }
        } catch (Throwable ignored) {
            // Tránh crash trong crash handler
        } finally {
            // Gọi handler mặc định để hệ thống xử lý crash (hiện dialog, ghi log, restart)
            if (defaultHandler != null) {
                defaultHandler.uncaughtException(thread, throwable);
            }
        }
    }
}

2. MainActivity.java - Đăng ký handler

package com.example.crashnotification;

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 androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.NotificationCompat;
import android.view.View;

public class MainActivity extends AppCompatActivity {

    private static final String CHANNEL_ID = "crash_channel";
    private static final int NOTIFICATION_ID = 12345; // Phải khớp với CrashHandler

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

        // ==== ĐĂNG KÝ CRASH HANDLER SỚM NHẤT CÓ THỂ ====
        Thread.setDefaultUncaughtExceptionHandler(new CrashHandler(this));

        findViewById(R.id.btnCreateNotification).setOnClickListener(v -> createNotification());
        findViewById(R.id.btnCrash).setOnClickListener(v -> {
            // Giả lập crash
            throw new RuntimeException("Test crash để xóa notification");
        });
    }

    private void createNotification() {
        Intent intent = new Intent(this, MainActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
        intent.putExtra("from_notification", true);
        PendingIntent pendingIntent = PendingIntent.getActivity(
                this, 0, intent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);

        NotificationManager manager = getSystemService(NotificationManager.class);
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
                .setSmallIcon(R.drawable.ic_launcher_foreground)
                .setContentTitle("Thông báo sẽ bị xóa khi crash")
                .setContentText("Bấm nút Crash để test")
                .setContentIntent(pendingIntent)
                .setAutoCancel(true);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(
                    CHANNEL_ID, "Crash Test", NotificationManager.IMPORTANCE_DEFAULT);
            manager.createNotificationChannel(channel);
        }

        manager.notify(NOTIFICATION_ID, builder.build()); // Dùng ID cố định
    }
}

3. 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="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:layout_marginBottom="20dp"
        android:text="Tạo thông báo (ID cố định)" />

    <Button
        android:id="@+id/btnCrash"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/btnCreateNotification"
        android:text="Gây Crash (Test)" />

</RelativeLayout>

Lưu ý quan trọng

  • ID cố định: Phải dùng cùng ID (12345) khi notify() và cancel().
  • Đăng ký sớm: Gọi Thread.setDefaultUncaughtExceptionHandler() ngay trong onCreate() của Application class hoặc Activity đầu tiên.
  • Không làm việc nặng trong handler: Chỉ cancel notification, log, rồi gọi default handler. Tránh network, DB, UI.
  • Application class tốt hơn: Nên đăng ký trong MyApplication extends Application → onCreate() để bao phủ toàn app.

Cách tốt hơn cho Production: ProcessDeath vs Crash

Crash handler chỉ hoạt động khi Java crash (UncaughtException). Nếu process bị kill bởi hệ thống (OOM, user swipe recent), handler **không chạy**.

Giải pháp bền vững: Foreground Service + Ongoing Notification

// Trong Service
Notification ongoing = new NotificationCompat.Builder(this, CHANNEL_ID)
        .setContentTitle("App đang chạy")
        .setContentText("Nhấn để mở")
        .setSmallIcon(R.drawable.ic_launcher)
        .setOngoing(true) // Không thể vuốt xóa
        .build();
startForeground(1, ongoing); // ID = 1

// Khi service stop (app đóng đúng cách):
stopForeground(true); // true = xóa notification

Cách này đảm bảo thông báo biến mất khi app dừng (crash, kill, swipe) vì service bị hủy → notification ongoing bị xóa.