Android 9 (Pie), Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord
After too much struggle with this crash finally, I fixed this exception completely and find the solution.
Make sure that have done this stuff in your service I list them as below : (some of this stuff are repetitious as mentioned in another answer I just write them again).
1- Call
startForeground()
in both onCreate and onStartCommand.(it's ok to call startForeground() many times)
@Override
public void onCreate() {
super.onCreate();
startCommand();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent == null) {
return START_NOT_STICKY;
}
final int command = intent.getIntExtra(MAIN_SERVICE_COMMAND_KEY, -1);
if (command == MAIN_SERVICE_START_COMMAND) {
startCommand();
return START_STICKY;
}
return START_NOT_STICKY;
}
private void startCommand() {
createNotificationAndStartForeground();
runningStatus.set(STARTED);
}
2- Stop your service using
context.stopService()
, there is no need to call stopForeground() or stopSelf().
try {
context.stopService(
new Intent(
context,
NavigationService.class
)
);
} catch (Exception ex) {
Crashlytics.logException(ex);
LogManager.e("Service manager can't stop service ", ex);
}
3- Start your service using
ContextCompat.startForegroundService()
it will handle different API versions.
ContextCompat.startForegroundService(
context,
NavigationService.getStartIntent(context)
);
4- If your service has actions ( need pending Intents ) handle your pending intents with a Broadcast receiver rather than your current service( it will call your service on Create() and can be dangerous, or use PendingIntent.FLAG_NO_CREATE) , it's a good practice to have a specific Broadcast receiver for handling your service notification actions, I mean create all your pending intents using PendingIntent.getBroadcast().
private PendingIntent getStopActionPendingIntent() {
final Intent stopNotificationIntent = getBroadCastIntent();
stopNotificationIntent.setAction(BROADCAST_STOP_SERVICE);
return getPendingIntent(stopNotificationIntent);
}
private PendingIntent getPendingIntent(final Intent intent) {
return PendingIntent.getBroadcast(
this,
0,
intent,
0
);
}
new NotificationCompat.Builder(this, CHANNEL_ID)
.addAction(
new NotificationCompat.Action(
R.drawable.notification,
getString(R.string.switch_off),
getStopActionPendingIntent()
)
)
5- Always before stop your service make sure that your service is created and started (I Create a global class that has my service state)
if (navigationServiceStatus == STARTED) {
serviceManager.stopNavigationService();
}
6- Set your notificationId to a long number such as 121412.
7- Using NotificationCompat.Builder will handle different API versions you just need to create notification channel for Build versions >= Build.VERSION_CODES.O.(This one is not a solution just make your code more readable)
8- Add
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
permission to your manifest. (this one is mentioned in android docs) Android Foreground Service
hope it helps :))
I was waiting my crash report to share the solution. I didn't get any crash or ANR almost 20 days. I want to share my solution. It can help those who encounter this problem.
In onCreate()
method
- First of all, my app is a media application. I didn't implement the mediasession yet. I'm creating a notification channel in the top of
onCreate()
. Official doc - I'm calling
Service.startForeground()
method afterContext.startForegroundService()
method. In myprepareAndStartForeground()
method.Note: I don't know why but ContextCompat.startForegroundService() doesn't work properly.
For this reason, I've added manually same function to my service class instead of calling ContextCompat.startForegroundService()
private fun startForegroundService(intent: Intent) {
if (Build.VERSION.SDK_INT >= 26) {
context.startForegroundService(intent)
} else {
// Pre-O behavior.
context.startService(intent)
}
}
prepareAndStartForeground()
method
private fun prepareAndStartForeground() {
try {
val intent = Intent(ctx, MusicService::class.java)
startForegroundService(intent)
val n = mNotificationBuilder.build()
// do sth
startForeground(Define.NOTIFICATION_ID, n)
} catch (e: Exception) {
Log.e(TAG, "startForegroundNotification: " + e.message)
}
}
It's my onCreate()
override fun onCreate() {
super.onCreate()
createNotificationChannel()
prepareAndStartForeground()
}
My onStartCommand()
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (intent == null) {
return START_STICKY_COMPATIBILITY
} else {
//....
//...
}
return START_STICKY
}
onRebind
, onBind
, onUnbind
methods like these
internal var binder: IBinder? = null
override fun onRebind(intent: Intent) {
stopForeground(true) // <- remove notification
}
override fun onBind(intent: Intent): IBinder? {
stopForeground(true) // <- remove notification
return binder
}
override fun onUnbind(intent: Intent): Boolean {
prepareAndStartForeground() // <- show notification again
return true
}
We need to clear something when onDestroy() calling
override fun onDestroy() {
super.onDestroy()
releaseService()
}
private fun releaseService() {
stopMedia()
stopTimer()
// sth like these
player = null
mContext = null
afChangeListener = null
mAudioBecomingNoisy = null
handler = null
mNotificationBuilder = null
mNotificationManager = null
mInstance = null
}
I hope this solution works properly for you.
The Android Service
component is a bit tricky to get working properly, especially on later Android versions where the OS adds additional restrictions. As mentioned in other answers, when starting your Service
, use ContextCompat.startForegroundService()
. Next, in Service.onStartCommand()
, call startForeground()
immediately. Store the Notification
you want to show as a member field and use that unless it is null. Example:
private var notification:Notification? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (notification == null) {
notification = createDefaultNotification()
}
startForeground(NOTIFICATION_ID, notification)
// Do any additional setup and work herre
return START_STICKY
}
Always return START_STICKY
in your Service
. Anything else is probably the wrong thing, especially if you're doing a audio player of any kind. In fact, if you're doing an audio player, you shouldn't implement your own Service but use MediaBrowserServiceCompat
(from AndroidX) instead.
I also recommend the blog posts I wrote on this: https://hellsoft.se/how-to-service-on-android-part-3-1e24113152cd