Android: Migrate Foreground Service to WorkManager - android

My application uses location in the background. From Android 12 onwards, we are not supposed to use Foreground Service and if we use it, the application crashes with the below exception:
Fatal Exception: java.lang.RuntimeException: Unable to start service com.xxx.sdk.location.tracking.xxx.xxx.xxxForegroundService#cb55d82 with Intent { cmp=xxx.xxx.xxx/xxx.xxx.sdk.location.tracking.xxx.service.xxxForegroundService }: android.app.ForegroundServiceStartNotAllowedException: Service.startForeground() not allowed due to mAllowStartForeground false: service com.xxx.xxx/com.xxx.sdk.location.tracking.xxx.service.xxxForegroundService
at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:5110)
at android.app.ActivityThread.access$2200(ActivityThread.java:310)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2319)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loopOnce(Looper.java:226)
at android.os.Looper.loop(Looper.java:313)
at android.app.ActivityThread.main(ActivityThread.java:8663)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:567)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1135)
I have gone through the replacement of WorkManager and creating a notification using it.
My goal is to display long running foreground notification to a user as we are fetching location in background. I used WorkManager but the notification appears and disappears as I am not performing any task in doWork() method.
How can I show continuous foreground notification using WorkManager?

Related

How to simulate foreground service killed by the Android system

I want to reproduce a scenario where android system kills the service.
In service class I'm posting foreground service notification from onCreate and returning START_REDELIVER_INTENT from onStartCommand method. Below exception I'm getting for some of devices.
Fatal Exception: android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground()
As per code, it shouldn't crash as I am posting foreground service notification from onCreate and cancelling that notification on service onDestroy. The only thing I'm guessing that might possible at some moment android system kills the service and due to that notification get cancelled automatically and service get restarted, and this time onCreate may not get called. But on the other side i'm also not sure whether this time(system kill service) onCreate will get called or not, if that get call then it is impossible to get the above exception.
Try to use a "Terminate" button, it placed in the bottom pane in android studio on the "Run" tab, it looks like a red circle with cross.

Error when starting a service in BroadcastReceiver

I am having a situation where I get this error when I am trying to start a service from a receiver when app is closed or runs in the background.
But the docs clearly state:
The state of your BroadcastReceiver (whether it is running or not)
affects the state of its containing process, which can in turn affect
its likelihood of being killed by the system. For example, when a
process executes a receiver (that is, currently running the code in
its onReceive() method), it is considered to be a foreground process.
The system keeps the process running except under cases of extreme
memory pressure.
In other words when the app is in the foreground therefore it can theoretically start a service.
So whats the problem here?
#Override
public void onReceive(Context context, Intent intent) {
// assumes WordService is a registered service
context.startService(new Intent(context, HelloService.class));
}
Error:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.testapp, PID: 26026
java.lang.RuntimeException: Unable to start receiver com.example.testapp.MyReceiver: java.lang.IllegalStateException: Not allowed to start service Intent { cmp=com.example.testapp/.HelloService }: app is in background uid UidRecord{bee03a7 u0a82 RCVR bg:+1m19s133ms idle change:uncached procs:1 seq(0,0,0)}
at android.app.ActivityThread.handleReceiver(ActivityThread.java:3194)
at android.app.ActivityThread.-wrap17(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1672)
at android.os.Handler.dispatchMessage(Handler.java:106)
The more relevant documentation for your situation is in the Android 8.0 release notes.
The system distinguishes between foreground and background apps. (The definition of background for purposes of service limitations is distinct from the definition used by memory management; an app might be in the background as pertains to memory management, but in the foreground as pertains to its ability to launch services.) An app is considered to be in the foreground if any of the following is true:
It has a visible activity, whether the activity is started or paused.
It has a foreground service.
Another foreground app is connected to the app, either by binding to one of its services or by making use of one of its content providers. For example, the app is in the foreground if another app binds to its:
IME
Wallpaper service
Notification listener
Voice or text service
If none of those conditions is true, the app is considered to be in the background.
(emphasis added)
So, from the standpoint of starting a background service, a BroadcastReceiver is not in the foreground.

App crashing right away after starting service via ContextCompat.startForegroundService()

My app is for now still targeting SDK 25
I'm trying to replace my background services in order to be able to target SDK 26.
In order to do so I now start my IntentService with
ContextCompat.startForegroundService()
If I understand the documentation correctly I then have 5 seconds to call startForeground() on the service.
When doing so it works fine.
But sometimes my service doesn't have any work to do, so it just exits right away (onHandleIntent() doesn't do anything) and yet the app crashes with the following error
AndroidRuntime: FATAL EXCEPTION:
Process: com.myAPPPackage, PID: 3855
android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground()
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1768)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6494)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
What about the 5 seconds delay in that case?
I can see that the service onDestroy() method is called like a few ms after the call to ContextCompat.startForegroundService()
Any idea why the app is crashing like that?
Any idea why the app is crashing like that?
You are not calling startForeground().
But sometimes my service doesn't have any work to do, so it just exits right away
You need to call startForeground() first.
Or have whatever it is that is calling startForegroundService() determine whether there is any work to do first, before calling startForegroundService(). That way, you do not have to bother calling startForegroundService() if there is no need for a foreground service.
If I understand the documentation correctly I then have 5 seconds to call startForeground() on the service.
Yes, but that does not mean that you can skip calling startForeground(). You must call startForeground(). You just have five seconds in which to do so. My guess is that this is to avoid an avenue of abuse for startForegroundService().

Context.startForegroundService() ANR without actually calling it

After updating my app to target API 27 (previously 25) I'm encountering many ANR's from users, which I can't reproduce. They seem related to the Oreo background execution limits, with the ANR message
Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{73bc351 u0 com.xxx.xxxx/.player.PlayFileService}
However I do not call Context.startForegroundService() anywhere in my code. What are some reasons this ANR could be generated that are not a result of a direct call to this method?
In my case, even though I didn't call Context.startForegroundService() directly, it was being called because my music app would go into the background and the service would be destroyed by the system. Then, when the user pressed a media button to resume playback after a couple minutes, the service would get restarted by the system, with that call since the app was in the background. I did call startForeground() eventually but it was after a bunch of configuration. I added a call to startForeground() at the beginning of onCreate() of my service with a blank notification and all my ANR's have disappeared.
Based on the documentation:
Prior to Android 8.0, the usual way to create a foreground service was
to create a background service, then promote that service to the
foreground. With Android 8.0, there is a complication; the system
doesn't allow a background app to create a background service. For
this reason, Android 8.0 introduces the new method
startForegroundService() to start a new service in the foreground.
After the system has created the service, the app has five seconds to
call the service's startForeground() method to show the new service's
user-visible notification. If the app does not call startForeground()
within the time limit, the system stops the service and declares the
app to be ANR
You can follow this SO which describes the approach to properly start the foreground service with Notification Channel.

Runtime exception Android O with boot_completed

I'm trying to start an IntentService within my BOOT_COMPLETED receiver, but in Android O (API 26) I get:
java.lang.RuntimeException:
java.lang.IllegalStateException:
Not allowed to start service Intent { act=intent.action.update cmp=packageName.services.OwnService }:
app is in background
(Message is in one line, but this way it's easier readable)
How can I do this the correct way?
Here are some options that I outlined in a blog post:
Workaround #1: startForegroundService()
Your BroadcastReceiver that receives the ACTION_BOOT_COMPLETED broadcast
could call startForegroundService() instead of startService() when on Android
8.0+:
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
public class OnBootReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Intent i=new Intent(context, TestIntentService.class);
if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.O) {
context.startForegroundService(i);
}
else {
context.startService(i);
}
}
}
Note that this works, to an extent, even if your service does not actually
ever call startForeground(). You are given a window of time to get around
to calling startForeground(), "comparable to the ANR interval to do this".
If your work is longer than a millisecond but less than a few seconds,
you could skip the Notification and the startForeground() call. However,
you will get an error in LogCat:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.commonsware.myapplication, PID: 5991
android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground()
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1775)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6541)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
Of course, if you do not mind having a Notification briefly, you are welcome
to use startForeground() as Android expects you to, in which case you can
do background work normally, albeit with an entry showing up in the user's notification
shade.
Workaround #2: goAsync()
BroadcastReceiver has offered goAsync() since API Level 11. This allows your
receiver to do work off the main application thread, so you could get rid of the
IntentService entirely and move your code into the BroadcastReceiver.
You still only have the ANR
timeout period to work with, but you will not be tying up your main application
thread. This is better than the first workaround, insofar as it has the same
time limitation but avoids the nasty error. However, it does require some amount
of rework.
Workaround #3: JobScheduler
If your work will take more than a few seconds and you want to avoid the
Notification, you could modify your code to implement a JobService and
work with JobScheduler. This has the added advantage of only giving you
control when other criteria are met (e.g., there is a usable Internet
connection). However, not only does this require a rewrite, but JobScheduler
is only available on Android 5.0+, so if your minSdkVersion is less than 21,
you will need some other solution on the older devices.
UPDATE: Eugen Pechanec pointed out JobIntentService,
which is an interesting JobService/IntentService mashup.
You may want to check the following section of the Android O behaviour changes documentation https://developer.android.com/preview/features/background.html#services
It now limits when the app is able to start background services.

Categories

Resources