10

In my application, I have a Service which is responsible for looking after a Bluetooth connection to an external device. This Service class periodically polls the external Bluetooth device for data and adds that latest data into a log held in cache (or possibly SD card) memory.

Amongst the various Activity classes that I have, there is one particular Activity that represents the main UI. It is responsible for displaying, in graphical form, the logged data based upon the cache file data. Let's call this Activity the Dashboard. The user can scroll back and forth on that graph to review the data collected and logged within the cache since the application was started.

For the purpose of this question, there are two modes of operation to consider. It is possible for the user to select a "log to SD card" option, whereby the application must continue the polling and logging to SD card even when all Activity classes are killed' (e.g. the user has gone back to the launcher). In this case my Service is started using .startService() and continues to run, and will only be stopped when the user invokes the application again and disabled SD card logging. The other mode is where the user hasn't selected "log to SD card", in which case the Service is still managing the Bluetooth connection, polling and logging to cache memory for the purpose of visually displaying the data on the graph, but only needs to do so while the Dashboard Activity is being used.

What I have at the moment is that the Dashboard Activity initially binds to the Service using bindService(), and does a corresponding call to unbindService() within the onPause() method (as otherwise I would of course leak the Service).

The problem is that the Service needs to maintain the Bluetooth connection and continue logging during orientation changes or when the user invokes another Activity over the top (e.g. checks an email). Now if the user has selected "log to SD card" resulting in a call to startService() then of course there is no problem. The problem of course is how to distinguish between an Activity being destroyed and then created again due to orientation (or some other configuration) change, and being destroyed because the user went back to the launcher. In the former case, I don't want the Service datalogging to have been interrupted. In the latter case, I want the Service to stop, if the user hasn't selected "log to SD card".

The best solution I can think of for this at the moment is for the service to be always started using startService(), so that it continues to run when the Dashboard has been destroyed. What I would then do is implement a time-out within the Service, whereby the Service will stop itself unless continuous SD card logging is enabled, or the Dashboard is onCreated again within five seconds (say) and re-binds to the Service. This seems a little crude, and I can't help thinking this must be a common design problem that has a better solution that I've overlooked.

user
  • 86,916
  • 18
  • 197
  • 190
Trevor
  • 10,903
  • 5
  • 61
  • 84

4 Answers4

8

Option 1: if you want the service to be destroyed, immediately when the main activity is finishing, but not during rotation:

To avoid automatic service stop you must start it manually before binding:

protected void onStart() {
  Intent intent = new Intent(getApplicationContext(), MServcie.class);
  startService(intent);
  bindService(intent, connection, Context.BIND_AUTO_CREATE);
}

Stop the service only if your Activity is finishing!

protected void onStop() {
  if (service != null) {
    unbindService(connection);
    service = null;
    if (isFinishing()) {
      stopService(new Intent(getApplicationContext(), MyServcie.class));
    }
  }
}

Option 2: if you want the os to decide when the service is being stopped.

protected void onStart() {
    Intent intent = new Intent(this, MServcie.class);
    getApplicationContext().bindService(intent, this, Context.BIND_AUTO_CREATE);
}
Frido
  • 91
  • 1
  • 3
  • Should I include their default super call before or after the above codes for onStart() and onStop() respectively? It seems like without super calls included, the app crashes. – ecle Apr 01 '21 at 02:36
4

One approach you could use, is check to see if the Activity isFinishing() before unbinding in your onPause(). If it is finishing, you would defer the unbinding to your onDestroy() method. Before onDestroy() is called, however, you could persist your ServiceConnection in the onRetainNonConfigurationInstance() method. If you do perform that persistence, than you wouldn't actually call unBind() inside of on your onDestroy() at all and simply let the new instance of your Activity do the unbinding.

That being said, depending on your application, you might find it is just easier to start/stop your service.

Justin Breitfeller
  • 13,737
  • 4
  • 39
  • 47
  • Many thanks for this, Justin. I hadn't realised about the `isFinishing()` method (shows I ought to refresh over the `Activity` documentation again). Using the result of this method I am now invoking `startService()` if I know that the `Activity` is being destroyed but is not finishing. – Trevor Dec 04 '11 at 15:07
  • Just make sure you read the documentation properly. IsFinishing should always be true when your Activity is going to be destroyed. At least that's how i read it. – Justin Breitfeller Dec 04 '11 at 16:50
  • will this not cause a runtime error because you are leaking the connection? http://stackoverflow.com/questions/12308400/what-does-it-mean-to-leak-a-service-connection – Sam Jun 25 '14 at 15:05
  • 1
    @Sam this answer is pretty out of date and partially misleading. Option 1 that Frido posted is a better laid out answer with all the required steps. – Justin Breitfeller Jun 26 '14 at 20:38
  • @JustinBreitfeller thanks for thre reply. If you use this option, haven't you lost some of the advantage of a bound service (that you can bind to it from multiple app components). What if I bound to it from some object that was running a background query AND I bound to it from an activity to do some up front UI IO? – Sam Jun 26 '14 at 21:23
  • @Sam In that case, you are going to need a more complicated solution. I haven't done it myself, but I would imagine you would then need to keep track of the number of bind/unbind calls and then use that to determine when to stop your service. – Justin Breitfeller Jun 27 '14 at 20:04
  • @JustinBreitfeller I actually ended up asking this here: http://stackoverflow.com/questions/24312016/bind-to-service-from-new-context-for-configuration-changes-or-bind-from-app-cont and came up with a potential answer myself. – Sam Jun 27 '14 at 21:31
3

You should use isChangingConfigurations(), not isFinishing(): the latter is false both in the case of an activity that loses visibility and when configuration changes, so you can't distinguish.

Alessio
  • 302
  • 4
  • 16
3

As you probably know already, a Service is really for having a separate lifecycle from Activities. As such, you could consider not using a Service for the purely monitoring scenario. You only need a lifecycle separate from Activities in the background case, but not in the purely monitoring case.

What you really seem to want is for your logging to be tied to the lifetime of your Application and for you to be able to control the lifetime of your Application through either starting/stopping Activities or your Service. If you did your polling in a separate thread accessible from the Application object, your Activities won't need to bind with a Service at all, but simply communicate with this logging thread. When you do want the background logging, you could start/stop the service which would also communicate with the logging thread to gracefully cleanup on stop, restart correctly, etc.

kabuko
  • 36,028
  • 10
  • 80
  • 93
  • Many thanks for your answer. I have accepted Justin's suggestion about using `onFinishing()` as that solved the problem, but your suggestion about putting the polling thread within the Application object is an interesting one, and something I might try. I do already have an object that extends `Application` for the purpose of holding a small amount of global state. – Trevor Dec 04 '11 at 15:09