Android Oreo:服务销毁时阻止通知消失及媒体播放器服务问题
Hey there! Let's tackle your two Android Oreo tech questions step by step—this stuff can get a bit nuanced, but I’ve worked through similar scenarios before, so let’s dive in.
First off, Android Oreo introduced stricter rules around foreground services and notifications, but there’s a straightforward trick to keep your notification alive even when the service shuts down:
- Use
stopForeground(false)instead ofstopForeground(true): When you’re ready to take your service out of foreground mode (before it gets destroyed), callingstopForeground(false)will detach the service from the notification but leave the notification visible in the status bar. The defaulttrueparameter removes the notification entirely, which is probably what you’re running into now. - Handle system-initiated destruction: If the service gets killed by the system (like during low memory), override the
onTaskRemoved()method in your service. Here, you can re-post the notification manually using theNotificationManagerto ensure it stays available for users to tap and restart the service. - Don’t forget notification channels: On Oreo+, you must create a valid notification channel before posting any notifications—otherwise, your notification won’t show up at all. Make sure you set this up early in your app’s lifecycle (like in
Application.onCreate()or your service’sonCreate()).
You’re right that storing playback state is key for resuming after service destruction. Let’s break this down into actionable steps for building the notification and handling state restoration:
Step 1: Store playback state when pausing or shutting down
Whenever your media player pauses or the service is about to be destroyed, save critical state to a persistent store like SharedPreferences (for simple data) or Room (if you need more complex media metadata):
// Example: Saving state in onPause() or onDestroy() private void savePlaybackState() { SharedPreferences prefs = getSharedPreferences("MediaPlayerState", MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); if (mediaPlayer != null) { editor.putInt("currentPosition", mediaPlayer.getCurrentPosition()); editor.putString("mediaUri", currentPlayingMediaUri.toString()); editor.putBoolean("isPaused", !mediaPlayer.isPlaying()); } editor.apply(); }
Step 2: Build a media control notification
Create a notification with playback controls (play/pause, skip, etc.) that links back to your service. Use NotificationCompat.Builder and the MediaStyle template for a native-looking media notification:
private Notification buildMediaNotification(boolean isPlaying) { // Create notification channel first (Oreo+) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel( "media_player_channel", "Media Controls", NotificationManager.IMPORTANCE_LOW ); NotificationManager notificationManager = getSystemService(NotificationManager.class); notificationManager.createNotificationChannel(channel); } // PendingIntent to restart service and restore playback when tapping the notification Intent restoreIntent = new Intent(this, MediaPlayerService.class); restoreIntent.setAction("ACTION_RESTORE_PLAYBACK"); PendingIntent restorePendingIntent = PendingIntent.getService( this, 0, restoreIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE ); // Play/Pause action intent String action = isPlaying ? "ACTION_PAUSE" : "ACTION_PLAY"; int iconRes = isPlaying ? R.drawable.ic_pause : R.drawable.ic_play; String actionText = isPlaying ? "暂停" : "播放"; Intent controlIntent = new Intent(this, MediaPlayerService.class); controlIntent.setAction(action); PendingIntent controlPendingIntent = PendingIntent.getService( this, 1, controlIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE ); // Build the notification NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "media_player_channel") .setContentTitle("正在播放: " + currentTrackTitle) .setContentText(currentTrackArtist) .setSmallIcon(R.drawable.ic_music_note) .setContentIntent(restorePendingIntent) .addAction(iconRes, actionText, controlPendingIntent) .setStyle(new androidx.media.app.NotificationCompat.MediaStyle() .setShowActionsInCompactView(0) // Show play/pause in compact view ) .setOngoing(true); // Prevent users from swiping away the notification (optional) return builder.build(); }
Step 3: Restore playback when restarting the service
In your service’s onStartCommand() method, check for the action from the notification (like ACTION_RESTORE_PLAYBACK), retrieve the saved state, and rebuild the media player:
@Override public int onStartCommand(Intent intent, int flags, int startId) { if (intent == null) return START_STICKY; switch (intent.getAction()) { case "ACTION_RESTORE_PLAYBACK": SharedPreferences prefs = getSharedPreferences("MediaPlayerState", MODE_PRIVATE); String savedUriStr = prefs.getString("mediaUri", null); int savedPosition = prefs.getInt("currentPosition", 0); boolean wasPaused = prefs.getBoolean("isPaused", true); if (savedUriStr != null) { Uri mediaUri = Uri.parse(savedUriStr); // Initialize media player mediaPlayer = MediaPlayer.create(this, mediaUri); mediaPlayer.seekTo(savedPosition); if (!wasPaused) { mediaPlayer.start(); } // Update notification to match current state startForeground(1, buildMediaNotification(!wasPaused)); } break; case "ACTION_PLAY": if (mediaPlayer != null && !mediaPlayer.isPlaying()) { mediaPlayer.start(); updateNotification(true); } break; case "ACTION_PAUSE": if (mediaPlayer != null && mediaPlayer.isPlaying()) { mediaPlayer.pause(); savePlaybackState(); updateNotification(false); } break; } return START_STICKY; // Let system restart service if it's killed (adjust based on your needs) } // Helper method to update the notification private void updateNotification(boolean isPlaying) { NotificationManager notificationManager = getSystemService(NotificationManager.class); notificationManager.notify(1, buildMediaNotification(isPlaying)); }
Quick notes
- Use
START_STICKY: Returning this fromonStartCommand()tells the system to restart your service if it’s unexpectedly killed, which helps with seamless playback resumption. - Release media resources: Always call
mediaPlayer.release()inonDestroy()to avoid memory leaks, and save the state right before releasing.
内容的提问来源于stack exchange,提问作者Joel Page

