Cleanly binding/unbinding to a Service in an Application
Asked Answered
D

3

13

I have an Android application that is binding to a persistent service (once started with startService()).

The service is an integral part of the application and thus is used in almost every Activity. Hence I want to bind to the service just once (instead of binding/unbinding in every Activity) and keep the binding during the lifetime of my application.

I've extended from Application and bind to the service in Application#onCreate(). However I now have the problem that I don't know when my application exists since Application#onTerminate() is never called, see JavaDoc:

This method is for use in emulated process environments. It will never be called on a production Android device, where processes are removed by simply killing them; no user code (including this callback) is executed when doing so.

So how do I cleanly unbind from a service bound in Application?

Doig answered 20/2, 2012 at 16:3 Comment(0)
D
13

I solved this problem by counting the references to the service binding in the Application. Every Activity has to call acquireBinding() in their onCreate() methods and call releaseBinding() in onDestroy(). If the reference counter reaches zero the binding is released.

Here's an example:

class MyApp extends Application {
    private final AtomicInteger refCount = new AtomicInteger();
    private Binding binding;

    @Override
    public void onCreate() {
        // create service binding here
    }

    public Binding acquireBinding() {
        refCount.incrementAndGet();
        return binding;
    }

    public void releaseBinding() {
        if (refCount.get() == 0 || refCount.decrementAndGet() == 0) {
            // release binding
        }
    }
}

// Base Activity for all other Activities
abstract class MyBaseActivity extend Activity {
    protected MyApp app;
    protected Binding binding;

    @Override
    public void onCreate(Bundle savedBundleState) {
        super.onCreate(savedBundleState);
        this.app = (MyApp) getApplication();
        this.binding = this.app.acquireBinding();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        this.app.releaseBinding();
    }
}
Doig answered 8/3, 2012 at 18:36 Comment(4)
How does your service survive screen rotations?Brei
What do you mean? A Service in Android doesn't have a UI so it keeps running. In this case the Service was previously started with context.startService() and is marked sticky so it runs until it's manually stopped or stops itself.Doig
I see. I was thinking that releasing the binding would stop the service, but not in this hybrid-service case. The binding is released in order to avoid a leaked service connection.Brei
Correct. The service is started with startService() but also is bound to.Doig
H
4

From Sven's answer:

I solved this problem by counting the references to the service binding in the Application. Every Activity has to call acquireBinding() in their onCreate() methods and call releaseBinding() in onDestroy(). If the reference counter reaches zero the binding is released.

I agree, BUT you shouldn't do it in onDestroy - that will often not get called.

Instead I suggest the following (based on your code sample)...

// Base Activity for all other Activities
abstract class MyBaseActivity extend Activity {
    protected MyApp app;
    protected Binding binding;

    @Override
    public void onCreate(Bundle savedBundleState) {
        super.onCreate(savedBundleState);
        this.app = (MyApp) getApplication();
        this.binding = this.app.acquireBinding();
    }

    @Override
    protected void onPause() {
        super.onPause();
        // Pre-HC, activity is killable after this.
        if ((11 > Build.VERSION.SDK_INT) && (isFinishing()))
            onFinishing();
    }

    @Override
    protected void onStop() {
        super.onStop();
        if ((10 < Build.VERSION.SDK_INT) && (isFinishing()))
            onFinishing();
    }

    protected void onFinishing() {
        // Do all activity clean-up here.
        this.app.releaseBinding();          
    }
}

BUT, my use of isFinishing() is just a thought - I'm not certain that it is reliable. Perhaps onPause/onStop get called with isFinishing() false, but then the activity gets killed - and your releaseBinding() never gets called.

If you get rid of the isFinishing check I think you need to move the acquireBinding() call from onCreate to onStart/onResume (depending on sdk version), to ensure that your ref count doesn't get messed up.

Who knew that releasing your app's service would be so complicated!

Her answered 31/3, 2012 at 21:42 Comment(1)
Thank you for the clarification. I will have a look at this. I also have the impression that my solution isn't reliable all the time.Doig
O
2

Is unbinding necessary at all in this case? The application gets killed anyway. I tried implementing a sample application doing this without unbinding and it seems to work properly.

Orthoepy answered 22/11, 2012 at 17:4 Comment(1)
If you don't unbind you should see "leaked service connection" errors in the logs.Doig

© 2022 - 2024 — McMap. All rights reserved.