-
Notifications
You must be signed in to change notification settings - Fork 3.9k
android: add AndroidChannelBuilder #4172
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
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?) |
android/build.gradle
Outdated
mavenCentral() | ||
mavenLocal() | ||
maven { | ||
url "https://oss.sonatype.org/content/repositories/snapshots" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we want to pull in the snapshots repo.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed
android/build.gradle
Outdated
|
||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
|
||
/** | ||
* 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need to specify implementation details like okhttp and "backed" and some delegate managedchannel.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
|
||
/** 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
||
private void unregisterNetworkListener() { | ||
if (needToUnregisterListener) { | ||
synchronized (lock) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you need to check needToUnregisterListener again within the lock?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does repeating the Build
check here help dead code elimination? If not, is it necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the review! Comments addressed.
android/build.gradle
Outdated
mavenCentral() | ||
mavenLocal() | ||
maven { | ||
url "https://oss.sonatype.org/content/repositories/snapshots" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed
|
||
/** | ||
* 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
android/build.gradle
Outdated
|
||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
|
||
/** 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
private final Context context; | ||
private final ConnectivityManager connectivityManager; | ||
|
||
private DefaultNetworkCallback defaultNetworkCallback; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
|
||
private void unregisterNetworkListener() { | ||
if (needToUnregisterListener) { | ||
synchronized (lock) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need the literal Class.forName("io.grpc.okhttp.OkHttpChannelBuilder")
, otherwise ProGuard won't detect the reference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
connectivityManager.registerDefaultNetworkCallback(defaultNetworkCallback); | ||
unregisterRunnable = | ||
new Runnable() { | ||
@TargetApi(Build.VERSION_CODES.LOLLIPOP) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to understand these annotations at some point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
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
Context
and registers as a network listener to automatically invoke the necessary methods onManagedChannel
(enterIdle
andresetConnectBackoff
) 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.Proxy
API 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