mirror of https://github.com/grpc/grpc-java.git
Add LifecycleOnDestroyHelper to support shutdown of channel/server on Android lifecycle changes (#8568)
This commit is contained in:
parent
28f2647aaf
commit
fcc7b9694e
|
|
@ -49,9 +49,12 @@ dependencies {
|
||||||
|
|
||||||
implementation libraries.androidx_annotation
|
implementation libraries.androidx_annotation
|
||||||
implementation libraries.androidx_core
|
implementation libraries.androidx_core
|
||||||
|
implementation libraries.androidx_lifecycle_common
|
||||||
implementation libraries.guava
|
implementation libraries.guava
|
||||||
testImplementation libraries.androidx_core
|
testImplementation libraries.androidx_core
|
||||||
testImplementation libraries.androidx_test
|
testImplementation libraries.androidx_test
|
||||||
|
testImplementation libraries.androidx_lifecycle_common
|
||||||
|
testImplementation libraries.androidx_lifecycle_service
|
||||||
testImplementation libraries.junit
|
testImplementation libraries.junit
|
||||||
testImplementation libraries.mockito
|
testImplementation libraries.mockito
|
||||||
testImplementation (libraries.robolectric) {
|
testImplementation (libraries.robolectric) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 The gRPC Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.grpc.binder;
|
||||||
|
|
||||||
|
import androidx.annotation.MainThread;
|
||||||
|
import androidx.lifecycle.Lifecycle;
|
||||||
|
import androidx.lifecycle.Lifecycle.State;
|
||||||
|
import androidx.lifecycle.LifecycleEventObserver;
|
||||||
|
import androidx.lifecycle.LifecycleObserver;
|
||||||
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
|
import io.grpc.ManagedChannel;
|
||||||
|
import io.grpc.Server;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helps work around certain quirks of {@link Lifecycle#addObserver} and {@link State#DESTROYED}.
|
||||||
|
*
|
||||||
|
* <p>In particular, calls to {@link Lifecycle#addObserver(LifecycleObserver)} are silently ignored
|
||||||
|
* if the owner is already destroyed.
|
||||||
|
*/
|
||||||
|
public final class LifecycleOnDestroyHelper {
|
||||||
|
|
||||||
|
private LifecycleOnDestroyHelper() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Arranges for {@link ManagedChannel#shutdownNow()} to be called on {@code channel} just before
|
||||||
|
* {@code lifecycle} is destroyed, or immediately if {@code lifecycle} is already destroyed.
|
||||||
|
*
|
||||||
|
* <p>Must only be called on the application's main thread.
|
||||||
|
*/
|
||||||
|
@MainThread
|
||||||
|
public static void shutdownUponDestruction(Lifecycle lifecycle, ManagedChannel channel) {
|
||||||
|
if (lifecycle.getCurrentState() == State.DESTROYED) {
|
||||||
|
channel.shutdownNow();
|
||||||
|
} else {
|
||||||
|
lifecycle.addObserver(
|
||||||
|
new LifecycleEventObserver() {
|
||||||
|
@Override
|
||||||
|
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
|
||||||
|
if (event == Lifecycle.Event.ON_DESTROY) {
|
||||||
|
source.getLifecycle().removeObserver(this);
|
||||||
|
channel.shutdownNow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Arranges for {@link Server#shutdownNow()} to be called on {@code server} just before {@code
|
||||||
|
* lifecycle} is destroyed, or immediately if {@code lifecycle} is already destroyed.
|
||||||
|
*
|
||||||
|
* <p>Must only be called on the application's main thread.
|
||||||
|
*/
|
||||||
|
@MainThread
|
||||||
|
public static void shutdownUponDestruction(Lifecycle lifecycle, Server server) {
|
||||||
|
if (lifecycle.getCurrentState() == State.DESTROYED) {
|
||||||
|
server.shutdownNow();
|
||||||
|
} else {
|
||||||
|
lifecycle.addObserver(
|
||||||
|
new LifecycleEventObserver() {
|
||||||
|
@Override
|
||||||
|
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
|
||||||
|
if (event == Lifecycle.Event.ON_DESTROY) {
|
||||||
|
source.getLifecycle().removeObserver(this);
|
||||||
|
server.shutdownNow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 The gRPC Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.grpc.binder;
|
||||||
|
|
||||||
|
import static android.os.Looper.getMainLooper;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyNoInteractions;
|
||||||
|
import static org.robolectric.Shadows.shadowOf;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LifecycleService;
|
||||||
|
import io.grpc.ManagedChannel;
|
||||||
|
import io.grpc.Server;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.MockitoJUnit;
|
||||||
|
import org.mockito.junit.MockitoRule;
|
||||||
|
import org.robolectric.Robolectric;
|
||||||
|
import org.robolectric.RobolectricTestRunner;
|
||||||
|
import org.robolectric.android.controller.ServiceController;
|
||||||
|
|
||||||
|
@RunWith(RobolectricTestRunner.class)
|
||||||
|
public final class LifecycleOnDestroyHelperTest {
|
||||||
|
|
||||||
|
@Rule public final MockitoRule mocks = MockitoJUnit.rule();
|
||||||
|
|
||||||
|
private ServiceController<MyService> sourceController;
|
||||||
|
private MyService sourceService;
|
||||||
|
|
||||||
|
@Mock ManagedChannel mockChannel;
|
||||||
|
@Mock Server mockServer;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
sourceController = Robolectric.buildService(MyService.class);
|
||||||
|
sourceService = sourceController.create().get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldShutdownChannelUponSourceDestruction() {
|
||||||
|
LifecycleOnDestroyHelper.shutdownUponDestruction(sourceService.getLifecycle(), mockChannel);
|
||||||
|
shadowOf(getMainLooper()).idle();
|
||||||
|
verifyNoInteractions(mockChannel);
|
||||||
|
|
||||||
|
sourceController.destroy();
|
||||||
|
shadowOf(getMainLooper()).idle();
|
||||||
|
verify(mockChannel).shutdownNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldShutdownChannelForInitiallyDestroyedSource() {
|
||||||
|
sourceController.destroy();
|
||||||
|
shadowOf(getMainLooper()).idle();
|
||||||
|
|
||||||
|
LifecycleOnDestroyHelper.shutdownUponDestruction(sourceService.getLifecycle(), mockChannel);
|
||||||
|
verify(mockChannel).shutdownNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldShutdownServerUponServiceDestruction() {
|
||||||
|
LifecycleOnDestroyHelper.shutdownUponDestruction(sourceService.getLifecycle(), mockServer);
|
||||||
|
shadowOf(getMainLooper()).idle();
|
||||||
|
verifyNoInteractions(mockServer);
|
||||||
|
|
||||||
|
sourceController.destroy();
|
||||||
|
shadowOf(getMainLooper()).idle();
|
||||||
|
verify(mockServer).shutdownNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldShutdownServerForInitiallyDestroyedSource() {
|
||||||
|
sourceController.destroy();
|
||||||
|
shadowOf(getMainLooper()).idle();
|
||||||
|
|
||||||
|
LifecycleOnDestroyHelper.shutdownUponDestruction(sourceService.getLifecycle(), mockServer);
|
||||||
|
verify(mockServer).shutdownNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class MyService extends LifecycleService {}
|
||||||
|
}
|
||||||
|
|
@ -191,6 +191,7 @@ subprojects {
|
||||||
guava_testlib: "com.google.guava:guava-testlib:${guavaVersion}",
|
guava_testlib: "com.google.guava:guava-testlib:${guavaVersion}",
|
||||||
androidx_annotation: "androidx.annotation:annotation:1.1.0",
|
androidx_annotation: "androidx.annotation:annotation:1.1.0",
|
||||||
androidx_core: "androidx.core:core:1.3.0",
|
androidx_core: "androidx.core:core:1.3.0",
|
||||||
|
androidx_lifecycle_common: "androidx.lifecycle:lifecycle-common:2.3.0",
|
||||||
androidx_lifecycle_service: "androidx.lifecycle:lifecycle-service:2.3.0",
|
androidx_lifecycle_service: "androidx.lifecycle:lifecycle-service:2.3.0",
|
||||||
androidx_test: "androidx.test:core:1.3.0",
|
androidx_test: "androidx.test:core:1.3.0",
|
||||||
androidx_test_rules: "androidx.test:rules:1.3.0",
|
androidx_test_rules: "androidx.test:rules:1.3.0",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue