Skip to content

Commit d668b24

Browse files
committed
android: add AndroidChannelBuilder
1 parent d45e1ab commit d668b24

File tree

3 files changed

+257
-0
lines changed

3 files changed

+257
-0
lines changed

android/build.gradle

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
apply plugin: 'com.android.library'
2+
3+
description = 'gRPC: Android'
4+
5+
buildscript {
6+
repositories {
7+
google()
8+
jcenter()
9+
mavenCentral()
10+
}
11+
dependencies {
12+
// TODO(ericgribkoff) Update this. Newer versions cause Gradle errors if build grpc-android
13+
// as a subproject.
14+
classpath 'com.android.tools.build:gradle:2.3.0'
15+
}
16+
}
17+
18+
android {
19+
buildToolsVersion "25.0.2"
20+
compileSdkVersion 27
21+
defaultConfig {
22+
minSdkVersion 14
23+
targetSdkVersion 27
24+
versionCode 1
25+
versionName "1.0"
26+
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
27+
}
28+
lintOptions {
29+
abortOnError false
30+
}
31+
}
32+
33+
repositories {
34+
mavenCentral()
35+
mavenLocal()
36+
}
37+
38+
dependencies {
39+
compile 'io.grpc:grpc-core:1.11.0-SNAPSHOT' // CURRENT_GRPC_VERSION
40+
compile 'io.grpc:grpc-okhttp:1.11.0-SNAPSHOT' // CURRENT_GRPC_VERSION
41+
}

android/src/main/AndroidManifest.xml

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
2+
package="io.grpc.android" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
package io.grpc.android;
2+
3+
import android.content.BroadcastReceiver;
4+
import android.content.Context;
5+
import android.content.Intent;
6+
import android.content.IntentFilter;
7+
import android.net.ConnectivityManager;
8+
import android.net.Network;
9+
import android.net.NetworkInfo;
10+
import android.os.Build;
11+
import io.grpc.CallOptions;
12+
import io.grpc.ClientCall;
13+
import io.grpc.ConnectivityState;
14+
import io.grpc.ExperimentalApi;
15+
import io.grpc.ForwardingChannelBuilder;
16+
import io.grpc.ManagedChannel;
17+
import io.grpc.ManagedChannelBuilder;
18+
import io.grpc.MethodDescriptor;
19+
import io.grpc.internal.GrpcUtil;
20+
import io.grpc.okhttp.OkHttpChannelBuilder;
21+
import java.util.concurrent.TimeUnit;
22+
23+
/**
24+
* Builds a {@link ManagedChannel} that automatically monitors the Android device's network state.
25+
* Network changes are used to update the connectivity state of the underlying OkHttp-backed
26+
* {@ManagedChannel} to smoothly handle intermittent network failures.
27+
*
28+
* <p>gRPC Cronet users should use {@code CronetChannelBuilder} directly, as Cronet itself monitors
29+
* the device network state.
30+
*
31+
* <p>Requires the Android ACCESS_NETWORK_STATE permission.
32+
*/
33+
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/4056")
34+
public final class AndroidChannelBuilder extends ForwardingChannelBuilder<AndroidChannelBuilder> {
35+
36+
private final ManagedChannelBuilder delegateBuilder;
37+
private final Context context;
38+
39+
/** Always fails. Call {@link #forAddress(String, int, Context)} instead. */
40+
public static AndroidChannelBuilder forTarget(String target) {
41+
throw new UnsupportedOperationException("call forTarget(String, Context) instead");
42+
}
43+
44+
/** Always fails. Call {@link #forAddress(String, int, Context)} instead. */
45+
public static AndroidChannelBuilder forAddress(String name, int port) {
46+
throw new UnsupportedOperationException("call forAddress(String, int, Context) instead");
47+
}
48+
49+
/** Creates a new builder for the given target and Android context. */
50+
public static final AndroidChannelBuilder forTarget(String target, Context context) {
51+
return new AndroidChannelBuilder(target, context);
52+
}
53+
54+
/** Creates a new builder for the given host, port, and Android context. */
55+
public static AndroidChannelBuilder forAddress(String name, int port, Context context) {
56+
return forTarget(GrpcUtil.authorityFromHostAndPort(name, port), context);
57+
}
58+
59+
private AndroidChannelBuilder(String target, Context context) {
60+
delegateBuilder = OkHttpChannelBuilder.forTarget(target);
61+
this.context = context;
62+
}
63+
64+
@Override
65+
protected ManagedChannelBuilder<?> delegate() {
66+
return delegateBuilder;
67+
}
68+
69+
@Override
70+
public ManagedChannel build() {
71+
return new AndroidChannel(delegateBuilder.build(), context);
72+
}
73+
74+
/**
75+
* Wraps an OkHttp channel and handles invoking the appropriate methods (e.g., {@link
76+
* ManagedChannel#resetConnectBackoff}) when the device network state changes.
77+
*/
78+
private static final class AndroidChannel extends ManagedChannel {
79+
80+
private final ManagedChannel delegate;
81+
private final Context context;
82+
private final NetworkReceiver networkReceiver;
83+
private final IntentFilter networkIntentFilter;
84+
private final ConnectivityManager connectivityManager;
85+
private DefaultNetworkCallback defaultNetworkCallback;
86+
87+
private AndroidChannel(final ManagedChannel delegate, Context context) {
88+
this.delegate = delegate;
89+
this.context = context;
90+
networkReceiver = new NetworkReceiver();
91+
networkIntentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
92+
connectivityManager =
93+
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
94+
95+
// Android N added the registerDefaultNetworkCallback API to listen to changes in the device's
96+
// default network. For earlier Android API levels, use the BroadcastReceiver API.
97+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
98+
defaultNetworkCallback =
99+
new DefaultNetworkCallback(connectivityManager.getActiveNetworkInfo());
100+
connectivityManager.registerDefaultNetworkCallback(defaultNetworkCallback);
101+
} else {
102+
context.registerReceiver(networkReceiver, networkIntentFilter);
103+
}
104+
}
105+
106+
@Override
107+
public ManagedChannel shutdown() {
108+
unregisterNetworkListener();
109+
return delegate.shutdown();
110+
}
111+
112+
@Override
113+
public boolean isShutdown() {
114+
return delegate.isShutdown();
115+
}
116+
117+
@Override
118+
public boolean isTerminated() {
119+
return delegate.isTerminated();
120+
}
121+
122+
@Override
123+
public ManagedChannel shutdownNow() {
124+
unregisterNetworkListener();
125+
return delegate.shutdownNow();
126+
}
127+
128+
@Override
129+
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
130+
return delegate.awaitTermination(timeout, unit);
131+
}
132+
133+
@Override
134+
public <RequestT, ResponseT> ClientCall<RequestT, ResponseT> newCall(
135+
MethodDescriptor<RequestT, ResponseT> methodDescriptor, CallOptions callOptions) {
136+
return delegate.newCall(methodDescriptor, callOptions);
137+
}
138+
139+
@Override
140+
public String authority() {
141+
return delegate.authority();
142+
}
143+
144+
@Override
145+
public ConnectivityState getState(boolean requestConnection) {
146+
return delegate.getState(requestConnection);
147+
}
148+
149+
@Override
150+
public void notifyWhenStateChanged(ConnectivityState source, Runnable callback) {
151+
delegate.notifyWhenStateChanged(source, callback);
152+
}
153+
154+
@Override
155+
public void resetConnectBackoff() {
156+
delegate.resetConnectBackoff();
157+
}
158+
159+
@Override
160+
public void prepareToLoseNetwork() {
161+
delegate.prepareToLoseNetwork();
162+
}
163+
164+
private void unregisterNetworkListener() {
165+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
166+
connectivityManager.unregisterNetworkCallback(defaultNetworkCallback);
167+
} else {
168+
context.unregisterReceiver(networkReceiver);
169+
}
170+
}
171+
172+
/** Respond to changes in the default network. Only used on API levels 24+. */
173+
private class DefaultNetworkCallback extends ConnectivityManager.NetworkCallback {
174+
175+
private boolean isConnected;
176+
177+
DefaultNetworkCallback(NetworkInfo currentNetwork) {
178+
isConnected = currentNetwork != null && currentNetwork.isConnected();
179+
}
180+
181+
@Override
182+
public void onAvailable(Network network) {
183+
if (isConnected) {
184+
delegate.prepareToLoseNetwork();
185+
} else {
186+
delegate.resetConnectBackoff();
187+
}
188+
isConnected = true;
189+
}
190+
191+
@Override
192+
public void onLost(Network network) {
193+
isConnected = false;
194+
}
195+
}
196+
197+
/** Respond to network changes. Only used on API levels < 24. */
198+
private class NetworkReceiver extends BroadcastReceiver {
199+
private boolean isConnected = true;
200+
201+
@Override
202+
public void onReceive(Context context, Intent intent) {
203+
ConnectivityManager conn =
204+
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
205+
NetworkInfo networkInfo = conn.getActiveNetworkInfo();
206+
boolean connected = networkInfo != null && networkInfo.isConnected();
207+
if (connected && !isConnected) {
208+
delegate.resetConnectBackoff();
209+
}
210+
isConnected = connected;
211+
}
212+
}
213+
}
214+
}

0 commit comments

Comments
 (0)