如何在onStop()/onDestroy()中显示弹窗、自定义Toast及实现定时通知?
Hey there! Let's walk through each of your Android app requirements with practical, actionable code and tips—no fluff, just what you need to implement this properly.
1. Trigger Notifications at a Specific Time
For scheduled notifications, you have two reliable options depending on your needs:
Option A: AlarmManager (For Precise, Time-Critical Triggers)
Great if you need exact timing, but note that on Android 12+, you'll need to handle restrictions for background alarms. Here's how to set it up:
First, create a notification channel (required for Android 8.0+):
private fun createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val name = "Scheduled Alerts" val descriptionText = "Notifications for scheduled events" val importance = NotificationManager.IMPORTANCE_DEFAULT val channel = NotificationChannel("SCHEDULED_CHANNEL_ID", name, importance).apply { description = descriptionText } val notificationManager: NotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.createNotificationChannel(channel) } }
Next, set up the AlarmManager to trigger a BroadcastReceiver at your target time:
fun scheduleNotification(targetTime: Long) { val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager val intent = Intent(this, NotificationReceiver::class.java) val pendingIntent = PendingIntent.getBroadcast( this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE // Use IMMUTABLE for API 31+ ) // Set the alarm (use RTC_WAKEUP to wake the device if needed) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { alarmManager.setExactAndAllowWhileIdle( AlarmManager.RTC_WAKEUP, targetTime, pendingIntent ) } else { alarmManager.setExact(AlarmManager.RTC_WAKEUP, targetTime, pendingIntent) } }
Then create the BroadcastReceiver to fire the notification:
class NotificationReceiver : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { context ?: return val notificationBuilder = NotificationCompat.Builder(context, "SCHEDULED_CHANNEL_ID") .setSmallIcon(R.drawable.ic_notification) .setContentTitle("Scheduled Alert") .setContentText("Your scheduled time has arrived!") .setPriority(NotificationCompat.PRIORITY_DEFAULT) .setAutoCancel(true) with(NotificationManagerCompat.from(context)) { notify(1, notificationBuilder.build()) } } }
Option B: WorkManager (For Non-Critical, Resilient Scheduling)
Perfect if you need the notification to fire even if the app is killed or the device restarts. Use OneTimeWorkRequest with a delay:
fun scheduleWorkManagerNotification(delayMillis: Long) { val workRequest = OneTimeWorkRequestBuilder<NotificationWorker>() .setInitialDelay(delayMillis, TimeUnit.MILLISECONDS) .build() WorkManager.getInstance(this).enqueue(workRequest) } class NotificationWorker(context: Context, params: WorkerParameters) : Worker(context, params) { override fun doWork(): Result { // Same notification building code as above val notificationBuilder = NotificationCompat.Builder(applicationContext, "SCHEDULED_CHANNEL_ID") .setSmallIcon(R.drawable.ic_notification) .setContentTitle("WorkManager Alert") .setContentText("Your scheduled time has arrived!") .setPriority(NotificationCompat.PRIORITY_DEFAULT) .setAutoCancel(true) with(NotificationManagerCompat.from(applicationContext)) { notify(2, notificationBuilder.build()) } return Result.success() } }
2. Show Popup/Custom Layout in onStop() or onDestroy()
First, a critical note: onDestroy() is not guaranteed to be called (Android may kill your app process without invoking it). So focus on onStop() for reliable behavior.
To show a popup or custom overlay when the app moves to the background, you'll need to use a system overlay window (since your Activity is no longer in the foreground). Here's how:
Step 1: Request Required Permissions
Add this to your AndroidManifest.xml:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
For Android 6.0+, you need to request the permission dynamically (users have to enable it in settings):
private fun checkOverlayPermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (!Settings.canDrawOverlays(this)) { val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:$packageName")) startActivityForResult(intent, OVERLAY_PERMISSION_REQUEST_CODE) } } }
Step 2: Show Custom Overlay in onStop()
Create a custom layout (e.g., layout/custom_overlay.xml) and add it to the WindowManager:
override fun onStop() { super.onStop() if (Settings.canDrawOverlays(this)) { showCustomOverlay() } } private fun showCustomOverlay() { val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager val overlayView = LayoutInflater.from(this).inflate(R.layout.custom_overlay, null) // Configure window parameters val params = WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY } else { WindowManager.LayoutParams.TYPE_SYSTEM_ALERT }, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, PixelFormat.TRANSLUCENT ) params.gravity = Gravity.CENTER // Add the view to the window windowManager.addView(overlayView, params) // Optional: Add a close button to the overlay overlayView.findViewById<Button>(R.id.btn_close).setOnClickListener { windowManager.removeView(overlayView) } }
⚠️ Important: Avoid adding overlays in onDestroy()—by the time this method is called, your app's resources may already be released, leading to crashes or leaks.
3. Show Custom Toast in onStop() or onDestroy()
Custom Toasts work here, but you must use the application context instead of the Activity context to avoid memory leaks (since the Activity is being destroyed). Here's how:
Step 1: Create Custom Toast Layout
Make a layout file (e.g., layout/custom_toast.xml) with your desired design.
Step 2: Display the Custom Toast
override fun onStop() { super.onStop() showCustomToast() } private fun showCustomToast() { val toast = Toast(applicationContext) val toastView = LayoutInflater.from(applicationContext).inflate(R.layout.custom_toast, null) toastView.findViewById<TextView>(R.id.toast_text).text = "App is moving to background!" toast.view = toastView toast.duration = Toast.LENGTH_LONG toast.show() }
⚠️ Note: Toasts shown in onDestroy() may not appear if the app process is immediately killed by the system. Stick to onStop() for consistent results.
内容的提问来源于stack exchange,提问作者Maamoun Med Oussama

