11

I'm trying to work out if bound service is appropriate for doing background work in my app. The requirements are that various application components can make web requests through it of varying priority. (Thus the service must maintain some sort of queue and be able to cancel it's ongoing requests for others of higher priority). I'd like the service to be relatively unobtrusive to the user such that they don't find it running after they are done with the application - if I want to do something more important that continues while the application is closed I can use startForeground() to push a notification during the process.

Solution the first: bind from the activity

So, for a given application component it should be able to bind to the service to get work done. But there seems to be a well known problem that if an activity is doing the binding, the binding will be lost during configuration change (rotation) as the activity will be closed.

So, I was thinking I could use another context that I create (new Context()) and bind from that to the service, then use a non-UI fragment to maintain this context across config changes until I deem that I am finished with it. I could do this only during the configuration change or as a permanent alternative to binding from the activity. (I should probably point out that this is a standard and recommended way to maintain instances across config changes)

Solution numero 2:

The main alternative I see is that I can use the application context to do the binding - but could this persist too long? and/or could there be some cyclic relationship between the app context and the service thus preventing the service and the app context being destroyed?

Questions:

So the question I'm trying to answer to myself is: should I use the first method (activities with temporary contexts)? Or the second (just bind service to the app context)?

Am I right in thinking the app context can bind to the service multiple times and then unbind from it the same number of times? (I.e. that you can have multiple valid bindings PER context)?

Could using my own context (new Context()) in the first solution cause any issues?

Edit

Found some more information: https://groups.google.com/forum/#!topic/android-developers/Nb58dOQ8Xfw

It also seems that it will difficult to 'create' a context arbitrarily so a combination of solution 1 and 2 seems appropriate where the service connection is maintained across the configurations change but the binding is to the app context. I am still concerned about the possibility of unbinding twice from the app context. Keeping count of the bindings myself seems unnecessary - can anyone confirm/deny that bindings are per connection and not per context?

Sam
  • 3,453
  • 1
  • 33
  • 57
  • Its intended to use binding for Activity-Service links. I dont understand why you do not just use it the way it was meant to be used. Just bind your activity again after a configuration change. – RaphMclee Jun 21 '14 at 05:49
  • Raph - won't the service die when the context is destroyed – Sam Jun 22 '14 at 08:14
  • I.e. I don't want to delay a long network action just because the phone is rotated.... – Sam Jun 22 '14 at 15:14
  • It depends. If you start the service with bind. The service will be destroyed when all bindings are removed. But you can start the service independently to the binding, so that it survives even without any binding. You want delay anything. – RaphMclee Jun 22 '14 at 18:08
  • Yes, sorry I wasn't making that clear - I could like to start it with bind so that it only exists as long as required. – Sam Jun 23 '14 at 07:09
  • You know the retainInstance() method on the activity? Probably this suits your need. – RaphMclee Jun 24 '14 at 09:26
  • There is no such method. Are you talking about the setRetainInstance(Boolean) method on fragments? This is what I was implying when I said a 'non-ui fragment'. Have a look here, you may be slightly out of date as some methods were deprecated: http://developer.android.com/guide/topics/resources/runtime-changes.html – Sam Jun 24 '14 at 10:56
  • You may have your `service` to be both `started` and `bound`. Then `unbind` will not destroy the `service` (the service itself will be responsible for calling `stopSelf` when it decides that the job is done AND no activity is bound to it). – Tomasz Gawel Jun 27 '14 at 19:26
  • @TomaszGawel I think I the automatic destroy on UnBind is the most desirable feature of a bound service – Sam Jun 30 '14 at 09:29
  • @TomaszGawel I posted some code on my answer to demonstrate what I mean... – Sam Jun 30 '14 at 17:21

4 Answers4

5

So after doing some digging I think I have come up with an (as yet) untested solution.

Firstly, based on Diane's suggestion here: https://groups.google.com/forum/#!topic/android-developers/Nb58dOQ8Xfw I should be binding to the application context - so my problem of losing the context is gone - I can maintain my ServiceConnection across configuration changed with a Non-UI fragment - great. Then when I am done I can use the app context to hand back the service connection and unbind. I shouldn't receive any leaky service connection warnings. (I should probably point out that this is a standard and recommended way to maintain instances across config changes)

The final crux of this problem was I was unsure of whether I could bind multiple times from the same context - the documentations on bindings imply there is some dependence between the binding and the context's lifecycle and so I was worried I would have to do my own form of reference counting. I had a look at the source code and ended up here: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4.2_r1/android/app/LoadedApk.java#LoadedApk.forgetServiceDispatcher%28android.content.Context%2Candroid.content.ServiceConnection%29

Crucially, these lines:

sd = map.get(c);
    if (sd != null) {
        map.remove(c);
        sd.doForget();
        if (map.size() == 0) {
            mServices.remove(context);
        }

Reveal that the map is being used for the reference counting I was worried about.

SO the take home is this:

  • Bound service will work fine with the app context and we SHOULD do this to prevent leaking a service connection from one activity to another during a config change
  • I can keep my service connection on a non-UI fragment safely and use it to unbind when I am done

I'll try and post some tested code soon.

UPDATE and tested solution: I've made some code to test this and published here: https://github.com/samskiter/BoundServiceTest

It seems to work quite well and the non-ui fragment (data fragment) acts as a nice proxy listener during rotation changes to catch results from the service (the intention of the listeners is to closely bind the requests to the UI in order to guarantee it stays responsive. Obviously any model changes can be propagated to the UI via observers.)

Edit: I thought I should explicitly answer the questions in the OP...

  • should I use the first method (activities with temporary contexts)? Or the second (just bind service to the app context)? The second

  • Am I right in thinking the app context can bind to the service multiple times and then unbind from it the same number of times? (I.e. that you can have multiple valid bindings PER context)? Yes

  • Could using my own context (new Context()) in the first solution cause any issues? This is not even possible

A final summary:

This pattern should be pretty powerful - I can prioritise network IO (or other tasks) coming from a variety of sources across my app. I could have a foreground activity making some small io the user has asked for, simultaneously I could have kicked of a foreground service to sync all my users data. Both the foregrounds service and the activity can be bound to the same Network service to get their requests done.

All this while making sure the service lives only exactly as long as it needs to - i.e. it plays nicely with android.

I'm excited to get this into an app soon.

UPDATE: I've tried to write this up and give some context to the wider problem of background work in a blog entry here: http://blog.airsource.co.uk/2014/09/10/android-bound-services/

Sam
  • 3,453
  • 1
  • 33
  • 57
  • Why do you need to mediate through a background fragment? It just seems unnecessarily complicated. – Rarw Jun 27 '14 at 19:04
  • so you can keep hold of the connection. it's the new alternative to using `onretainnonconfigurationinstance` and not that complicated – Sam Jun 28 '14 at 11:31
  • It's just another layer of complexity. You're using the fragment to just maintain the context you bind the service too which is odd to me since a service has its own context and can run without being bound to anything. But post your final solution because I'm interested to see what you end up using. – Rarw Jun 28 '14 at 15:41
  • Ah sorry, I might have explained this badly. I'm only intending to use the fragment to maintain the connection and not the context. Maintaining a context like that would lead to a leak. Keeping the connection is effectively just a way of holding pointers to the service and so keeping it alive. So when all 'pointers' to the service have gone it can be destroyed. – Sam Jun 28 '14 at 16:15
  • You mean references. But your fragment's lifecycle is still tied to the activity that is hosting it, background or otherwise. So how does the configuration change not cause a change in the fragment lifecycle and lose connection to the service anyway? – Rarw Jun 28 '14 at 16:35
  • It uses a non-UI fragment that retains across config changes: http://developer.android.com/guide/topics/resources/runtime-changes.html#RetainingAnObject – Sam Jun 28 '14 at 17:08
  • Any thoughts @Rarw or others? – Sam Jun 30 '14 at 17:20
  • Looks good. Kind of a nice android specific twist on an observer pattern. – Rarw Jun 30 '14 at 19:35
  • Yea, the caching behaviour of the data fragment alone seems great for handling async callbacks (as opposed to having to resubscribe to conventional observables and read initial state on every onCreate()). – Sam Jul 01 '14 at 17:59
0

Can you not just select configurations that you would like to handle with the configChanges attribute in your manifest and do the orientation changes in the UI manually? In this case you only need to bind to the service in onCreate and then unBind in onDestroy.

or maybe try something like this ( I have not done proper error checking):

  

    class MyServiceConnection implements ServiceConnection,Parcelable {
                public static final Parcelable.Creator CREATOR
                = new Parcelable.Creator() {
                    public MyServiceConnection createFromParcel(Parcel in) {
                        return new MyServiceConnection(in);
                    }

                    public MyServiceConnection[] newArray(int size) {
                        return new MyServiceConnection[size];
                    }
                };

                @Override
                public int describeContents() {
                    return 0;
                }

                @Override
                public void writeToParcel(Parcel dest, int flags) {

                }

                @Override
                public void onServiceConnected(ComponentName name, IBinder service) {

                }

                @Override
                public void onServiceDisconnected(ComponentName name) {

                }
            }
            MyServiceConnection myServiceConnection;
            boolean configChange = false;

            protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.activity_main);
                if (savedInstanceState != null) {
                    myServiceConnection = savedInstanceState.getParcelable("serviceConnection");
                } else {
                    myServiceConnection = new MyServiceConnection();
                }

            }
            @Override
            protected void onSaveInstanceState(Bundle outState) {
                super.onSaveInstanceState(outState);
                if (myServiceConnection != null) {
                    outState.putParcelable("serviceConnection",myServiceConnection);
                    configChange = true;
                }
            }
            @Override
            protected void onDestroy() {
                super.onDestroy();
                if (!configChange && myServiceConnection != null){
                    unbindService(myServiceConnection);
                }
            }
        }

hoomi
  • 1,882
  • 2
  • 16
  • 17
  • Not really - that is a last-resort approach. See Romain Guy's comments here: http://stackoverflow.com/questions/2620917/how-to-handle-an-asynctask-during-screen-rotation Apologies for the down-vote - I don't want others to reach this question and solve the problem this way. – Sam Jun 26 '14 at 14:00
  • No worries. I added another solution. – hoomi Jun 26 '14 at 14:27
  • hmmm, I see you are trying to make service connection parcelable but I don't see how it get's parcelled. Could you explain a little more please? – Sam Jun 26 '14 at 17:00
  • In order to unbind from a service you need the service connection instance that you used when you bund to the service. Since service connection is an interface your class does not need to have any fields .That is why I have empty constructors. You could however add some fields if you wanted to but you have to then change parcelable method appropriately. Let me know if you need more explanation – hoomi Jun 27 '14 at 07:13
  • but you aren't doing anything in writeToParcel? and if you only have empty constructors how do you call this method: `MyServiceConnection(in)` – Sam Jun 27 '14 at 07:52
  • I only use `writeToParcel` if I had some fields in my class but in this example I do not. Also, I don't have to call `MyServiceConnection(in)` since in does not hold any useful data for me (as I did not write anything into it). I could just call `MyServiceConnection()` in this example. You could just write a simple example and see it working. – hoomi Jun 27 '14 at 08:37
  • sorry I'm pretty sure this wont work. you will lose your instance of the ServiceConnection and will make a new one after you deserialise. You therefore cannot unbind with the same ServiceConnection you bound with (because you lost the first one in the parceling process). – Sam Jun 27 '14 at 09:40
  • 1
    You are absolutely right. When I ran my test I did not realize that there was a ServiceConnection leak. – hoomi Jun 27 '14 at 14:09
  • After reading some of the android I think I have a solution to the question that I'll post later. – Sam Jun 27 '14 at 14:18
0

There is a much easier way to handle this situation called an IntentService which you can read more about here. From the android site:

"The IntentService class provides a straightforward structure for running an operation on a single background thread. This allows it to handle long-running operations without affecting your user interface's responsiveness. Also, an IntentService isn't affected by most user interface lifecycle events, so it continues to run in circumstances that would shut down an AsyncTask"

Rather than binding your service to your activity you can start long-running actions on a background thread by simply using an intent that starts your IntentService

public class RSSPullService extends IntentService {

    @Override
    protected void onHandleIntent(Intent workIntent) {
    // Gets data from the incoming Intent
    String dataString = workIntent.getDataString();
    ...
    // Do work here, based on the contents of dataString
    ...
    }
}

This is an example taken from the android docs. You would send an intent with the relevant data then handle that data within the service to do what you want. For example, you could just add a priority flag to your intent so your service knows which requests come before others.

The benefit of an intent service is that it runs on a background thread and is not tied to the lifecycle of the starting activity. That means you configuration changes should not have an effect on the service execution.

When your service is done you can report work status by using a local broadcast - either sending the results directly back to the activity (via broadcast receiver) or possibly even through onNewIntent() (though getting that to work is a bit more clunky.

Edit - answer questions in comment

IntentService is a relatively small class. This makes it easy to modify. The stock code for IntentService calls stopSelf() and dies when it runs out of work to do. This can be easily fixed. Examining the source for the IntentService (see the previous link) you can see that it pretty much works off a queue already, receiving messages in onStart() and then executing them in the order received as described in the comment. Overriding onStart() will allow you to implement a new queue structure to meet your needs. Use the example code there for how to handle the incoming message and get the Intent then just create your own data structure for handling priority. You should be able to start/stop your web requests in the IntentService the same way you would in a Service. Thus by overriding onStart() and onHandleIntent() you should be able to do what you want.

Community
  • 1
  • 1
Rarw
  • 7,645
  • 3
  • 28
  • 46
  • Hi @Rarw, thanks for your response, unfortunately I already had a look at IntentService but it did not fit my requirements as I need to be able to cancel and queue jobs up by priority and therefore need a reasonably custom service implementation: "The requirements are that various application components can make web requests through it of varying priority. (Thus the service must maintain some sort of queue and be able to cancel it's ongoing requests for others of higher priority)." – Sam Jun 27 '14 at 18:28
  • The specific problem with this is that an IntentService is designed to run tasks sequentially: "Work requests run sequentially. If an operation is running in an IntentService, and you send it another request, the request waits until the first operation is finished." Of course, correct me if you feel I have misunderstood. – Sam Jun 27 '14 at 18:29
  • A final note: I cannot return immediately from the IntentService and then post updates later as an IntentService stops itself when it's work queue is empty: "Clients send requests through startService(Intent) calls; the service is started as needed, handles each Intent in turn using a worker thread, and stops itself when it runs out of work." – Sam Jun 27 '14 at 18:36
  • 1
    I will answer your questions above – Rarw Jun 27 '14 at 18:52
  • Thanks, I also posted my own answer after doing some digging in the android source to abate my nerves – Sam Jun 27 '14 at 18:55
  • OK, I see how it is possible to keep the intentservice running. So I would also send a special intent to stop the service when I am done with it. Am I not effectively recreating a bound service? – Sam Jun 28 '14 at 12:17
  • There should be a stopService() method in your activity or fragment the same way there is a startService(). But yes you could send an intent with a "stop" flag and have the service stop itself. There's a lot of ways to do that. – Rarw Jun 28 '14 at 15:39
0

I had a similar problem, where I have a Bound Service used in an Activity. Inside the activity I define a ServiceConnection, mConnection, and inside onServiceConnected I set a class field, syncService that's a reference to the Service:

private SynchronizerService<Entity> syncService;

(...)

/** Defines callbacks for service binding, passed to bindService() */
private ServiceConnection mConnection = new ServiceConnection() {

    @Override
    public void onServiceConnected(ComponentName className, IBinder service) {
        // We've bound to LocalService, cast the IBinder and get
        // LocalService instance
        Log.d(debugTag, "on Service Connected");
        LocalBinder binder = (LocalBinder) service;
        //HERE
        syncService = binder.getService();
        //HERE
        mBound = true;
        onPostConnect();
    }

    @Override
    public void onServiceDisconnected(ComponentName arg0) {
        Log.d(debugTag, "on Service Disconnected");
        syncService = null;
        mBound = false;
    }
};

Using this method, whenever the orientation changed I would get a NullPointerException when referencing the syncService variable, despite the fact the service was running, and I tried several methods that never worked.

I was about to implement the solution proposed by Sam, using a retained fragment to keep the variable, but first remembered to try a simple thing: setting the syncService variable to static.. and the connection reference is maintained when the orientation changes!

So now I have

private static SynchronizerService<Entity> syncService = null;

...

/** Defines callbacks for service binding, passed to bindService() */
private ServiceConnection mConnection = new ServiceConnection() {

    @Override
    public void onServiceConnected(ComponentName className, IBinder service) {
        // We've bound to LocalService, cast the IBinder and get
        // LocalService instance
        Log.d(debugTag, "on Service Connected");
        LocalBinder binder = (LocalBinder) service;
        //HERE
        if(syncService == null) {
            Log.d(debugTag, "Initializing service connection");
            syncService = binder.getService();
        }
        //HERE
        mBound = true;
        onPostConnect();
    }

    @Override
    public void onServiceDisconnected(ComponentName arg0) {
        Log.d(debugTag, "on Service Disconnected");
        syncService = null;
        mBound = false;
    }
};
miguel_rdp
  • 17
  • 3
  • I don't think setting a variable to static, effectively making it global, is a standard or recommended way to maintain any variable on an activity. what if I re-use the activity? or what if it is destroyed for other reasons? – Sam Jul 07 '14 at 14:31
  • 1
    @Sam I don't see why not.. If it were an async task I would agree, but if the service is always running, and its life cycle is well known and controlled , why not keep a private static variable, or even a global one, out of the activity, for that matter, if one wants to reuse it? Surely it's less costly than an headless fragment, although I agree that your solution is perhaps more elegant – miguel_rdp Jul 07 '14 at 23:55
  • Because a bound service is not always running. From the POV of the activity, you can only guarantee it lives as long as that activity (excluding config changes now we have our fix) so you better clear it when you are destroyed proper. So making it available to anyone else is pointless as you could just die, remove your connection and kill the service. If you mean model objects could use it - they can just grab their own reference and use bound activity as it is intended - where application components each individually bind to it to keep it alive for their purposes. – Sam Jul 08 '14 at 10:10
  • Although using a private static will probably work for most purposes I think there is a certain assertion there will only be once instance of each activity alive at a time. Remember that a backgrounded activity is only stopped (or if it's visible it's paused). If you chose to only disconnect from the service in onDestroy you could have two instances of your activity in different parts of their lifecycle's adjusting and changing the static variable. Essentially it looks simple but hides problems and could easily be done wrong. Why does `onSaveInstanceState` exist if statics are fine? – Sam Jul 08 '14 at 10:19
  • 1
    @Sam I see what you're saying. I guess it depends on the architecture. In my app's case, it's more than a "state"... the service should always be available, like for example an HTTP pooled client, or an app-wide image cache should. As for the concurrency problem you are stating, 2 activities changing the static variable, I think your solution could also have the same problem if 2 activities try to manipulate the variable inside the retained fragment, no? – miguel_rdp Jul 08 '14 at 11:00
  • No it will be fine as the retained fragment is per activity 'instance'. (where instance means an activity as it lives across config changes). To me it sounds like you shouldn't be using bound service if you need it always available. – Sam Jul 08 '14 at 11:11
  • 1
    I use the bound service because it's easier to communicate with and on can call methods directly etc. Oh I didn't realize your retained fragment was per instance. If I run into problems using statics or a singleton pattern, I'll definitely implement your solution then! – miguel_rdp Jul 08 '14 at 11:26
  • yes, definitely have a look at this: http://developer.android.com/guide/topics/resources/runtime-changes.html#RetainingAnObject – Sam Jul 08 '14 at 11:49