I implemented parts of my Android app as two local bound services, one depending on the other. (I only use bindService() with them; never startService().) However, when I bind the service which has a dependency, I get a "bound" and "connected" reference to it before its own dependency is ready. This makes calls to some of its methods fail. How can I ensure I don't use my service until its dependent services are connected? Can I delay my service's connection until its own dependencies are connected?
Normally, one only gets references to bound services which are ready to be used, because onCreate() and onBind() have been called on them already. However, when you call bindService() in onCreate() of a service A for a service B, the onServiceConnected() callback for the connection to B can be (and in my case is!) called after the onServiceConnected() for the connection to A. By default everything's running in the same (main UI) thread, so you can't just make A's onBind() wait for the connection to B before it returns.
I made a simple app to demonstrate the issue. Here are the relevant excerpts. My activity loads the service with the dependency, ServiceA:
public class MainActivity extends Activity {
private ServiceA serviceA;
private final ServiceConnection connectionA = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
serviceA = ((ServiceA.Binder) service).getService();
setGreeting();
}
@Override
public void onServiceDisconnected(ComponentName name) {
serviceA = null;
}
};
protected void setGreeting() {
String greeting = serviceA.getGreeting();
TextView textView = (TextView) findViewById(R.id.greeting);
textView.setText(greeting);
}
@Override
protected void onStart() {
super.onStart();
bindService(new Intent(this, ServiceA.class),
connectionA, Context.BIND_AUTO_CREATE);
}
/* ... */
}
Here's ServiceB, which ServiceA depends on:
public class ServiceB extends Service {
public class Binder extends android.os.Binder {
public ServiceB getService() {
return ServiceB.this;
}
}
@Override
public IBinder onBind(Intent intent) {
return new Binder();
}
public String getGreeting() {
return "Hello";
}
}
And here's ServiceA:
public class ServiceA extends Service {
public class Binder extends android.os.Binder {
public ServiceA getService() {
return ServiceA.this;
}
}
@Override
public IBinder onBind(Intent intent) {
return new Binder();
}
private ServiceB serviceB;
private final ServiceConnection connectionB = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
serviceB = ((Service1.Binder) service).getService();
}
@Override
public void onServiceDisconnected(ComponentName name) {
serviceB = null;
}
};
@Override
public void onCreate() {
bindService(new Intent(this, ServiceB.class),
connectionB, Context.BIND_AUTO_CREATE);
super.onCreate();
}
@Override
public void onDestroy() {
unbindService(connectionB);
super.onDestroy();
}
public String getGreeting() {
return serviceB.getGreeting() + "!";
}
}
I've added both services to AndroidManifest.xml. When I run this, serviceB is null in the call to serviceA.getGreeting() from setGreeting(), because connectionB.onServiceConnected() has not been called yet.
Is there some way I can make connectionA.onServiceConnected() be called only after connectionB.onServiceConnected() has been? Or how else can I make sure I don't use serviceB before serviceA.serviceB has been set?
I noticed that if, instead of calling setGreeting() directly in onServiceConnected(), I post() it to the end of the looper's queue, the problem seems to disappear:
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
serviceA = ((ServiceA.Binder) service).getService();
new Handler(getMainLooper()).post(new Runnable() {
@Override
public void run() {
setGreeting();
}
});
}
I suppose this is because all the onServiceConnected() calls to be made happen to be in the looper's queue already, so by waiting for all the current tasks, serviceA.serviceB is ready by the time setGreeting() gets called. But this feels unreliable to me. Is this reliable? Is there a more reliable way to wait until my services are ready?