Reputation: 1056
My issue might be blatent misunderstanding of services and my use of them, or a poss conflict with other apps. When I start a specific activity, I start two background services - location tracking to give distance travelled, and elapsed timer, both of which are passed to the activity with a BroadcastReceiver
. I initiate each service with a Long
through the Intent
object from my primary Activity
:
if (!Utils.isServiceRunning(this, TrackService.class)) {
Intent i = new Intent(this, TrackService.class);
i.putExtra("SUB_ID", submissionID);
startService(i);
}
And I use the following code to detect if the service is already running:
public static boolean isServiceRunning(Activity activity, Class<?> serviceClass) {
ActivityManager manager = (ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
if (serviceClass.getName().equals(service.service.getClassName())) {
return true;
}
}
return false;
}
An example of the onStartCommand
is below:
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
submissionID = intent.getLongExtra("SUB_ID", 0L);
// I then use this submissionID to get other data
super.onStartCommand(intent, flags, startId);
return START_STICKY;
}
On some devices this seems to work perfectly - The service(s) works in the background, allowing me to use other activities in the app & also drop in and out of the app, and when I next open my specific activity, the distance and time are updated and correct.
However, I'm getting lots of crash reports, and anecdotal evidence from users suggests it's after dropping out of my app to use the camera (I've yet to ascertain whether the crash in my app happens while using the other app, or when they re-enter my app):
Fatal Exception: java.lang.RuntimeException: Unable to start service edu.cornell.birds.ebird.services.TrackService@3f77db78 with null: java.lang.NullPointerException: Attempt to invoke virtual method 'long android.content.Intent.getLongExtra(java.lang.String, long)' on a null object reference
at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:3149)
at android.app.ActivityThread.access$2500(ActivityThread.java:165)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1515)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5669)
at java.lang.reflect.Method.invoke(Method.java)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:960)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:755)
Caused by java.lang.NullPointerException: Attempt to invoke virtual method 'long android.content.Intent.getLongExtra(java.lang.String, long)' on a null object reference
at edu.cornell.birds.ebird.services.TrackService.onStartCommand(TrackService.java:102)
at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:3112)
at android.app.ActivityThread.access$2500(ActivityThread.java:165)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1515)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5669)
at java.lang.reflect.Method.invoke(Method.java)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:960)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:755)
It seems to me that for some reason the service is stopping, and when restarting cannot access the data from the intent (I understand what a NullPointer error is). Is this because I'm returning START_STICKY
which returns null data on a restart?
Does anyone know of any reason why the service should be stopping? And how I can prevent this happening?
I'm unable to recreate using my own device (Moto G4, Android 7.0), where it works as I hoped it would.
Upvotes: 4
Views: 3009
Reputation: 3193
Anything in Android can be killed at any point, for instance if the system has few resources. So your code should be always ready for that. And a big change in how background services are handled will come in O. That given, I think you could read the Android documentation:
"If this service is not already running, it will be instantiated and started (creating a process for it if needed); if it is running then it remains running. Every call to this method will result in a corresponding call to the target service's onStartCommand(Intent, int, int) method, with the intent given here."
so don't keep all the responsibility to the caller, but move it to the called. Start the service any time you need to pass it some info, and then let the service, which knows already if it's instantiated and running, to handle the case. Also, guard against nulls, as always in Android:
// caller, no if-check here (no responsibility)
startService(new Intent(this, TrackService.class).putExtra("SUB_ID", submissionID));
then the other side, the called service:
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent == null) {
// do nothing and return
return START_STICKY;
}
// here your intent is not null, use it
submissionID = intent.getLongExtra("SUB_ID", 0L);
// if you got the most recent data already, do nothing
// else, retrieve data using the passed id
return START_STICKY;
last but not least, please note you've no need to call super.onStartCommand(intent, flags, startId); , have a look at its implementation. Keep in mind the inversion of responsibility, because it's a more general approach you can use quite a lot coding Android, not only in this example. Hope it helps!
Upvotes: 1
Reputation: 177
Did you register your broadcast reciever in intent action of manifest file ? or dynamically?
Upvotes: -1
Reputation: 95646
Android can (and will) stop your Service
whenever it wants to. Because you have returned START_STICKY
from onStartCommand()
, Android should restart your Service
after it has been killed. In this case you will get a null Intent
in onStartCommand()
after the restart. There is no way to prevent Android from killing your Service
if it wants to.
You need to save any useful data in a persistent storage (SharedPreferences, file, database, etc.) on a regular basis so that you can recover after being restarted.
Upvotes: 1
Reputation: 973
Quick fix for this problem would be :
Return START_REDELIVER_INTENT
in the onStartCommand() callback in the service instead of START_STICKY
so that the entire intent is sent following a restart.
or
you can wrap the code inside onStartCommand with an if condition like this:
if (null == intent || null == intent.getAction ()) {
return START_STICKY;
}else{
//ur code goes in
return START_STICKY;
}
Reason for this null pointer exception would be: "if there are not any pending start commands to be delivered to the service, it will be called with a null intent object, so you must take care to check for this."
Upvotes: 0