mirror of https://github.com/grpc/grpc-java.git
android: add AndroidChannelBuilder (#4172)
This commit is contained in:
parent
a47266ea4a
commit
ba86a86c77
|
|
@ -63,6 +63,7 @@ them before continuing, and set them again when resuming.
|
|||
$ MAJOR=1 MINOR=7 PATCH=0 # Set appropriately for new release
|
||||
$ VERSION_FILES=(
|
||||
build.gradle
|
||||
android/build.gradle
|
||||
android-interop-testing/app/build.gradle
|
||||
core/src/main/java/io/grpc/internal/GrpcUtil.java
|
||||
cronet/build.gradle
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
apply plugin: 'com.android.library'
|
||||
|
||||
description = 'gRPC: Android'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
maven {
|
||||
url "https://plugins.gradle.org/m2/"
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.0.1'
|
||||
classpath "net.ltgt.gradle:gradle-errorprone-plugin:0.0.13"
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: "net.ltgt.errorprone"
|
||||
|
||||
android {
|
||||
compileSdkVersion 27
|
||||
defaultConfig {
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 27
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
testOptions {
|
||||
unitTests {
|
||||
includeAndroidResources = true
|
||||
}
|
||||
}
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'io.grpc:grpc-core:1.12.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
testImplementation 'io.grpc:grpc-okhttp:1.12.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
|
||||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation 'org.robolectric:robolectric:3.7.1'
|
||||
testImplementation 'com.google.truth:truth:0.39'
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="io.grpc.android">
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
|
||||
</manifest>
|
||||
|
|
@ -0,0 +1,299 @@
|
|||
/*
|
||||
* Copyright 2018, gRPC Authors All rights reserved.
|
||||
*
|
||||
* 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.android;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkInfo;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import io.grpc.CallOptions;
|
||||
import io.grpc.ClientCall;
|
||||
import io.grpc.ConnectivityState;
|
||||
import io.grpc.ExperimentalApi;
|
||||
import io.grpc.ForwardingChannelBuilder;
|
||||
import io.grpc.ManagedChannel;
|
||||
import io.grpc.ManagedChannelBuilder;
|
||||
import io.grpc.MethodDescriptor;
|
||||
import io.grpc.internal.GrpcUtil;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
|
||||
/**
|
||||
* Builds a {@link ManagedChannel} that, when provided with a {@link Context}, will automatically
|
||||
* monitor the Android device's network state to smoothly handle intermittent network failures.
|
||||
*
|
||||
* <p>Requires the Android ACCESS_NETWORK_STATE permission.
|
||||
*
|
||||
* @since 1.12.0
|
||||
*/
|
||||
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/4056")
|
||||
public final class AndroidChannelBuilder extends ForwardingChannelBuilder<AndroidChannelBuilder> {
|
||||
|
||||
private static final String LOG_TAG = "AndroidChannelBuilder";
|
||||
|
||||
@Nullable private static final Class<?> OKHTTP_CHANNEL_BUILDER_CLASS = findOkHttp();
|
||||
|
||||
private static final Class<?> findOkHttp() {
|
||||
try {
|
||||
return Class.forName("io.grpc.okhttp.OkHttpChannelBuilder");
|
||||
} catch (ClassNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private final ManagedChannelBuilder delegateBuilder;
|
||||
|
||||
@Nullable private Context context;
|
||||
|
||||
public static final AndroidChannelBuilder forTarget(String target) {
|
||||
return new AndroidChannelBuilder(target);
|
||||
}
|
||||
|
||||
public static AndroidChannelBuilder forAddress(String name, int port) {
|
||||
return forTarget(GrpcUtil.authorityFromHostAndPort(name, port));
|
||||
}
|
||||
|
||||
private AndroidChannelBuilder(String target) {
|
||||
if (OKHTTP_CHANNEL_BUILDER_CLASS == null) {
|
||||
throw new UnsupportedOperationException("No ManagedChannelBuilder found on the classpath");
|
||||
}
|
||||
try {
|
||||
delegateBuilder =
|
||||
(ManagedChannelBuilder)
|
||||
OKHTTP_CHANNEL_BUILDER_CLASS
|
||||
.getMethod("forTarget", String.class)
|
||||
.invoke(null, target);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to create ManagedChannelBuilder", e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Enables automatic monitoring of the device's network state. */
|
||||
public AndroidChannelBuilder context(Context context) {
|
||||
this.context = context;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ManagedChannelBuilder<?> delegate() {
|
||||
return delegateBuilder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ManagedChannel build() {
|
||||
return new AndroidChannel(delegateBuilder.build(), context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps an OkHttp channel and handles invoking the appropriate methods (e.g., {@link
|
||||
* ManagedChannel#resetConnectBackoff}) when the device network state changes.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static final class AndroidChannel extends ManagedChannel {
|
||||
|
||||
private final ManagedChannel delegate;
|
||||
|
||||
@Nullable private final Context context;
|
||||
@Nullable private final ConnectivityManager connectivityManager;
|
||||
|
||||
private final Object lock = new Object();
|
||||
|
||||
@GuardedBy("lock")
|
||||
private Runnable unregisterRunnable;
|
||||
|
||||
@VisibleForTesting
|
||||
AndroidChannel(final ManagedChannel delegate, @Nullable Context context) {
|
||||
this.delegate = delegate;
|
||||
this.context = context;
|
||||
|
||||
if (context != null) {
|
||||
connectivityManager =
|
||||
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
configureNetworkMonitoring();
|
||||
} else {
|
||||
connectivityManager = null;
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("lock")
|
||||
private void configureNetworkMonitoring() {
|
||||
// Eagerly check current network state to verify app has required permissions
|
||||
NetworkInfo currentNetwork;
|
||||
try {
|
||||
currentNetwork = connectivityManager.getActiveNetworkInfo();
|
||||
} catch (SecurityException e) {
|
||||
Log.w(
|
||||
LOG_TAG,
|
||||
"Failed to configure network monitoring. Does app have ACCESS_NETWORK_STATE"
|
||||
+ " permission?",
|
||||
e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Android N added the registerDefaultNetworkCallback API to listen to changes in the device's
|
||||
// default network. For earlier Android API levels, use the BroadcastReceiver API.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && connectivityManager != null) {
|
||||
// The connection status may change before registration of the listener is complete, but
|
||||
// this will at worst result in invoking resetConnectBackoff() instead of enterIdle() (or
|
||||
// vice versa) on the first network change.
|
||||
boolean isConnected = currentNetwork != null && currentNetwork.isConnected();
|
||||
|
||||
final DefaultNetworkCallback defaultNetworkCallback =
|
||||
new DefaultNetworkCallback(isConnected);
|
||||
connectivityManager.registerDefaultNetworkCallback(defaultNetworkCallback);
|
||||
unregisterRunnable =
|
||||
new Runnable() {
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
@Override
|
||||
public void run() {
|
||||
connectivityManager.unregisterNetworkCallback(defaultNetworkCallback);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
final NetworkReceiver networkReceiver = new NetworkReceiver();
|
||||
IntentFilter networkIntentFilter =
|
||||
new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
|
||||
context.registerReceiver(networkReceiver, networkIntentFilter);
|
||||
unregisterRunnable =
|
||||
new Runnable() {
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
@Override
|
||||
public void run() {
|
||||
context.unregisterReceiver(networkReceiver);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void unregisterNetworkListener() {
|
||||
synchronized (lock) {
|
||||
if (unregisterRunnable != null) {
|
||||
unregisterRunnable.run();
|
||||
unregisterRunnable = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ManagedChannel shutdown() {
|
||||
unregisterNetworkListener();
|
||||
return delegate.shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShutdown() {
|
||||
return delegate.isShutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTerminated() {
|
||||
return delegate.isTerminated();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ManagedChannel shutdownNow() {
|
||||
unregisterNetworkListener();
|
||||
return delegate.shutdownNow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
|
||||
return delegate.awaitTermination(timeout, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <RequestT, ResponseT> ClientCall<RequestT, ResponseT> newCall(
|
||||
MethodDescriptor<RequestT, ResponseT> methodDescriptor, CallOptions callOptions) {
|
||||
return delegate.newCall(methodDescriptor, callOptions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String authority() {
|
||||
return delegate.authority();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConnectivityState getState(boolean requestConnection) {
|
||||
return delegate.getState(requestConnection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyWhenStateChanged(ConnectivityState source, Runnable callback) {
|
||||
delegate.notifyWhenStateChanged(source, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetConnectBackoff() {
|
||||
delegate.resetConnectBackoff();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enterIdle() {
|
||||
delegate.enterIdle();
|
||||
}
|
||||
|
||||
/** Respond to changes in the default network. Only used on API levels 24+. */
|
||||
@TargetApi(Build.VERSION_CODES.N)
|
||||
private class DefaultNetworkCallback extends ConnectivityManager.NetworkCallback {
|
||||
private boolean isConnected = false;
|
||||
|
||||
private DefaultNetworkCallback(boolean isConnected) {
|
||||
this.isConnected = isConnected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAvailable(Network network) {
|
||||
if (isConnected) {
|
||||
delegate.enterIdle();
|
||||
} else {
|
||||
delegate.resetConnectBackoff();
|
||||
}
|
||||
isConnected = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLost(Network network) {
|
||||
isConnected = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Respond to network changes. Only used on API levels < 24. */
|
||||
private class NetworkReceiver extends BroadcastReceiver {
|
||||
private boolean isConnected = false;
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
ConnectivityManager conn =
|
||||
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
NetworkInfo networkInfo = conn.getActiveNetworkInfo();
|
||||
boolean wasConnected = isConnected;
|
||||
isConnected = networkInfo != null && networkInfo.isConnected();
|
||||
if (isConnected && !wasConnected) {
|
||||
delegate.resetConnectBackoff();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,373 @@
|
|||
/*
|
||||
* Copyright 2018, gRPC Authors All rights reserved.
|
||||
*
|
||||
* 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.android;
|
||||
|
||||
import static android.os.Build.VERSION_CODES.LOLLIPOP;
|
||||
import static android.os.Build.VERSION_CODES.N;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.robolectric.RuntimeEnvironment.getApiLevel;
|
||||
import static org.robolectric.Shadows.shadowOf;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkInfo;
|
||||
import io.grpc.CallOptions;
|
||||
import io.grpc.ClientCall;
|
||||
import io.grpc.ManagedChannel;
|
||||
import io.grpc.MethodDescriptor;
|
||||
import java.util.HashSet;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.annotation.Implementation;
|
||||
import org.robolectric.annotation.Implements;
|
||||
import org.robolectric.shadows.ShadowConnectivityManager;
|
||||
import org.robolectric.shadows.ShadowNetwork;
|
||||
import org.robolectric.shadows.ShadowNetworkInfo;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(shadows = {AndroidChannelBuilderTest.ShadowDefaultNetworkListenerConnectivityManager.class})
|
||||
public final class AndroidChannelBuilderTest {
|
||||
private static final NetworkInfo WIFI_CONNECTED =
|
||||
ShadowNetworkInfo.newInstance(
|
||||
NetworkInfo.DetailedState.CONNECTED, ConnectivityManager.TYPE_WIFI, 0, true, true);
|
||||
private static final NetworkInfo WIFI_DISCONNECTED =
|
||||
ShadowNetworkInfo.newInstance(
|
||||
NetworkInfo.DetailedState.DISCONNECTED, ConnectivityManager.TYPE_WIFI, 0, true, false);
|
||||
private final NetworkInfo MOBILE_CONNECTED =
|
||||
ShadowNetworkInfo.newInstance(
|
||||
NetworkInfo.DetailedState.CONNECTED,
|
||||
ConnectivityManager.TYPE_MOBILE,
|
||||
ConnectivityManager.TYPE_MOBILE_MMS,
|
||||
true,
|
||||
true);
|
||||
private final NetworkInfo MOBILE_DISCONNECTED =
|
||||
ShadowNetworkInfo.newInstance(
|
||||
NetworkInfo.DetailedState.DISCONNECTED,
|
||||
ConnectivityManager.TYPE_MOBILE,
|
||||
ConnectivityManager.TYPE_MOBILE_MMS,
|
||||
true,
|
||||
false);
|
||||
|
||||
private ConnectivityManager connectivityManager;
|
||||
private ShadowConnectivityManager shadowConnectivityManager;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
connectivityManager =
|
||||
(ConnectivityManager)
|
||||
RuntimeEnvironment.application.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
shadowConnectivityManager = shadowOf(connectivityManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void channelBuilderClassFoundReflectively() {
|
||||
// This should not throw with OkHttpChannelBuilder on the classpath
|
||||
AndroidChannelBuilder.forTarget("target");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = 23)
|
||||
public void nullContextDoesNotThrow_api23() {
|
||||
TestChannel delegateChannel = new TestChannel();
|
||||
ManagedChannel androidChannel = new AndroidChannelBuilder.AndroidChannel(delegateChannel, null);
|
||||
|
||||
// Network change and shutdown should be no-op for the channel without an Android Context
|
||||
shadowConnectivityManager.setActiveNetworkInfo(WIFI_CONNECTED);
|
||||
RuntimeEnvironment.application.sendBroadcast(
|
||||
new Intent(ConnectivityManager.CONNECTIVITY_ACTION));
|
||||
androidChannel.shutdown();
|
||||
|
||||
assertThat(delegateChannel.resetCount).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = 24)
|
||||
public void nullContextDoesNotThrow_api24() {
|
||||
shadowConnectivityManager.setActiveNetworkInfo(MOBILE_DISCONNECTED);
|
||||
TestChannel delegateChannel = new TestChannel();
|
||||
ManagedChannel androidChannel = new AndroidChannelBuilder.AndroidChannel(delegateChannel, null);
|
||||
|
||||
// Network change and shutdown should be no-op for the channel without an Android Context
|
||||
shadowConnectivityManager.setActiveNetworkInfo(MOBILE_CONNECTED);
|
||||
androidChannel.shutdown();
|
||||
|
||||
assertThat(delegateChannel.resetCount).isEqualTo(0);
|
||||
assertThat(delegateChannel.enterIdleCount).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = 23)
|
||||
public void resetConnectBackoff_api23() {
|
||||
TestChannel delegateChannel = new TestChannel();
|
||||
ManagedChannel androidChannel =
|
||||
new AndroidChannelBuilder.AndroidChannel(
|
||||
delegateChannel, RuntimeEnvironment.application.getApplicationContext());
|
||||
assertThat(delegateChannel.resetCount).isEqualTo(0);
|
||||
|
||||
// On API levels < 24, the broadcast receiver will invoke resetConnectBackoff() on the first
|
||||
// connectivity action broadcast regardless of previous connection status
|
||||
shadowConnectivityManager.setActiveNetworkInfo(WIFI_CONNECTED);
|
||||
RuntimeEnvironment.application.sendBroadcast(
|
||||
new Intent(ConnectivityManager.CONNECTIVITY_ACTION));
|
||||
assertThat(delegateChannel.resetCount).isEqualTo(1);
|
||||
|
||||
// The broadcast receiver may fire when the active network status has not actually changed
|
||||
RuntimeEnvironment.application.sendBroadcast(
|
||||
new Intent(ConnectivityManager.CONNECTIVITY_ACTION));
|
||||
assertThat(delegateChannel.resetCount).isEqualTo(1);
|
||||
|
||||
// Drop the connection
|
||||
shadowConnectivityManager.setActiveNetworkInfo(null);
|
||||
RuntimeEnvironment.application.sendBroadcast(
|
||||
new Intent(ConnectivityManager.CONNECTIVITY_ACTION));
|
||||
assertThat(delegateChannel.resetCount).isEqualTo(1);
|
||||
|
||||
// Notify that a new but not connected network is available
|
||||
shadowConnectivityManager.setActiveNetworkInfo(MOBILE_DISCONNECTED);
|
||||
RuntimeEnvironment.application.sendBroadcast(
|
||||
new Intent(ConnectivityManager.CONNECTIVITY_ACTION));
|
||||
assertThat(delegateChannel.resetCount).isEqualTo(1);
|
||||
|
||||
// Establish a connection
|
||||
shadowConnectivityManager.setActiveNetworkInfo(MOBILE_CONNECTED);
|
||||
RuntimeEnvironment.application.sendBroadcast(
|
||||
new Intent(ConnectivityManager.CONNECTIVITY_ACTION));
|
||||
assertThat(delegateChannel.resetCount).isEqualTo(2);
|
||||
|
||||
// Disconnect, then shutdown the channel and verify that the broadcast receiver has been
|
||||
// unregistered
|
||||
shadowConnectivityManager.setActiveNetworkInfo(null);
|
||||
RuntimeEnvironment.application.sendBroadcast(
|
||||
new Intent(ConnectivityManager.CONNECTIVITY_ACTION));
|
||||
androidChannel.shutdown();
|
||||
shadowConnectivityManager.setActiveNetworkInfo(MOBILE_CONNECTED);
|
||||
RuntimeEnvironment.application.sendBroadcast(
|
||||
new Intent(ConnectivityManager.CONNECTIVITY_ACTION));
|
||||
|
||||
assertThat(delegateChannel.resetCount).isEqualTo(2);
|
||||
// enterIdle is not called on API levels < 24
|
||||
assertThat(delegateChannel.enterIdleCount).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = 24)
|
||||
public void resetConnectBackoffAndEnterIdle_api24() {
|
||||
shadowConnectivityManager.setActiveNetworkInfo(MOBILE_DISCONNECTED);
|
||||
TestChannel delegateChannel = new TestChannel();
|
||||
ManagedChannel androidChannel =
|
||||
new AndroidChannelBuilder.AndroidChannel(
|
||||
delegateChannel, RuntimeEnvironment.application.getApplicationContext());
|
||||
assertThat(delegateChannel.resetCount).isEqualTo(0);
|
||||
assertThat(delegateChannel.enterIdleCount).isEqualTo(0);
|
||||
|
||||
// Establish an initial network connection
|
||||
shadowConnectivityManager.setActiveNetworkInfo(MOBILE_CONNECTED);
|
||||
assertThat(delegateChannel.resetCount).isEqualTo(1);
|
||||
assertThat(delegateChannel.enterIdleCount).isEqualTo(0);
|
||||
|
||||
// Switch to another network to trigger enterIdle()
|
||||
shadowConnectivityManager.setActiveNetworkInfo(WIFI_CONNECTED);
|
||||
assertThat(delegateChannel.resetCount).isEqualTo(1);
|
||||
assertThat(delegateChannel.enterIdleCount).isEqualTo(1);
|
||||
|
||||
// Switch to an offline network and then to null
|
||||
shadowConnectivityManager.setActiveNetworkInfo(WIFI_DISCONNECTED);
|
||||
shadowConnectivityManager.setActiveNetworkInfo(null);
|
||||
assertThat(delegateChannel.resetCount).isEqualTo(1);
|
||||
assertThat(delegateChannel.enterIdleCount).isEqualTo(1);
|
||||
|
||||
// Establish a connection
|
||||
shadowConnectivityManager.setActiveNetworkInfo(MOBILE_CONNECTED);
|
||||
assertThat(delegateChannel.resetCount).isEqualTo(2);
|
||||
assertThat(delegateChannel.enterIdleCount).isEqualTo(1);
|
||||
|
||||
// Disconnect, then shutdown the channel and verify that the callback has been unregistered
|
||||
shadowConnectivityManager.setActiveNetworkInfo(null);
|
||||
androidChannel.shutdown();
|
||||
shadowConnectivityManager.setActiveNetworkInfo(MOBILE_CONNECTED);
|
||||
|
||||
assertThat(delegateChannel.resetCount).isEqualTo(2);
|
||||
assertThat(delegateChannel.enterIdleCount).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = 24)
|
||||
public void newChannelWithConnection_entersIdleOnConnectionChange_api24() {
|
||||
shadowConnectivityManager.setActiveNetworkInfo(MOBILE_CONNECTED);
|
||||
TestChannel delegateChannel = new TestChannel();
|
||||
ManagedChannel androidChannel =
|
||||
new AndroidChannelBuilder.AndroidChannel(
|
||||
delegateChannel, RuntimeEnvironment.application.getApplicationContext());
|
||||
|
||||
shadowConnectivityManager.setActiveNetworkInfo(WIFI_CONNECTED);
|
||||
assertThat(delegateChannel.resetCount).isEqualTo(0);
|
||||
assertThat(delegateChannel.enterIdleCount).isEqualTo(1);
|
||||
|
||||
androidChannel.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = 23)
|
||||
public void shutdownNowUnregistersBroadcastReceiver_api23() {
|
||||
TestChannel delegateChannel = new TestChannel();
|
||||
ManagedChannel androidChannel =
|
||||
new AndroidChannelBuilder.AndroidChannel(
|
||||
delegateChannel, RuntimeEnvironment.application.getApplicationContext());
|
||||
|
||||
shadowConnectivityManager.setActiveNetworkInfo(null);
|
||||
RuntimeEnvironment.application.sendBroadcast(
|
||||
new Intent(ConnectivityManager.CONNECTIVITY_ACTION));
|
||||
androidChannel.shutdownNow();
|
||||
shadowConnectivityManager.setActiveNetworkInfo(WIFI_CONNECTED);
|
||||
RuntimeEnvironment.application.sendBroadcast(
|
||||
new Intent(ConnectivityManager.CONNECTIVITY_ACTION));
|
||||
|
||||
assertThat(delegateChannel.resetCount).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = 24)
|
||||
public void shutdownNowUnregistersNetworkCallback_api24() {
|
||||
shadowConnectivityManager.setActiveNetworkInfo(null);
|
||||
TestChannel delegateChannel = new TestChannel();
|
||||
ManagedChannel androidChannel =
|
||||
new AndroidChannelBuilder.AndroidChannel(
|
||||
delegateChannel, RuntimeEnvironment.application.getApplicationContext());
|
||||
|
||||
androidChannel.shutdownNow();
|
||||
shadowConnectivityManager.setActiveNetworkInfo(WIFI_CONNECTED);
|
||||
|
||||
assertThat(delegateChannel.resetCount).isEqualTo(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends Robolectric ShadowConnectivityManager to handle Android N's
|
||||
* registerDefaultNetworkCallback API.
|
||||
*/
|
||||
@Implements(value = ConnectivityManager.class)
|
||||
public static class ShadowDefaultNetworkListenerConnectivityManager
|
||||
extends ShadowConnectivityManager {
|
||||
private HashSet<ConnectivityManager.NetworkCallback> defaultNetworkCallbacks = new HashSet<>();
|
||||
|
||||
public ShadowDefaultNetworkListenerConnectivityManager() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setActiveNetworkInfo(NetworkInfo activeNetworkInfo) {
|
||||
if (getApiLevel() >= N) {
|
||||
NetworkInfo previousNetworkInfo = getActiveNetworkInfo();
|
||||
if (activeNetworkInfo != null && activeNetworkInfo.isConnected()) {
|
||||
notifyDefaultNetworkCallbacksOnAvailable(
|
||||
ShadowNetwork.newInstance(activeNetworkInfo.getType() /* use type as network ID */));
|
||||
} else if (previousNetworkInfo != null) {
|
||||
notifyDefaultNetworkCallbacksOnLost(
|
||||
ShadowNetwork.newInstance(
|
||||
previousNetworkInfo.getType() /* use type as network ID */));
|
||||
}
|
||||
}
|
||||
super.setActiveNetworkInfo(activeNetworkInfo);
|
||||
}
|
||||
|
||||
private void notifyDefaultNetworkCallbacksOnAvailable(Network network) {
|
||||
for (ConnectivityManager.NetworkCallback networkCallback : defaultNetworkCallbacks) {
|
||||
networkCallback.onAvailable(network);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyDefaultNetworkCallbacksOnLost(Network network) {
|
||||
for (ConnectivityManager.NetworkCallback networkCallback : defaultNetworkCallbacks) {
|
||||
networkCallback.onLost(network);
|
||||
}
|
||||
}
|
||||
|
||||
@Implementation(minSdk = N)
|
||||
protected void registerDefaultNetworkCallback(
|
||||
ConnectivityManager.NetworkCallback networkCallback) {
|
||||
defaultNetworkCallbacks.add(networkCallback);
|
||||
}
|
||||
|
||||
@Implementation(minSdk = LOLLIPOP)
|
||||
@Override
|
||||
public void unregisterNetworkCallback(ConnectivityManager.NetworkCallback networkCallback) {
|
||||
if (getApiLevel() >= N) {
|
||||
if (networkCallback != null || defaultNetworkCallbacks.contains(networkCallback)) {
|
||||
defaultNetworkCallbacks.remove(networkCallback);
|
||||
}
|
||||
}
|
||||
super.unregisterNetworkCallback(networkCallback);
|
||||
}
|
||||
}
|
||||
|
||||
private static class TestChannel extends ManagedChannel {
|
||||
int resetCount;
|
||||
int enterIdleCount;
|
||||
|
||||
@Override
|
||||
public ManagedChannel shutdown() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShutdown() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTerminated() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ManagedChannel shutdownNow() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <RequestT, ResponseT> ClientCall<RequestT, ResponseT> newCall(
|
||||
MethodDescriptor<RequestT, ResponseT> methodDescriptor, CallOptions callOptions) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String authority() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetConnectBackoff() {
|
||||
resetCount++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enterIdle() {
|
||||
enterIdleCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -23,13 +23,19 @@ ln -s "/tmp/protobuf-${PROTOBUF_VERSION}/$(uname -s)-$(uname -p)" /tmp/protobuf
|
|||
|
||||
./gradlew install
|
||||
|
||||
# Build Cronet
|
||||
# Build grpc-cronet
|
||||
|
||||
pushd cronet
|
||||
./cronet_deps.sh
|
||||
../gradlew build
|
||||
popd
|
||||
|
||||
# Build grpc-android
|
||||
|
||||
pushd android
|
||||
../gradlew build
|
||||
popd
|
||||
|
||||
# Build examples
|
||||
|
||||
cd ./examples/android/clientcache
|
||||
|
|
|
|||
Loading…
Reference in New Issue