mirror of https://github.com/grpc/grpc-java.git
parent
b29c3ec021
commit
128324540f
|
|
@ -119,6 +119,7 @@ final class ServiceBinding implements Bindable, ServiceConnection {
|
|||
state = State.BINDING;
|
||||
Status bindResult = bindInternal(sourceContext, bindIntent, this, bindFlags);
|
||||
if (!bindResult.isOk()) {
|
||||
handleBindServiceFailure(sourceContext, this);
|
||||
state = State.UNBOUND;
|
||||
mainThreadExecutor.execute(() -> notifyUnbound(bindResult));
|
||||
}
|
||||
|
|
@ -142,6 +143,19 @@ final class ServiceBinding implements Bindable, ServiceConnection {
|
|||
}
|
||||
}
|
||||
|
||||
// Over the years, the API contract for Context#bindService() has been inconsistent on the subject
|
||||
// of error handling. But inspecting recent AOSP implementations shows that, internally,
|
||||
// bindService() retains a reference to the ServiceConnection when it throws certain Exceptions
|
||||
// and even when it returns false. To avoid leaks, we *always* call unbindService() in case of
|
||||
// error and simply ignore any "Service not registered" IAE and other RuntimeExceptions.
|
||||
private static void handleBindServiceFailure(Context context, ServiceConnection conn) {
|
||||
try {
|
||||
context.unbindService(conn);
|
||||
} catch (RuntimeException e) {
|
||||
logger.log(Level.FINE, "Could not clean up after bindService() failure.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@AnyThread
|
||||
public void unbind() {
|
||||
|
|
|
|||
|
|
@ -68,6 +68,9 @@ public final class ServiceBindingTest {
|
|||
shadowApplication = shadowOf(appContext);
|
||||
shadowApplication.setComponentNameAndServiceForBindService(serviceComponent, mockBinder);
|
||||
|
||||
// Don't call onServiceDisconnected() upon unbindService(), just like the real Android doesn't.
|
||||
shadowApplication.setUnbindServiceCallsOnServiceDisconnected(false);
|
||||
|
||||
binding = newBuilder().build();
|
||||
shadowOf(getMainLooper()).idle();
|
||||
}
|
||||
|
|
@ -137,6 +140,7 @@ public final class ServiceBindingTest {
|
|||
assertThat(observer.gotUnboundEvent).isTrue();
|
||||
assertThat(observer.unboundReason.getCode()).isEqualTo(Code.CANCELLED);
|
||||
assertThat(binding.isSourceContextCleared()).isTrue();
|
||||
assertThat(shadowApplication.getBoundServiceConnections()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -174,6 +178,7 @@ public final class ServiceBindingTest {
|
|||
assertThat(observer.gotUnboundEvent).isTrue();
|
||||
assertThat(observer.unboundReason.getCode()).isEqualTo(Code.UNIMPLEMENTED);
|
||||
assertThat(binding.isSourceContextCleared()).isTrue();
|
||||
assertThat(shadowApplication.getBoundServiceConnections()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -187,6 +192,7 @@ public final class ServiceBindingTest {
|
|||
assertThat(observer.unboundReason.getCode()).isEqualTo(Code.PERMISSION_DENIED);
|
||||
assertThat(observer.unboundReason.getCause()).isEqualTo(securityException);
|
||||
assertThat(binding.isSourceContextCleared()).isTrue();
|
||||
assertThat(shadowApplication.getBoundServiceConnections()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -257,7 +263,8 @@ public final class ServiceBindingTest {
|
|||
} catch (IllegalMonitorStateException ime) {
|
||||
// Expected.
|
||||
} catch (InterruptedException inte) {
|
||||
throw new AssertionError("Interrupted exception when we shouldn't have been able to wait.", inte);
|
||||
throw new AssertionError(
|
||||
"Interrupted exception when we shouldn't have been able to wait.", inte);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue