Android how do I wait until a service is actually connected?

45,477

Solution 1

How can I wait for ServiceConnection.onServiceConnected being called reliably?

You don't. You exit out of onCreate() (or wherever you are binding) and you put you "needs the connection established" code in onServiceConnected().

Are all the event handlers: Activity.onCreate, any View.onClickListener.onClick, ServiceConnection.onServiceConnected, etc. actually called in the same thread

Yes.

When exactly is ServiceConnection.onServiceConnected actually going to be called? Upon completion of Activity.onCreate or sometime when A.oC is still running?

Your bind request probably is not even going to start until after you leave onCreate(). Hence, onServiceConnected() will called sometime after you leave onCreate().

Solution 2

I ended up with something like this:

1) to give the auxiliary stuff some scope, I created an internal class. At least, the ugly internals are separated from the rest of the code. I needed a remote service doing something, therefore the word Something in class name

private RemoteSomethingHelper mRemoteSomethingHelper = new RemoteSomethingHelper();
class RemoteSomethingHelper {
//...
}

2) there are two things necessary to invoke a remote service method: the IBinder and the code to execute. Since we don't know which one becomes known first, we store them:

private ISomethingService mISomethingService;
private Runnable mActionRunnable;

Each time we write to one of these fileds, we invoke _startActionIfPossible():

    private void _startActionIfPossible() {
        if (mActionRunnable != null && mISomethingService != null) {
            mActionRunnable.run();
            mActionRunnable = null;
        }
    }
    private void performAction(Runnable r) {
        mActionRunnable = r;
        _startActionIfPossible();
    }

This, of course, assumes that the Runnable has access to mISomethingService, but this is true for runnables created within the methods of the RemoteSomethingHelper class.

It is really good that the ServiceConnection callbacks are called on the UI thread: if we are going to invoke the service methods from the main thread, we do not need to care about synchronization.

ISomethingService is, of course, defined via AIDL.

3) Instead of just passing arguments to methods, we create a Runnable that will invoke the method with these arguments later, when invocation is possible:

    private boolean mServiceBound;
    void startSomething(final String arg1) {
        // ... starting the service ...
        final String arg2 = ...;
        performAction(new Runnable() {
            @Override
            public void run() {
                try {
                    // arg1 and arg2 must be final!
                    mISomethingService.startSomething(arg1, arg2);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }

4) finally, we get:

private RemoteSomethingHelper mRemoteSomethingHelper = new RemoteSomethingHelper();
class RemoteSomethingHelper {
    private ISomethingService mISomethingService;
    private Runnable mActionRunnable;
    private boolean mServiceBound;
    private void _startActionIfPossible() {
        if (mActionRunnable != null && mISomethingService != null) {
            mActionRunnable.run();
            mActionRunnable = null;
        }
    }
    private ServiceConnection mServiceConnection = new ServiceConnection() {
        // the methods on this class are called from the main thread of your process.
        @Override
        public void onServiceDisconnected(ComponentName name) {
            mISomethingService = null;
        }
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mISomethingService = ISomethingService.Stub.asInterface(service);
            _startActionIfPossible();
        }
    }
    private void performAction(Runnable r) {
        mActionRunnable = r;
        _startActionIfPossible();
    }

    public void startSomething(final String arg1) {
        Intent intent = new Intent(context.getApplicationContext(),SomethingService.class);
        if (!mServiceBound) {
            mServiceBound = context.getApplicationContext().bindService(intent, mServiceConnection, 0);
        }
        ComponentName cn = context.getApplicationContext().startService(intent);
        final String arg2 = ...;
        performAction(new Runnable() {
            @Override
            public void run() {
                try {
                    mISomethingService.startSomething(arg1, arg2);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

context is a field in my class; in an Activity, you can define it as Context context=this;

I did not need queuing actions; if you do, you can implement it.

You likely will need a result callback in startSomething(); I did, but this is not shown in this code.

Solution 3

I had the same problem. I didn't want to put my bound service dependent code in onServiceConnected, though, because I wanted to bind/unbind with onStart and onStop, but I didn't want the code to run again every time the activity came back to the front. I only wanted it to run when the activity was first created.

I finally got over my onStart() tunnel vision and used a Boolean to indicate whether this was the first onServiceConnected run or not. That way, I can unbindService in onStop and bindService again in onStart without running all the start up stuff each time.

Solution 4

I did something similar before, the only different is I was not binding to service, but just starting it.

I would broadcast an intent from the service to notify the caller/activity about it is started.

Solution 5

I wanted to add some things you should or should not do:

  1. bind the service not on create but onResume and unbind it onPause. Your app can go into pause (background) at any time by user interaction or OS-Screens. Use a distinct try/catch for each and every service unbinding, receiver unregistering etc in onPause so if one is not bound or registered the exception doesn't prevent the others from being destroyed too.

  2. I usually capsule binding in a public MyServiceBinder getService() Method. I also always use a blocking boolean variable so I don't have to keep an eye on all those calls using the servie in the activity.

Example:

boolean isBindingOngoing = false;
MyService.Binder serviceHelp = null;
ServiceConnection myServiceCon = null;

public MyService.Binder getMyService()
{
   if(serviceHelp==null)
   {
       //don't bind multiple times
       //guard against getting null on fist getMyService calls!
       if(isBindingOngoing)return null; 
       isBindingOngoing = true;
       myServiceCon = new ServiceConnection(
           public void onServiceConnected(ComponentName cName, IBinder binder) {
               serviceHelp = (MyService.Binder) binder;
               //or using aidl: serviceHelp = MyService.Stub.AsInterface(binder);
               isServiceBindingOngoing = false;
               continueAfterServiceConnect(); //I use a method like this to continue
           }

           public void onServiceDisconnected(ComponentName className) {
              serviceHelp = null;
           }
       );
       bindService(serviceStartIntent,myServiceCon);
   }
   return serviceHelp;
}
Share:
45,477

Related videos on Youtube

Ryan
Author by

Ryan

Updated on February 23, 2021

Comments

  • Ryan
    Ryan over 3 years

    I have an Activity calling a Service defined in IDownloaderService.aidl:

    public class Downloader extends Activity {
     IDownloaderService downloader = null;
    // ...
    

    In Downloader.onCreate(Bundle) I tried to bindService

    Intent serviceIntent = new Intent(this, DownloaderService.class);
    if (bindService(serviceIntent, sc, BIND_AUTO_CREATE)) {
      // ...
    

    and within the ServiceConnection object sc I did this

    public void onServiceConnected(ComponentName name, IBinder service) {
      Log.w("XXX", "onServiceConnected");
      downloader = IDownloaderService.Stub.asInterface(service);
      // ...
    

    By adding all kinds of Log.xx I found that the code after if(bindService(...)) actually goes BEFORE ServiceConnection.onServiceConnected is being called - that is, when downloader is still null - which gets me into trouble. All the samples in ApiDemos avoid this timing problem by only calling services when triggered by user actions. But what should I do to right use this service after bindService succeeds? How can I wait for ServiceConnection.onServiceConnected being called reliably?

    Another question related. Are all the event handlers: Activity.onCreate, any View.onClickListener.onClick, ServiceConnection.onServiceConnected, etc. actually called in the same thread (mentioned in the doc as the "main thread")? Are there interleaves between them, or Android would schedule all events come into being handled one-by-one? Or, When exactly is ServiceConnection.onServiceConnected actually going to be called? Upon completion of Activity.onCreate or sometime when A.oC is still running?

    • Alessio
      Alessio over 10 years
      If you still need an answer I gave a solution at stackoverflow.com/a/22134635/1336747
    • Sam
      Sam about 9 years
      @Alessio, I don't think your solution actually works.
  • Ryan
    Ryan about 14 years
    Thanks for the quick reply. So in the broadcast receiver you would bind to the service to call the RPC methods, or are you not calling them in your case?
  • Ryan
    Ryan about 14 years
    Thanks for this information. Hope the Android document could get things clear like this.
  • hackbod
    hackbod about 14 years
    You can't bind to a service from a broadcast receiver (since the broadcast receive is done once it returns from onReceive).
  • hackbod
    hackbod about 14 years
    The various Service API demos have examples; see Local Service Binding and Remote Service Binding in particular: developer.android.com/resources/samples/ApiDemos/src/com/… also the Service java doc has the sample code for local service binding: developer.android.com/reference/android/app/…
  • Ryan
    Ryan about 14 years
    Thanks hackbod. Apparently this section doesn't appear in my local copy of document. I'll take a look online.
  • jfritz42
    jfritz42 over 12 years
    Although I can understand that onCreate() must finish before onServiceConnected() will start (I guess it is pending in the main thread Looper), I notice that onStart() and onResume() ALSO run before onServiceConnected()! Isn't this becoming a dangerous race condition? View components that depend on the Service are ready to go, but the Service isn't connected yet.
  • Paul Praet
    Paul Praet about 11 years
    Related question: are you sure your activity still exists when onServiceConnected is called ?
  • CommonsWare
    CommonsWare about 11 years
    @PaulPraet: Good question. I have no idea. The window of potential problems is small, and it's possible that if the activity were destroyed that onServiceConnected() simply would not get called, particularly if you called unbindService() along the way. However, I have not tried tracing through this specific scenario, mostly because I avoid binding like the plague.
  • dcow
    dcow about 11 years
    @jfritz42 That is the problem isn't it.. Why doesn't Android just give us a synchronous version of bind?
  • ericn
    ericn over 9 years
    thanks @CommonsWare but the question is when onServiceConnected() will get called e.g. 1 sec, 1 min or never?
  • MikeL
    MikeL over 8 years
    @eric You can not count on any particular time. This is why the call is asynchronous.
  • Kevin Lee
    Kevin Lee over 8 years
    @jfritz42 In onCreate or onStart or onResume, the views can try asking the service object for the values (but first check whether the service object exists), and if the service object is there it should be able to give some value, else revert to some default. Under onServiceConnected, update the views. No matter which happens first, it seems that any state in the service would be synced to the views you are holding in the activity.
  • ybloodz
    ybloodz almost 4 years
    Thanks! I Spent a whole day trying to figure out why my service isn't starting. It was weird because 1 out of 10 times it would start. I assumed it was connected immediately after bindService .