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_core
|
||||
implementation libraries.androidx_lifecycle_common
|
||||
implementation libraries.guava
|
||||
testImplementation libraries.androidx_core
|
||||
testImplementation libraries.androidx_test
|
||||
testImplementation libraries.androidx_lifecycle_common
|
||||
testImplementation libraries.androidx_lifecycle_service
|
||||
testImplementation libraries.junit
|
||||
testImplementation libraries.mockito
|
||||
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}",
|
||||
androidx_annotation: "androidx.annotation:annotation:1.1.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_test: "androidx.test:core:1.3.0",
|
||||
androidx_test_rules: "androidx.test:rules:1.3.0",
|
||||
|
|
Loading…
Reference in New Issue