Background: I have a few services (open for other apps to use) which run in the same process. The RPC is implemented using AIDL and therefore the services have to be open to multiple threads.
This leads me to the question: can a client still use a binder to make RPC calls even after the client unbound from the service. If so then:
Issue: I am worried that a client (either intentionally or accidentally) will bind to one of the services, unbind (possibly destroying the service because there are no other clients bound to it), then still use the binder to make remote calls. But, because the service is destroyed (resources have been released) the calls may cause exceptions. While some exceptions are implicitly "passed" back to the client (e.g. NullPointerException and IllegalStateException), most aren't and will propagate all the way back and crash the process, which may contain other alive services.
EDIT: a client can use a binder after unbinding (example code below). Now, what is the best way for a destroyed service to handle/respond to these calls (keeping in mind that the calls happen on a different thread than the thread which call onDestroy)?
public class MyService extends Service {
private volatile boolean destroyed = false;
private final IMyServiceInterface.Stub binder = new IMyServiceInterface.Stub() {
@Override
public boolean isDestroyed() {
// even worse, what if... `if (destroyed) { throw new RuntimeException(); }`
return destroyed;
}
};
@Override
public IBinder onBind(Intent intent) { return binder; }
@Override
public void onDestroy() { destroyed = true; }
}
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final Button button = (Button) findViewById(R.id.button);
button.setOnClickListener((view) ->
bindService(new Intent(this, MyService.class), serviceConnection, Context.BIND_AUTO_CREATE));
}
private final ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
final IMyServiceInterface service = IMyServiceInterface.Stub.asInterface(binder);
attemptExperiment(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
throw new AssertionError("service wasn't supposed to crash...");
}
};
private void attemptExperiment(IMyServiceInterface service) {
new Thread(() -> {
Toasts.show(this, "unbinding service and sleeping for 5 seconds...");
unbindService(serviceConnection);
try {
Thread.sleep(TimeUnit.SECONDS.toMillis(5));
} catch (InterruptedException e) {
return;
}
final boolean destroyed;
try {
destroyed = service.isDestroyed();
} catch (RemoteException e) {
Toasts.show(this, "service remote exception");
return;
}
Toasts.show(this, "service destroyed: " + destroyed);
}).start();
}
}