यह गाइड बताता है कि **UncaughtExceptionHandler** का उपयोग करके ऐप क्रैश होने पर नोटिफिकेशन को प्रोग्रामेटिक रूप से कैसे साफ़ किया जाए। यह सुनिश्चित करता है कि "भूत" नोटिफिकेशन न रहें जो टैप करने पर क्रैश लूप का कारण बनते हैं।
समस्या
जब कोई एक्टिविटी क्रैश होती है (अनकॉट एक्सेप्शन), तो ऐप प्रक्रिया समाप्त हो जाती है। यदि कोई नोटिफिकेशन लंबित था जो उस क्रैश हुई एक्टिविटी को खोलता है, तो नोटिफिकेशन पर टैप करने से ऐप पुनः आरंभ होता है → क्रैश एक्टिविटी लॉन्च होती है → फिर से क्रैश होती है → अनंत लूप।
समाधान: कस्टम UncaughtExceptionHandler
एक ग्लोबल एक्सेप्शन हैंडलर सेट करें जो डिफ़ॉल्ट हैंडलर को सौंपने से पहले नोटिफिकेशन कैंसिल करता है।
चरण 1: CrashHandler.java
package com.example.crashclearnotification;
import android.app.NotificationManager;
import android.content.Context;
public class CrashHandler implements Thread.UncaughtExceptionHandler {
private static final int NOTIFICATION_ID = 12345; // आपके नोटिफिकेशन आईडी से मेल खाना चाहिए
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) {
notificationManager.cancel(NOTIFICATION_ID);
// या सभी कैंसिल करें: notificationManager.cancelAll();
}
} catch (Exception e) {
// कैंसिलेशन विफल होने पर भी क्रैश रिपोर्टिंग जारी रखें
e.printStackTrace();
} finally {
// डिफ़ॉल्ट हैंडलर को सौंपें (सिस्टम क्रैश डायलॉग दिखाता है, प्ले कंसोल को रिपोर्ट करता है, आदि)
if (defaultHandler != null) {
defaultHandler.uncaughtException(thread, throwable);
} else {
// फॉलबैक: प्रक्रिया समाप्त करें
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(10);
}
}
}
}
चरण 2: Application उपवर्ग में आरंभीकरण
package com.example.crashclearnotification;
import android.app.Application;
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// ग्लोबल अनकॉट एक्सेप्शन हैंडलर स्थापित करें
Thread.setDefaultUncaughtExceptionHandler(new CrashHandler(this));
}
}
चरण 3: AndroidManifest.xml - Application वर्ग घोषित करें
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
package="com.example.crashclearnotification">
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application
android:name=".MyApplication" <!-- कस्टम 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/Theme.CrashClearNotification">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
चरण 4: MainActivity.java - नोटिफिकेशन बनाएँ
package com.example.crashclearnotification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.NotificationCompat;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
private static final String CHANNEL_ID = "crash_demo_channel";
private static final String CHANNEL_NAME = "क्रैश डेमो नोटिफिकेशन";
public static final int NOTIFICATION_ID = 12345; // CrashHandler के साथ साझा
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
createNotificationChannel();
Button btnNotify = findViewById(R.id.btnCreateNotification);
Button btnCrash = findViewById(R.id.btnForceCrash);
btnNotify.setOnClickListener(v -> createNotification());
btnCrash.setOnClickListener(v -> forceCrash());
}
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);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentTitle("क्रैश टेस्ट नोटिफिकेशन")
.setContentText("ऐप को क्रैश करें फिर मुझे टैप करें - मैं गायब हो जाऊँगा")
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(pendingIntent)
.setAutoCancel(true);
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
manager.notify(NOTIFICATION_ID, builder.build());
}
private void forceCrash() {
// जानबूझकर NullPointerException फेंकें
throw new NullPointerException("जानबूझकर क्रैश टेस्ट के लिए");
}
private void createNotificationChannel() {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH);
channel.setDescription("क्रैश पर ऑटो-क्लियर डेमो");
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(channel);
}
}
}
चरण 5: लेआउट (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="नोटिफिकेशन बनाएँ (आईडी: 12345)"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<Button
android:id="@+id/btnForceCrash"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="ऐप को क्रैश करें (NullPointerException)"
android:layout_marginTop="16dp"
android:backgroundTint="#FF0000"
app:layout_constraintTop_toBottomOf="@id/btnCreateNotification"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="निर्देश: 1) नोटिफिकेशन बनाएँ 2) होम दबाएँ 3) ऐप को क्रैश करें 4) नोटिफिकेशन शेड खोलें - नोटिफिकेशन गायब होना चाहिए"
android:textSize="14sp"
android:textColor="#666"
app:layout_constraintTop_toBottomOf="@id/btnForceCrash"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
यह कैसे काम करता है
1. ऐप शुरू होता है → MyApplication.onCreate() → CrashHandler स्थापित 2. उपयोगकर्ता नोटिफिकेशन बनाता है (आईडी 12345) 3. उपयोगकर्ता ऐप को क्रैश करता है (उदाहरण के लिए, NullPointerException) 4. सिस्टम CrashHandler.uncaughtException() को कॉल करता है 5. CrashHandler: a) NotificationManager.cancel(12345) → नोटिफिकेशन हटाता है b) defaultHandler.uncaughtException() → सामान्य क्रैश संचालन जारी रखता है 6. ऐप प्रक्रिया मर जाती है 7. उपयोगकर्ता नोटिफिकेशन शेड खोलता है → नोटिफिकेशन गायब (साफ़ किया गया) 8. उपयोगकर्ता ऐप पुनः लॉन्च करता है → साफ़ शुरुआत, कोई भूत नोटिफिकेशन नहीं
उन्नत: सभी नोटिफिकेशन कैंसिल करें
// CrashHandler में, विशिष्ट आईडी के बजाय: notificationManager.cancelAll(); // ऐप के सभी नोटिफिकेशन हटाता है
उन्नत: क्रैश रिपोर्टिंग एकीकरण
यदि Firebase Crashlytics या समान का उपयोग कर रहे हैं, तो डिफ़ॉल्ट हैंडलर को कॉल करने से पहले कस्टम लॉजिक जोड़ें:
@Override
public void uncaughtException(Thread thread, Throwable throwable) {
// 1. नोटिफिकेशन साफ़ करें
if (notificationManager != null) {
notificationManager.cancelAll();
}
// 2. कस्टम क्रैश लॉगिंग (वैकल्पिक)
// Crashlytics.logException(throwable); // यदि Crashlytics उपयोग कर रहे हैं
// 3. डिफ़ॉल्ट हैंडलर को सौंपें (Crashlytics, प्ले कंसोल, सिस्टम डायलॉग)
if (defaultHandler != null) {
defaultHandler.uncaughtException(thread, throwable);
}
}
सीमाएँ
| परिदृश्य | काम करता है? | टिप्पणी |
|---|---|---|
| Java अनकॉट एक्सेप्शन | ✅ हाँ | Thread.setDefaultUncaughtExceptionHandler द्वारा पकड़ा गया |
| नेटिव क्रैश (SIGSEGV, आदि) | ❌ नहीं | JVM स्तर से नीचे; जावा हैंडलर नहीं चलता |
| ANR (एप्लिकेशन नॉट रिस्पॉन्डिंग) | ❌ नहीं | सिस्टम द्वारा संभाला जाता है, अनकॉट एक्सेप्शन नहीं |
| सिस्टम द्वारा प्रक्रिया मारी गई (OOM) | ❌ नहीं | कोड निष्पादन का कोई अवसर नहीं |
वैकल्पिक दृष्टिकोण: स्टार्टअप पर साफ़ करें
सरल, अधिक मजबूत दृष्टिकोण: ऐप स्टार्टअप पर सभी नोटिफिकेशन साफ़ करें।
// MyApplication.onCreate() या MainActivity.onCreate() में: NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); manager.cancelAll(); // ऐप के सभी नोटिफिकेशन साफ़ करता है
यह क्रैश, बल-स्टॉप, सिस्टम किल, या सामान्य पुनः आरंभ के बाद काम करता है। अधिकांश ऐप्स के लिए अनुशंसित।
सर्वोत्तम अभ्यास
- स्थिर notificationId उपयोग करें जिसे CrashHandler और नोटिफिकेशन निर्माता दोनों जानते हैं
- Application.onCreate() में हैंडलर स्थापित करें, एक्टिविटी में नहीं
- डिफ़ॉल्ट हैंडलर को हमेशा सौंपें - क्रैश रिपोर्टिंग न तोड़ें
- स्टार्टअप पर cancelAll() पर विचार करें सरलता और मजबूती के लिए
- Firebase Crashlytics जैसे क्रैश रिपोर्टिंग टूल के साथ परीक्षण करें