Skip to content
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

Add observeDefaultNetwork to ImageLoader #2379

Closed
wants to merge 1 commit into from
Closed

Add observeDefaultNetwork to ImageLoader #2379

wants to merge 1 commit into from

Conversation

daniellevass
Copy link

Hi,

As per the discussion in #2374 - I have added observeDefaultNetwork to the ImageLoader.Builder, and implemented it in the NetworkObserver.

Unsure if it would be better to add it into the Extras or defaults (like networkCachePolicy for example) - as this isn't something I figure many others will change - happy to take your direction if you'd like me to refactor this at all 🙇‍♀️

I have tested these changes fix our specific issue by using mavenLocal and a simple Android Automotive project - I can share more on this if interested.

@colinrtwhite
Copy link
Member

colinrtwhite commented Jul 18, 2024

@daniellevass Are you interested in adding this to 2.x or 3.x? The current stable release is on the 2.x branch. If you're using the 2.x releases I'd add this option to ImageLoaderOptions.

For 3.x I think we're probably better off refactoring NetworkObserver to be public and allow custom implementations since we can make more substantial changes as 3.x is in alpha. That way it'll be more flexible vs. adding a configuration method. This is probably a bit more involved so I'm happy to take it on.

@@ -50,7 +53,12 @@ private class RealNetworkObserver(
val request = NetworkRequest.Builder()
.addCapability(NET_CAPABILITY_INTERNET)
.build()
connectivityManager.registerNetworkCallback(request, networkCallback)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have to also change val isAnyOnline = connectivityManager.allNetworks.any below to only consider the default network?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question - it doesn't look like it.

We had been asked by Google to only make the registerNetworkCallback to registerDefaultNetworkCallback change in our application, and we could then verify network requests using db shell dumpsys connectivity | grep -i package.name. If we got network logs of type TRACK_DEFAULT it meant everything was good and using the correct network to make the requests, but if we received LISTEN type logs it was using the incorrect network. I could verify with our test app that after the change I made, we now only got TRACK_DEFAULT logs when using coil to load images.

I can share more info on how to reproduce on an automotive emulator if you're interested, as it's not straight forward!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, James here. I'm an Android Platform lead at Google for the Connectivity teams. Thanks Danielle for mentioning this change. (and putting it up). I wanted to provide some input in case it is helpful.

Regarding Network.isOnline, what is the purpose of that code? I would assume, it checks to see if any network is online by virtue of naming. However, it doesn't seem to have any connection to the passed in networkCallback in this commented block of code? I see the callback triggers isOnline, however whatever network was passed back via the callback is then ignored by usage of connectivityManager.allNetworks.

Given that, what question is isOnline trying to answer? Here are the two questions I would guess:

  • Is there a network available to me in my context? I.e., is there a network available I can use.
  • Is there any network available at all whether I have access to it or not? I.e., there could be a VPN available with INTERNET capabilities that I don't have access to and can't use, but I would still want to show isOnline as true.

I'm guessing it is the former. Is there a network available that I can use? If so, I will recommend additional changes, but want to confirm before doing so.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tech31 Yep, you're right that isOnline (and more broadly NetworkObserver) is trying to determine if there's an available network with internet that we can use. Though, reading your other comment I think we probably only want to check the default network by default. Essentially we want to know if OkHttpClient.newCall/HttpUrlConnection/HttpClient.request will fail immediately. If so, we use this info to skip querying the network and read from the disk cache.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok cool. Yes, in this case, you should always rely on the default network for the reasons I mentioned above (such as being put on a VPN, etc). Otherwise what you are returned may/will not accurately cover the intended question "do I have access to the internet".

@daniellevass
Copy link
Author

Ah! I didn't think to check there was a 2.x branch - apologies! We'd much prefer this in a 2.x release if that's not too much trouble! I'll close this PR and raise a new one against that branch

Copy link

@tech31 tech31 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for putting up this change!

@@ -50,7 +53,12 @@ private class RealNetworkObserver(
val request = NetworkRequest.Builder()
.addCapability(NET_CAPABILITY_INTERNET)
.build()
connectivityManager.registerNetworkCallback(request, networkCallback)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, James here. I'm an Android Platform lead at Google for the Connectivity teams. Thanks Danielle for mentioning this change. (and putting it up). I wanted to provide some input in case it is helpful.

Regarding Network.isOnline, what is the purpose of that code? I would assume, it checks to see if any network is online by virtue of naming. However, it doesn't seem to have any connection to the passed in networkCallback in this commented block of code? I see the callback triggers isOnline, however whatever network was passed back via the callback is then ignored by usage of connectivityManager.allNetworks.

Given that, what question is isOnline trying to answer? Here are the two questions I would guess:

  • Is there a network available to me in my context? I.e., is there a network available I can use.
  • Is there any network available at all whether I have access to it or not? I.e., there could be a VPN available with INTERNET capabilities that I don't have access to and can't use, but I would still want to show isOnline as true.

I'm guessing it is the former. Is there a network available that I can use? If so, I will recommend additional changes, but want to confirm before doing so.

@@ -50,7 +53,12 @@ private class RealNetworkObserver(
val request = NetworkRequest.Builder()
.addCapability(NET_CAPABILITY_INTERNET)
.build()
connectivityManager.registerNetworkCallback(request, networkCallback)

if (observeDefaultNetwork && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@colinrtwhite I saw in #2374 mentioned to include observeDefaultNetwork in case someone is using a custom SocketFactory. I don't think it would have an impact to the code in this file? They can bind to whatever network they want, but it looks like this code was previously interested in networks with capability INTERNET, which could be different than the network used by the app in any case (such as by a custom SocketFactory), or is this somehow connected to a passed in SocketFactory somewhere I might have missed?

The reason I am asking, is I am wondering if you even need the observeDefaultNetwork capability to begin with. The guidance is to use registerDefaultNetworkCallback whenever possible. One of the main reasons, is when you are put on a restricted default network (e.g. a VPN). If you use registerNetworkCallback, you'll never receive a callback when that happens as it will filter out all restricted networks even though you have access to them (unless you happen to have the restricted network permission which is OEM controlled and very rare). This is one example, there are others (such as OEM usage of per-app networking features in Android).

We actually plan to add clear documentation to avoid using registerNetworkCallback unless you have a very good reason to do so in the very near future to the Android developer portal to help clear things up.

Thanks.

Copy link
Member

@colinrtwhite colinrtwhite Jul 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tech31 Thanks for taking a look! NetworkObserver is a weak point in the codebase as I'm not too familiar with the networking APIs. Please let me know if you see other potential improvements (or feel free to submit a PR)!

in case someone is using a custom SocketFactory

It's possible for a user to provide a custom OkHttpClient to ImageLoader, which could use a custom SocketFactory that performs network requests on a non-default network (as I understand it). It's a bit more clear on the 2.x branch where Coil depends on OkHttp directly. I wanted to err on the side of not changing the default behaviour in the 2.x (stable) branch in case it could be an unexpected change for someone. Though, if you think it's worthwhile we could directly migrate to registerDefaultNetworkCallback.

For a bit of context, Coil uses the NetworkObserver to determine if the device is "offline". If true, it automatically sends all network requests to the disk cache and fails if the image isn't disk cached.

Reading through your comments it sounds like the ideal would be to use registerDefaultNetworkCallback by default and allow users to provide a custom NetworkObserver if they don't use the default network with their OkHttpClient. This change would probably be good to land in the 3.x releases (currently in alpha). What do you think?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading through your comments it sounds like the ideal would be to use registerDefaultNetworkCallback by default and allow users to provide a custom NetworkObserver if they don't use the default network with their OkHttpClient. This change would probably be good to land in the 3.x releases (currently in alpha). What do you think?

Yeah, that sounds reasonable to me from an implementation perspective. Basically, if you want to allow users to customize the types of networks they care about, you would basically do what you are already doing today by using registerNetworkCallback, except that instead of newing up a NetworkRequest in NetworkObserver, you would allow users to pass in a custom NetworkRequest which would then be used. How that would make sense from an implementation detail, e.g. new method on NetworkObserver or having a custom observer would be totally up to you.

One note on the above; I'm guessing that 99%+ of your users would be fine with the default network, so you'll probably want to comment any such functionality that users should only use such methods if they really need to, otherwise to rely on the default behavior. What we see on the Android Connectivity side is usage of registerNetworkCallback unnecessarily, which can have unintended consequences. We plan to update the guidance on the Android developer portal as well in the near future saying as much ("always use the default network unless you know what you're doing and really need something else" to paraphrase).

@tech31
Copy link

tech31 commented Aug 3, 2024 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants