android: add AndroidChannelBuilder#4172
Conversation
54a580d to
d668b24
Compare
d668b24 to
64a8fce
Compare
|
I revised the The tests cover both API levels before and after Android 24, which added the Devices with a pre-24 OS fallback to the older |
|
@ejona86 Friendly ping (branch cut for v1.12 is in five days, looks like this is definitely at risk for making the cut now?) |
| mavenCentral() | ||
| mavenLocal() | ||
| maven { | ||
| url "https://oss.sonatype.org/content/repositories/snapshots" |
There was a problem hiding this comment.
I don't think we want to pull in the snapshots repo.
|
|
||
| dependencies { | ||
| implementation 'io.grpc:grpc-core:1.12.0-SNAPSHOT' // CURRENT_GRPC_VERSION | ||
| implementation 'io.grpc:grpc-okhttp:1.12.0-SNAPSHOT' // CURRENT_GRPC_VERSION |
There was a problem hiding this comment.
I think I'd prefer to leave off the grpc-okhttp dependency for now, as it would be hard to remove to support mixing with cronet.
|
|
||
| /** | ||
| * Builds a {@link ManagedChannel} that automatically monitors the Android device's network state. | ||
| * Network changes are propagated to the underlying OkHttp-backed {@ManagedChannel} to smoothly |
There was a problem hiding this comment.
I don't think we need to specify implementation details like okhttp and "backed" and some delegate managedchannel.
|
|
||
| /** Always fails. Call {@link #forAddress(String, int, Context)} instead. */ | ||
| public static AndroidChannelBuilder forTarget(String target) { | ||
| throw new UnsupportedOperationException("call forTarget(String, Context) instead"); |
There was a problem hiding this comment.
As discussed in the API review meeting, these could be supported and we'd remove the Context from the forTarget call.
There was a problem hiding this comment.
Done. Context is now optional and set via the context() builder method.
|
|
||
| // 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) { |
There was a problem hiding this comment.
Does this work for libraries? I thought many of these fields only work for applications, as when the library is compiled the final value isn't known and nothing rebuilds the library.
|
|
||
| private void unregisterNetworkListener() { | ||
| if (needToUnregisterListener) { | ||
| synchronized (lock) { |
There was a problem hiding this comment.
Do you need to check needToUnregisterListener again within the lock?
There was a problem hiding this comment.
Yes, good catch - fixed by switch to guardedby
| private final Object lock = new Object(); | ||
|
|
||
| // May only go from true to false, and lock must be held when assigning this | ||
| private volatile boolean needToUnregisterListener = true; |
There was a problem hiding this comment.
I'd suggest making this just @GuardedBy("lock"). It's only going to be rarely used, because shutdown isn't called frequently. Little benefit in optimizing the avoidance of the lock.
| private void unregisterNetworkListener() { | ||
| if (needToUnregisterListener) { | ||
| synchronized (lock) { | ||
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && connectivityManager != null) { |
There was a problem hiding this comment.
Does repeating the Build check here help dead code elimination? If not, is it necessary?
There was a problem hiding this comment.
Refactored to use runnable instead of this conditional
| private final Context context; | ||
| private final ConnectivityManager connectivityManager; | ||
|
|
||
| private DefaultNetworkCallback defaultNetworkCallback; |
There was a problem hiding this comment.
Since these are only used for shut down, you could consider just storing a Runnable for clean-up. It's fine as-is though.
(I'm mainly considering it because of the Build.VERSION.SDK_INT >= Build.VERSION_CODES.N check during shut down)
ericgribkoff
left a comment
There was a problem hiding this comment.
Thanks for the review! Comments addressed.
| mavenCentral() | ||
| mavenLocal() | ||
| maven { | ||
| url "https://oss.sonatype.org/content/repositories/snapshots" |
|
|
||
| /** | ||
| * Builds a {@link ManagedChannel} that automatically monitors the Android device's network state. | ||
| * Network changes are propagated to the underlying OkHttp-backed {@ManagedChannel} to smoothly |
|
|
||
| dependencies { | ||
| implementation 'io.grpc:grpc-core:1.12.0-SNAPSHOT' // CURRENT_GRPC_VERSION | ||
| implementation 'io.grpc:grpc-okhttp:1.12.0-SNAPSHOT' // CURRENT_GRPC_VERSION |
|
|
||
| /** Always fails. Call {@link #forAddress(String, int, Context)} instead. */ | ||
| public static AndroidChannelBuilder forTarget(String target) { | ||
| throw new UnsupportedOperationException("call forTarget(String, Context) instead"); |
There was a problem hiding this comment.
Done. Context is now optional and set via the context() builder method.
|
|
||
| // 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) { |
| private final Object lock = new Object(); | ||
|
|
||
| // May only go from true to false, and lock must be held when assigning this | ||
| private volatile boolean needToUnregisterListener = true; |
| private final Context context; | ||
| private final ConnectivityManager connectivityManager; | ||
|
|
||
| private DefaultNetworkCallback defaultNetworkCallback; |
|
|
||
| private void unregisterNetworkListener() { | ||
| if (needToUnregisterListener) { | ||
| synchronized (lock) { |
There was a problem hiding this comment.
Yes, good catch - fixed by switch to guardedby
| private void unregisterNetworkListener() { | ||
| if (needToUnregisterListener) { | ||
| synchronized (lock) { | ||
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && connectivityManager != null) { |
There was a problem hiding this comment.
Refactored to use runnable instead of this conditional
69b49bf to
4638d70
Compare
|
|
||
| private static final Class<?> findClass(String className) { | ||
| try { | ||
| return Class.forName(className); |
There was a problem hiding this comment.
You need the literal Class.forName("io.grpc.okhttp.OkHttpChannelBuilder"), otherwise ProGuard won't detect the reference.
| connectivityManager.registerDefaultNetworkCallback(defaultNetworkCallback); | ||
| unregisterRunnable = | ||
| new Runnable() { | ||
| @TargetApi(Build.VERSION_CODES.LOLLIPOP) |
There was a problem hiding this comment.
I'd like to understand these annotations at some point.
There was a problem hiding this comment.
Analogous to SuppressWarnings. Without it there are linter errors about unregisterNetworkCallback requiring Android L in the runnable.
| @Nullable private final Context context; | ||
| @Nullable private final ConnectivityManager connectivityManager; | ||
|
|
||
| @Nullable private DefaultNetworkCallback defaultNetworkCallback; |
There was a problem hiding this comment.
I had sort of imagined these two fields would just be local variables, since the Runnables could just get a closure of them. This is fine too.
4f008e4 to
d72570b
Compare
d72570b to
6a928f4
Compare
|
@ejona86 Made a few small changes to address comments and catch the security exception thrown for apps that don't have the ACCESS_NETWORK_STATE permission. This should be ready to merge now, PTAL |
Introduces a wrapper around an OkHttp channel that accepts an Android
Contextand registers as a network listener to automatically invoke the necessary methods onManagedChannel(enterIdleandresetConnectBackoff) when the device connection state changes.This will have to be a new artifact with a direct Android dependency: interacting with the Android network monitoring APIs in the existing OkHttp package via reflection could work, but we need to provide the connectivity manager with implementations of Android callback interfaces, which would require using the
java.lang.reflect.ProxyAPI to construct these reflectively. It seems much cleaner to just have (yet one more) new artifact for Android-specific functionality.Manually tested with a local app. Robolectric tests to follow. This reduces the burden on users to take advantage of the gRPC channel API methods: this can all be accomplished with gRPC's public API, but doing so requires users to (a) keep track of every channel reference and (b) understand the Android network monitoring APIs, which have changed over time, and how to plumb these changes to the respective gRPC channel methods.
cc @srini100