Skip to content

Commit 54a580d

Browse files
committed
android: add AndroidChannelBuilder
1 parent d45e1ab commit 54a580d

File tree

3 files changed

+255
-0
lines changed

3 files changed

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

0 commit comments

Comments
 (0)