Minimal android foreground service killed on high-end phone

20,114

Solution 1

A service started by startForeground belongs to the second most important group visible process:

  1. A visible process is doing work that the user is currently aware of, so killing it would have a noticeable negative impact on the user experience. A process is considered visible in the following conditions:

  2. It is running an Activity that is visible to the user on-screen but not in the foreground (its onPause() method has been called). This may occur, for example, if the foreground Activity is displayed as a dialog that allows the previous Activity to be seen behind it.

  3. It has a Service that is running as a foreground service, through Service.startForeground() (which is asking the system to treat the service as something the user is aware of, or essentially visible to them).

  4. It is hosting a service that the system is using for a particular feature that the user is aware, such as a live wallpaper, input method service, etc.

The number of these processes running in the system is less bounded than foreground processes, but still relatively controlled. These processes are considered extremely important and will not be killed unless doing so is required to keep all foreground processes running.

That being said, you can never be sure that your service is not killed at any time. E.g. memory pressure, low battery etc. See who-lives-and-who-dies.


For how to handle it, basically you answered the question yourself. The way to go is START_STICKY:

For started services, there are two additional major modes of operation they can decide to run in, depending on the value they return from onStartCommand(): START_STICKY is used for services that are explicitly started and stopped as needed, while START_NOT_STICKY or START_REDELIVER_INTENT are used for services that should only remain running while processing any commands sent to them. See the linked documentation for more detail on the semantics.

As a general guideline you should do as little as possible in the background (ore foreground) service, i.e. only do the location tracking and keep everything else in your foreground activity. Only the tracking should require very little configuration an can be loaded quickly. Also the smaller your service is the less likely it is to be killed. Your activity will be restored by the system in the state that is was before it went into background, as long as it is not killed as well. A "cold-start" of the foreground activity on the other hand should not be a problem.
I don't consider that as ugly, because this guarantees that the phone always provides the best experience to the user. This is the most important thing it has to do. That some devices close services after 30 minutes (possibly without user interaction) is unfortunate.

So, as you stated, you have to

Persist everything in the service in e.g. a Room database. Every variable, every custom class, every time any of them changes and then start the service with START_STICKY.

See creating a never ending service

Implicit question:

Depending on how long it takes for Android to re-create the service after killing it, a large portion of locations may be lost.

This usually takes only a really short time. Especially because you can use the Fused Location Provider Api for the location updates, which is an independent system service and very unlikely to be killed. So it mainly depends on the time you need to recreate the service in onStartCommand.

Also take note that from Android 8.0 onwards you need to use a forground service because of the background location limits.


Edit: As recently covered in the news: Some manufacturers may give you a hard time to keep your service running. The site https://dontkillmyapp.com/ keeps track of the manufacturers and possible mitigations for your device. Oneplus is currently (29.01.19) one of the worst offenders.

When releasing their 1+5 and 1+6 phones, OnePlus introduced one of the most severe background limits on the market to date, dwarfing even those performed by Xiaomi or Huawei. Not only did users need to enable extra settings to make their apps work properly, but those settings even get reset with firmware update so that apps break again and users are required to re-enable those settings on a regular basis.

Solution for users

Turn off System Settings > Apps > Gear Icon > Special Access > Battery Optimization.

sadly there is

No known solution on the developer end

Solution 2

I know it's late but this may help some one. I too faced the same problem of keeping the foreground service alive without being killed by OS from different manufacturers. Most of the Chinese manufacturer's OS kills the foreground service even if it is added to the exception lists (Battery, Cleaner, etc) and allowed to auto start.

I found this link which solved me this long time problem of keeping the service alive.

All you have to do is run your foreground service in a seperate process. That's it.

You can do that by adding android:process to your service in your AndroidManifest.xml.

For Example:

<service android:name=".YourService"
        android:process=":yourProcessName" />

You can refer the docs to know more about android:process

EDIT: SharedPreferences won't work across multiple processes. In this case, you must go for IPC (Inter Process Communication) methods or you can use ContentProviders for storing and accessing your data to be used across processes. Referred from docs.

Share:
20,114
user1202032
Author by

user1202032

Updated on January 22, 2021

Comments

  • user1202032
    user1202032 over 3 years

    I'm trying to create an app that lets users log routes (locations/GPS). To ensure locations are logged even when the screen is off, I have created a foreground service for the location logging. I store the locations in a Room Database which is injected into my service using Dagger2.

    However, this service is killed by Android which is, of course, not good. I could subscribe to low memory warnings but that doesn't solve the underlying problem of my service getting killed after ~30 minutes on a modern high-end phone running Android 8.0

    I have created a minimal project with only a "Hello world" activity and the service: https://github.com/RandomStuffAndCode/AndroidForegroundService

    The service is started in my Application class, and route logging is started through a Binder:

    // Application
    @Override
    public void onCreate() {
        super.onCreate();
        mComponent = DaggerAppComponent.builder()
                .appModule(new AppModule(this))
                .build();
    
        Intent startBackgroundIntent = new Intent();
        startBackgroundIntent.setClass(this, LocationService.class);
        startService(startBackgroundIntent);
    }
    
    // Binding activity
    bindService(new Intent(this, LocationService.class), mConnection, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT);
    // mConnection starts the route logging through `Binder` once connected. The binder calls startForeground()
    

    I probably don't need the BIND_AUTO_CREATE flag, I've been testing different flags in an attempt to not get my service killed - no luck so far.

    Using the profiler it does not seem like I have any memory leaks, memory usage is stable at ~35mb:

    profiler

    Using adb shell dumpsys activity processes > tmp.txt i can confirm that foregroundServices=true and my service is listed 8th in the LRU list:

    Proc # 3: prcp F/S/FGS trm: 0 31592:com.example.foregroundserviceexample/u0a93 (fg-service)

    It seems like it is not possible to create a foreground service that you can trust to not get killed. So what can we do? Well...

    1. Put the service in a separate process, in an attempt to let Android kill the UI/Activities while leaving the service alone. Would probably help, but doesn't seem like a guarantee
    2. Persist everything in the service in e.g. a Room database. Every variable, every custom class, every time any of the changes and then start the service with START_STICKY. This seems kind of wasteful and doesn't lead to very beautiful code, but it would probably work... somewhat. Depending on how long it takes for Android to re-create the service after killing it, a large portion of locations may be lost.

    Is this really the current state of doing stuff in the background on Android? Isn't there a better way?

    EDIT: Whitelisting the app for battery optimization (disabling it) does not stop my service from being killed

    EDIT: Using Context.startForegroundService() to start the service does not improve the situation

    EDIT: So this indeed only occurs on some devices, but it occurs consistently on them. I guess you have to make a choice of either not supporting a huge number of users or write really ugly code. Awesome.

  • user1202032
    user1202032 about 6 years
    Good post thank you. Regarding Fused Provider API being killed.... I dont expect that system to be killed, but my reference to the Fused Provider Listener is in my service so when my service is killed i cannot receive updates until the service is restarted, and my FusedProviderListener is re-created and re-subscribed
  • leonardkraemer
    leonardkraemer about 6 years
    That's true. You can use the last known location to try to fill in the blank (a bit). Unfortunately google does not allow programmatic access to the users location history.
  • leonardkraemer
    leonardkraemer about 6 years
    As far as I can see this restarts the service after 5 to 10 minutes. Is this faster or more reliable than the system wise restart for START_STICKY?
  • leonardkraemer
    leonardkraemer about 6 years
    Also since lolipop the alarm manager can not be abused in this way see stackoverflow.com/questions/29914724/…
  • M D P
    M D P about 6 years
    @leoderprofi yes, START_STICKY is just a joke, and no it doesn't restart after interval, I copied from my own app that is being used for over 2 years now, it saves last best location on every interval inside infiniteRunning() method, it only restarts when an error happens. and don't worry about lollipop, I've set minSdkVersion to 14 and targetSdkVersion and compileSdkVersion to 27. and it's working just fine even on android N.
  • M D P
    M D P about 6 years
    It certainly should be reliable since people are getting paid based on the location history my app reports to server. so yes it's more reliable.
  • leonardkraemer
    leonardkraemer about 6 years
    @M D P that is not how minSdkVersion works, it doesn't change how a certain version of android behaves, just on what devices the app runs. Can you elaborate what mechanism restarts the app, when it was closed due to memory pressure and what mechanisms are used to prevent closing?
  • leonardkraemer
    leonardkraemer about 6 years
    Looks like the limitation to 1 minute is only for repeating alarms. Considering this I think the AlarmManager approach can work.
  • M D P
    M D P about 6 years
    @leoderprofi exactly, AlarmManager is an important key, no app can stop your service, even when the memory is low, system will hold that until it can be run, then runs the task you set to AlarmManager, only user can force stop it, in that case if you are building this service for a company, you can make your service Administrator, then if user removes admin or force stops the service, you get notified and can report it to the company's server.
  • user1202032
    user1202032 about 6 years
    @MDP Thanks for your input but this seems kind of pointless TBH. Surely your infiniteRunningThread is killed along with the rest of your apps process...?
  • user1202032
    user1202032 about 6 years
    Update: START_STICKY is completely unreliable. Sometimes the service is restarted but often its not... its killed and never ever restarted. The "Neverending service" link you posed is not going to work consistently as onDestroy() is not called consistently (by design)
  • leonardkraemer
    leonardkraemer about 6 years
    Good to know, do you know the exact circumstance when it is not restarted? Probably it's vendor specific though. As M D P suggested you can use AlarmManager to make sure the service is triggered/restarted at a constant interval. It seems to be the critical part, not sure about the other stuff.
  • StackOverthrow
    StackOverthrow about 6 years
    @user1202032 I remember from the 4.x days that on some devices, a sticky service would restart within 3-5 seconds, while on others it would take 20-30 seconds. How long did you wait to decide it's "never" restarted?
  • M D P
    M D P about 6 years
    @user1202032 idk what exactly you want, but it doesn't matter if gets killed, because of the AlarmManager, it always will be restarted unless user force-stop it.
  • user1202032
    user1202032 about 6 years
    @MDP No it will not. Its not a recurring alarm so it will only restart if restartApp() is called. If the process is killed, so is infiniteRunningThread which means restartApp() is never called and the service is never restarted.
  • user1202032
    user1202032 about 6 years
    @leoderprofi I've replied to him, his approach will not work.
  • user1202032
    user1202032 about 6 years
    @TKK I waited more than 1 hour
  • user1202032
    user1202032 about 6 years
    I guess, but considering no solution that works exists, this came close. Lots of good info
  • leonardkraemer
    leonardkraemer over 5 years
    @Steve M Do you mean in the last bit? Yes it is, some manufacturers even stop foreground services, regardless of any battery exemption lists or settings.
  • Steve M
    Steve M over 5 years
    @leonardkraemer no I mean the first quoted block is not relevant since it's not about foreground services and not a good answer to the OP's question
  • leonardkraemer
    leonardkraemer over 5 years
    @SteveM you were right, that part came from trying to provide a quick answer. I assumed startForeground had not been called. I updated the answer in a way, that fits the question better.
  • k8C
    k8C over 5 years
    Thank you, my foreground service is no longer terminated on my Redmi Note 6 Pro
  • Joshua
    Joshua over 5 years
    @KhoaChuAnh It will work like a charm, until you don't use the same SharedPreferences across two processes.
  • k8C
    k8C over 5 years
    I use SharedPreferences in my service with the android:process attribute without problems. As I have read somewhere on the Internet, you can still use BroadcastReceiver, ResultReceiver or Messenger to communicate between activity and service running in another process
  • Joshua
    Joshua over 5 years
    Yes, Messenger and ResultReceiver are some of the IPC methods. But those doesn't seem to work in the background. BroadcastReceiver works in background but from oreo and up, it has some limitations like only few events per hour will be received in the background (when it's in the background for a long time) even if it's an explicit broadcast (That's what I read somewhere).
  • IgorGanapolsky
    IgorGanapolsky about 4 years
    What would be the ramifications of running in a separate process?
  • Sandor Mezil
    Sandor Mezil over 3 years
    Among all the threads this was gold. I'd like to add that this allow the service not to be cleared by the "sweep away background apps" action.