14

I have an Android application that uses a Remote Service and I bind to it with bindService(), which is asynchronous.

The app is useless until the service is bound, so I would like to simply wait until the binding is finished before any Activity is started. Is there a way to have the service bound before onCreate() or onResume() is called? I think there might be a way to do the binding in Application. Any ideas?

Edit:

if in onCreate() I do this.

bindService(service, mWebServiceConnection, BIND_AUTO_CREATE);
synchronized (mLock) { mLock.wait(40000); }

The ServiceConnection.onServiceConnected doesn't get called for 40 seconds. It's clear that I have to let onCreate() return if I want the service to bind.

So it appears there's no way to do what I want.

Edit 2: Android how do I wait until a service is actually connected? has some good commentary about what is going on in Android when binding a service.

Community
  • 1
  • 1
vipw
  • 7,593
  • 4
  • 25
  • 48
  • Is it that the app is useless until the service is bound or until the service has completed its task? – CaseyB Jun 24 '11 at 18:49
  • @CaseyB Useless until bound. The entire UI is results from the service. – vipw Jun 24 '11 at 18:51
  • 3
    possible duplicate of [Android how do I wait until a service is actually connected?](http://stackoverflow.com/questions/3055599/android-how-do-i-wait-until-a-service-is-actually-connected) – Sam Jun 19 '15 at 08:44
  • Besides being a duplicate, half the answers here are wrong anyway. I think this might as well be closed. – Sam Jun 19 '15 at 08:45

5 Answers5

6

When I need to wait a service to be bound before doing something else I play with locks. Precisely, the ServiceConnection owns a lock object and exposes a waitUntilConnected method that block on the lock until a wake up signal. That notification is located in the onServiceConnected callback.

public class MyServiceConnection implements ServiceConnection {

    private volatile boolean connected = false;
    private Object lock = new Object();

    @Override
    public void onServiceConnected(ComponentName name, IBinder binder) {
        connected = true;

        synchronized (lock) {
            lock.notifyAll();
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        connected = false;
    }

    public void waitUntilConnected() throws InterruptedException {
        if (!connected) {
            synchronized (lock) {
                lock.wait();
            }
        }
    }

}

So, for example, if an activity has to wait a service to be bound, it calls simply the waitUntilConnected method.

protected void onStart() {
    super.onStart();

    bindService(myServiceIntent, myServiceConnection, Context.BIND_AUTO_CREATE);
    try {
        myServiceConnection.waitUntilConnected();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

I placed the waitUntilConnected method in onStart just as an example, but it has to be called in a different thread. I'd like to hear a more elegant way! :)

zxon
  • 3
  • 4
Alessio
  • 302
  • 4
  • 16
  • 6
    How can this work without deadlocking? Everything happens in the same thread. – Nik Sep 09 '14 at 21:11
  • It's for this reason that I wrote "I placed the `waitUntilConnected` method in `onStart`, but it's obviously better if it is called in a different thread". The code is just a theoretical example! :) – Alessio Sep 10 '14 at 09:36
  • I just edited it to be more clear anyway, thanks! :) – Alessio Sep 10 '14 at 09:38
  • Maybe a CountDownLatch would be better than basic locks? they both work just as well though. – jophde Nov 02 '14 at 09:39
  • 2
    Can this even work? The documentation for `ServiceConnection` says `Like many callbacks from the system, the methods on this class are called from the main thread of your process.` – Sam Jun 19 '15 at 08:27
  • It did for me. It's obviusly always better think in an async way. For example using `MessageQueue` should be the Android correct way, if I remember well (it's been ages since I've been coding on Android)... – Alessio Jun 19 '15 at 15:42
  • 1
    it should be noted, calling unbind on a service doesn't call onServiceDisconnected. That said, this response does a good job answering the question to block until bindservice finishes. – sudocoder Aug 21 '18 at 17:53
6

You cannot have bindService() block. However, your ServiceConnection (2nd parameter to bindService) has callbacks to tell you when the service is connected and disconnected, so you can have other code block until your onServiceConnected() method unblocks it.

mah
  • 39,056
  • 9
  • 76
  • 93
  • 1
    Removed a dumb response: let me try again. I think that it won't work if I'm blocking in onCreate() because it's the same thread that has to deliver the ServiceConnection message. I'll test it and respond. – vipw Jun 24 '11 at 18:54
  • It seems that your options are to either have each activity bind its own connection, or if you wish a single binding you could overload the Application class and provide static methods within it (making sure they're synchronized to avoid the race conditions). Is binding to the service taking such a long time that you cannot afford to do it within each activity? – mah Jun 24 '11 at 18:57
  • 6
    You can do something like call bindService in onStart and then display an infinite ProgressDialog until the ServiceConnection's onServiceConnected method is fired. Use a Handler to time out if the bindService call fails. – Femi Jun 24 '11 at 19:04
  • @Femi It looks like your comment is the most correct answer in this thread. Would you re-write it as an answer for future reference and also to receive some upvotes? – Paolo Brandoli Sep 02 '12 at 20:33
  • 3
    @Femi no, you cannot block `bindService()`. the reason is that you are always called back on the main thread. if you block (e.g., `wait()`) after calling, you'll never be called back ... because you never return control to the looper. – Jeffrey Blattman Jul 09 '13 at 00:02
5

It seems that there is a way to do this. KeyChain.java and several Google-written classes uses a LinkedBlockingQueue to allow synchronously bind to a service.

For example, see the method called bind on this: https://github.com/android/platform_frameworks_base/blob/master/keystore/java/android/security/KeyChain.java

It seems to return the service object synchronously due to the use of blocking queue.

Unfortunately, as stated on the Android docs https://developer.android.com/reference/android/security/KeyChain.html, some methods throws InterruptedException, due to the taking of element from the queue that may be interrupted when waiting.

Randy Sugianto 'Yuku'
  • 71,383
  • 57
  • 178
  • 228
  • 5
    Using a BlockingQueue is a clever way of blocking the thread running KeyChain.bind() until the service connects. **But** that won't work when called from the main/UI thread because the call to ServiceConnection.onServiceConnected() gets queued up (e.g., using a task executor,) not run in parallel. The call to onServiceConnected() will never run until the thread's current task completes... but it will be blocked by BlockingQueue.take(). I assume KeyChain's Context's implementation of bindService() calls onServiceConnected() in parallel, unlike an Activity's implementation. – spaaarky21 Sep 30 '13 at 16:35
  • 1
    Hmm, that perfectly makes sense. It's weird that we can bind to other services like alarm, notification, synchronously but not our own service. – Randy Sugianto 'Yuku' Oct 01 '13 at 03:19
  • ensureNotOnMainThread(context); so still can't block bindService at main thread – Gohan Sep 27 '16 at 12:28
1

Android 10 has introduced a new bindService method signature when binding to a service to provide an Executor (which can be created from the Executors).

/**
     * Same as {@link #bindService(Intent, ServiceConnection, int)} with executor to control
     * ServiceConnection callbacks.
     * @param executor Callbacks on ServiceConnection will be called on executor. Must use same
     *      instance for the same instance of ServiceConnection.
    */
    public boolean bindService(@RequiresPermission @NonNull Intent service,
            @BindServiceFlags int flags, @NonNull @CallbackExecutor Executor executor,
            @NonNull ServiceConnection conn) {
        throw new RuntimeException("Not implemented. Must override in a subclass.");
    }

See this Answer

k_o_
  • 5,143
  • 1
  • 34
  • 43
0

bindService() cannot be made to block. That kind of defeats the whole purpose of a Service. You said that you whole UI consists of results from the service. I think you need to rethink your UI and populate it with some kind of intermediate representation that shows the user that the app is gathering data.

CaseyB
  • 24,780
  • 14
  • 77
  • 112
  • 4
    No, it makes perfect sense to use a Service to avoid blocking on network I/O, but to block the UI in the first place while you're simply waiting for the service to connect. – Glenn Maynard Apr 24 '13 at 16:35
  • 4
    did you know that services normally run on the main UI thread? did you know that AIDL service calls are synchronous? so no, it doesn't defeat the purpose of a service. a service is not (necessarily) a background task. it's an encapsulation of business (as opposed to UI) logic. you can run background tasks in the UI layer as well (see `AsyncTask`), but those are not services. regardless, we are talking about binding to the service, not invoking the service. – Jeffrey Blattman Jul 09 '13 at 00:05