45

So, with Android O, you need to have your service running as a foreground service if you want to receive more than just a few location updates per hour.

I noticed that the old method for starting a foreground service does seem to work on O. i.e.

startForeground(NOTIFICATION_ID, getNotification());

According to the behaviour changes guide here: https://developer.android.com/preview/behavior-changes.html

The NotificationManager.startServiceInForeground() method starts a foreground service. The old way to start a foreground service no longer works.

Though the new method only works when targeting O, it seems that the old method still seems to work on an O device whether targeting O or not.

Edit Including example:

The Google sample project LocationUpdatesForegroundService actually has a working example where you can see the issue first hand. https://github.com/googlesamples/android-play-location/tree/master/LocationUpdatesForegroundService

The startForeground method seems to work without issue whether targeting and compiling against API level 25 OR targeting and compiling against O (as directed to here: https://developer.android.com/preview/migration.html#uya)

So, to reproduce:

  1. Configure the app gradle as mentioned in the previous link
  2. Open the app
  3. Request location updates
  4. Close app (either via back button or home button)

Service is running in foreground (shown by icon in notification shade). Location updates are coming through as expected (every 10 seconds) even on a device running O. What I am missing here?

CopsOnRoad
  • 237,138
  • 77
  • 654
  • 440
the_new_mr
  • 3,476
  • 5
  • 42
  • 57
  • 1
    My interpretation from [this bit of documentation](https://developer.android.com/preview/features/background.html#services) is that using `startServiceinForeground()` is less risky, in case you are in the background and on the cusp of losing the ability to start background services. – CommonsWare Apr 06 '17 at 11:25
  • From reading the section https://developer.android.com/preview/behavior-changes.html#abll, it reads that starting a service in the foreground just won't work at all in Android O (at least when targeting it). I was just surprised to see it work despite this sentence in the documentation. So, if an app keeps the target SDK < 26 and is sure to start a foreground service using the old startForeground() method, everything should be fine? – the_new_mr Apr 06 '17 at 14:51
  • It better be fine, otherwise lots of apps will break. – CommonsWare Apr 06 '17 at 20:13
  • Yes, that's what I was thinking. But was also thinking that this possibly could have been a "blow them out of the water" approach by Google to stop users' batteries from getting destroyed with apps that do background stuff and exhaust battery. – the_new_mr Apr 07 '17 at 16:37
  • Is it just me or there's no `startServiceinForeground()` method in [NotificationManager](https://developer.android.com/reference/android/app/NotificationManager.html) ? – mbonnin Sep 05 '17 at 08:04
  • 1
    Think they changed the API since ODP1. Careful though. They haven't appeared to update all the docs. https://developer.android.com/about/versions/oreo/background.html#services – the_new_mr Sep 05 '17 at 11:07
  • My 2 cents: starting a service from an explicit, custom broadcast, and not doing it in "the Android O way" makes my app crash on Android O, when targeting Android O. I needed to make the documented adjustmants to get rid of the crash. – Frank Sep 05 '17 at 18:27
  • Yes it works for me too BUT the cost is that your service will get killed by the system as soon as your app is idle. At least that's what happens in my case. My service is scanning for BTLE devices and I do NOT want the system to kill that service when my app is no longer in the foreground! – Brian Reinhold Feb 20 '18 at 11:03
  • I believe startService only works when the app is in the foreground. – Boy Nov 02 '18 at 08:51

8 Answers8

62

This worked for me.

  1. In Activity class, start service using startForegroundService() instead of startService()
    Intent myService = new Intent(this, MyService.class);

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        startForegroundService(myService);
    } else {
        startService(myService);
    }
  1. Now in Service class in onStartCommand() do as following
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    ......
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

        Notification.Builder builder = new Notification.Builder(this, ANDROID_CHANNEL_ID)
                .setContentTitle(getString(R.string.app_name))
                .setContentText(text)
                .setAutoCancel(true);

        Notification notification = builder.build();
        startForeground(1, notification);

    } else {

        NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                .setContentTitle(getString(R.string.app_name))
                .setContentText(text)
                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                .setAutoCancel(true);

        Notification notification = builder.build();

        startForeground(1, notification);
    }
    return START_NOT_STICKY;
}

Note: Using Notification.Builder instead of NotificationCompat.Builder made it work. Only in Notification.Builder you will need to provide Channel ID which is new feature in Android Oreo.

Hope it works!

EDIT:

If you target API level 28 or higher, you need FOREGROUND_SERVICE permission otherwise, your app will crash.

Just add this to the AndroidManifest.xml file.

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
bikram
  • 7,127
  • 2
  • 51
  • 63
  • 1
    you just need to change from NotificationCompat from v7 to v4 to get the method with the ChannelId parameter (import android.support.v4.app.NotificationCompat;) according to https://medium.com/google-developers/migrating-mediastyle-notifications-to-support-android-o-29c7edeca9b7 – Angel Koh Sep 29 '17 at 08:19
  • "cannot resolve this method" getting this error when i use startForegroundService() – chetan Oct 12 '17 at 09:10
  • @chetan you must use startForegroundService() in Activity class to start a service. Don't use it in service class – bikram Oct 12 '17 at 12:28
  • 3
    You need to create the channel (https://developer.android.com/reference/android/app/NotificationChannel.html) as well to make it work properly. – vikoo Jan 24 '18 at 08:53
  • 12
    `ContextCompat.startForegroundService(Context, Intent)` is what should be used here. Its implementation is similar to the one indicated in item 1 above. – VictorB Aug 06 '18 at 15:02
  • 1
    Remember to add also `` to your manifest. The above example didn't work in my case, but adding the permission helped. – Dzhuneyt Apr 14 '19 at 10:20
14

In the Activity (or any context that starts the foreground service), call this:

Intent intent = new Intent(this, MyService.class)
ContextCompat.startForegroundService(context, intent);

When the service has started, create a notification channel using similar code to what Android docs say, and then create a builder and use it:

final Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID).setSmallIcon(...)//
            .setPriority(...).setCategory(...).setContentTitle(...).setContentText(...).setTicker(...);
// and maybe other preparations to the notification...
startForeground(notificationId, builder.build());
android developer
  • 114,585
  • 152
  • 739
  • 1,270
6

Usually you start your service from a broadcast receiver using startService. They are saying that it's no more possible (or reliable) to call startService because there are now background limitations, so you need to call startServiceInForeground instead. However from docs it's not really clear when it happens because the app is whitelisted when it receives a broadcast intent, so it's not clear exactly when startService throws IllegalStateException.

greywolf82
  • 21,813
  • 18
  • 54
  • 108
  • 1
    Yes. I agree that it's definitely not clear and thanks for your answer. Thing is, when I tried starting a service and bringing it to the foreground, it still seemed to work whether targeting O or not. This is what makes me wonder if I'm missing something. I'll update my question to include an example (from Google's own code samples no less). – the_new_mr Apr 07 '17 at 17:13
  • IMHO, the API still works but they are talking about the special case when you can't start a service from a receiver and your app is in "cached state". – greywolf82 Apr 07 '17 at 17:15
  • I see what you mean. We just need clarity on the matter. What's more confusing is the fact that the Google code sample works the way it's written on an O device. – the_new_mr Apr 07 '17 at 17:22
  • @greywolf82 "because the app is whitelisted when it receives a broadcast intent," Is it true for any kind of broadcast? Because according to documentation it's only true for stuff which is visible to user. From https://developer.android.com/preview/features/background.html#services -> "An app is placed on the whitelist when it handles a task that's visible to the user, such as: Handling a high-priority Firebase Cloud Messaging (FCM) message. Receiving a broadcast, such as an SMS/MMS message. Executing a PendingIntent from a notification." – Waqas May 10 '17 at 10:07
  • I've found that the BOOT_COMPLETED and MY_PACKAGE_REPLACED broadcasts do not place the app into the whitelist. I've opened a bug requesting that the broadcasts that place the app into the whitelist are documented: https://issuetracker.google.com/issues/62821226. – Noel Jun 29 '17 at 20:02
3

The legacy way of starting a foreground service still works when the app is in the foreground but the recommended way to start a foreground service for Apps targeting API level 26/Android O is to use the newly introduced NotificationManager#startServiceInForeground method to create a foreground service in the first place.

The old way of starting the service in the background and then promoting it to the foreground will not work if the app is in background mode, because of the background execution limitations of Android O.

Migration process and steps are documented here. https://developer.android.com/preview/features/background.html#migration

balendran
  • 139
  • 6
  • 9
    NotificationManager. startServiceInForeground has been removed. context.startForegroundService instead of it. – Kislingk Sep 11 '17 at 15:16
  • Will startService work in the background if the service is already started and running in the foreground? I use this to send info to my service – Carson Holzheimer Sep 27 '17 at 03:04
3

As also @Kislingk mentioned in a comment NotificationManager.startServiceInForeground was removed. It was marked as deprecated w/ the commit 08992ac.

From the commit message:

Rather than require an a-priori Notification be supplied in order to start a service directly into the foreground state, we adopt a two-stage compound operation for undertaking ongoing service work even from a background execution state. Context#startForegroundService() is not subject to background restrictions, with the requirement that the service formally enter the foreground state via startForeground() within 5 seconds. If the service does not do so, it is stopped by the OS and the app is blamed with a service ANR.

einsA
  • 797
  • 1
  • 7
  • 19
  • startServiceInForeground is not even mentioned on that page. – Neo42 Feb 09 '18 at 15:23
  • @Neo42 of course you have to look in the class `NotificationManager` to find that method! https://android.googlesource.com/platform/frameworks/base/+/08992ac57e973d6bf32693725ebb341a481e5944%5E%21/#F6 – einsA Feb 14 '18 at 12:13
0

I add sample if some need with backstack builder

    val notifyManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
    val playIntent    = Intent(this, this::class.java).setAction(PAUSE)
    val cancelIntent  = Intent(this, this::class.java).setAction(EXIT)

    val stop          = PendingIntent.getService(this, 1, playIntent, PendingIntent.FLAG_UPDATE_CURRENT)
    val exit          = PendingIntent.getService(this, 2, cancelIntent, PendingIntent.FLAG_UPDATE_CURRENT)

    val builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        notifyManager.createNotificationChannel(NotificationChannel(NOTIFICATION_ID_CHANNEL_ID, getString(R.string.app_name), NotificationManager.IMPORTANCE_HIGH))
        NotificationCompat.Builder(this, NOTIFICATION_ID_CHANNEL_ID)
    } else
        NotificationCompat.Builder(this)

    builder.apply {
        setContentTitle(station.name)
        setContentText(metaToText(meta) )
        setSmallIcon(R.drawable.ic_play_arrow_white_24px)
        setAutoCancel(false)
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) priority = Notification.PRIORITY_MAX
        addAction(R.drawable.ic_stop_white_24px, getString(R.string.player_notification_stop), stop)
        addAction(R.drawable.ic_close_white_24px, getString(R.string.player_notification_exit), exit)
    }

    val stackBuilder = TaskStackBuilder.create(this)
    stackBuilder.addParentStack(PlayerActivity::class.java)
    stackBuilder.addNextIntent(Intent(this, PlayerActivity::class.java))
    builder.setContentIntent(stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT))

    startForeground(NOTIFICATION_ID, builder.build())
pavol.franek
  • 1,396
  • 3
  • 19
  • 42
0

startForeground(1, notification); will work for on Android O but as per Android O requirement we have to show a persistent notification to the user. At the same time it might confuse the user in some cases (system notification about App running in background and impacting battery) and so user may uninstall the app. So best would be use newly introduce WorkManager class to schedule the task as foreground.

  1. Create your worker class (say MyWorker) by extending "Worker" class where you can perform the long running task. Override below method in this class:
    • doWork() [Required]
    • onStopped() [Optional]
    • onWorkFinished [Optional] etc.
  2. Create repeating/periodic [PeriodicWorkRequest] or non-repeating [OneTimeWorkRequest] work as per requirement.
  3. Get the instance of WorkManager and en queue the work.

Code snippet:

OneTimeWorkRequest work =
     new OneTimeWorkRequest.Builder(MyWorker.class)
 .build();
WorkManager.getInstance().enqueue(work);    
Akki
  • 775
  • 8
  • 19
0

In android O , android have background restrictions so we have to manage or call the startForegroundService(service) method instead of startSetvice()

Add permission in manifest

    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

// We start the service like

 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        var service = Intent(context, AnyService::class.java)
        context?.startForegroundService(service)
    } else {
        var service = Intent(context, AnyService::class.java)
        context?.startService(service)
    }

in side AnyService class

class AnyService : Service() {

override fun onBind(intent: Intent?): IBinder? {


}

override fun onCreate() {

    if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O)
        startMyOwnForeground()
    else
        startForeground(1, Notification())

}


override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {


    return START_STICKY

}

override fun onDestroy() {
    super.onDestroy()
}


@RequiresApi(Build.VERSION_CODES.O)
private fun startMyOwnForeground() {
    val NOTIFICATION_CHANNEL_ID = "example.permanence"
    val channelName = "Background Service"
    val chan = NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_NONE)
    chan.lightColor = Color.BLUE
    chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE

    val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
    manager.createNotificationChannel(chan)

    val notificationBuilder = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
    val notification = notificationBuilder.setOngoing(true)
        .setContentTitle("App is running in background")
        .setPriority(NotificationManager.IMPORTANCE_MIN)
        .setCategory(Notification.CATEGORY_SERVICE)
        .build()
    startForeground(2, notification)
}

}

  • So for those who are looking for a Java implementation instead of Kotlin, just change the ```val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager``` to ```NotificationManager manager = getSystemService(NotificationManager.class);``` – Manu S Pillai May 02 '21 at 20:14
  • For reference - https://developer.android.com/training/notify-user/channels#CreateChannel – Manu S Pillai May 02 '21 at 20:16