NEW Game developers: check out PacketZoom Mobile Connect
Date By Leanid Vouk Tags Android / http / performance / reliability / networking

One of our previous blog posts about android http libraries gained quite a bit of an interest from developer community. Encouraged we thought it might be a good idea to expand it more into in-depth series of posts on networking performance.

Hello

You as a developer might be already using one of popular http libs out there like OkHttp, Retrofit, Volley, Fresco... the list goes on. Main advantage of these libraries is they make many complicated tasks much simpler. For instance, make an asynchronous API call and convert JSON response into model object with using just one line of code. This is all cool stuff and greatly simplifies life by reducing boilerplate code, leaves less room for the bugs and lets you focus more on a product itself.

There's no such thing as a free lunch though. Everything has it's cost so simplicity usually comes with the price of not understanding what happens under the hood and having less control over those things. Take for example accepting default parameters (local cache, connect timeout, read timeout, write timeout, max connections per host, max concurrent requests) for all your requests. It's simple and it works. But by their very nature, these defaults are "best guesses" hardcoded into the code by the library's developers. That means optimizing for the "common" case at the cost of suboptimal behaviour in many other cases. Unfortunately when it comes to mobile networks, the common case fails us on a very frequent basis.

Here's a TL;DR of this post. We are announcing a completely free service to automatically and dynamically select an optimal set of parameters for your OKHttp and other similar libraries. To get the full story on how these parameters affect your users' experience, read on.

Timeouts

Here is gist which shows all default parameters in OkHttp

As you can see read timeout, connect timeout, write timeout are all set to 10 seconds regardless of network type, carrier, country, time of day etc. This one size fits all approach can be catastrophic for user experience. For example read timeout on 2G network should be much longer than on fast wifi network otherwise your requests will be failing with

java.net.SocketTimeoutException: Read timed out

Luckily you can still configure OkHttpClient to put values you want:

Things ain't looking any better with default HttpURLConnection API, here is what official documentation says about default connect timeout:

   /**
    * Sets the maximum time in milliseconds to wait while connecting.
    * Connecting to a server will fail with a {@link SocketTimeoutException} if
    * the timeout elapses before a connection is established. The default value
    * of {@code 0} causes us to do a blocking connect. This does not mean we
    * will never time out, but it probably means you'll get a TCP timeout
    * after several minutes.
    * @throws IllegalArgumentException if {@code timeoutMillis < 0}.
    */
    void setConnectTimeout (int timeoutMillis);

Blocking for Several minutes on a network call is not something thats makes your app great and your users happy. Ten seconds from previous example seemed definitely better than several minutes. Still in fast wifi/lte networks where round-trip times are measured in tens of milliseconds it's an overkill to wait 10 seconds to conclude that connection has failed, you're better off trying to retry in a just a few seconds

Now assume you have an app with worldwide distribution working across all possible network types, what values you put there ? At the very least you have to subscribe to ConnectivityManager network change events to apply proper set of values for given network type (LTE/HSPA/UMTS/WCDMA etc.) You'd have to keep this set of values somewhere hardcoded in your app or in persistent storage. Keep in mind that actual characteristics of a network vary not just by network type but also by network carrier, location, time of day etc. Timeouts that work well for Verizon in the US may not work so well on Airtel in India. Heck, they may not even work well on Sprint in the US.

And timeouts are not the only default parameter you have to worry about.

Concurrency

Lets say you have thumbnails view with infinite scrolling in your app. You might be using thread pool either explicitly in your code or implicitly within your http library to process image requests in parallel. It might be a good idea to set maximum number of parallel requests or thread pool size in order to efficiently utilize available bandwidth. (This is because of the nature of TCP's legacy bandwidth discovery algorithms... more about that here.)

It's easy to pick any number of threads however it's possible that even a single download may fill up the 2G or 3G bandwidth. In contrast for fast LTE connection, for a set of smallish images, you might want to run 10 requests in parallel to truly take advantage of the available bandwidth.

What modern http libraries offer you is again the same old one size fits all approach.

Volley limits networking thread pool with 4 threads

/** Number of network request dispatcher threads to start. */
private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;

While OkHttp uses Integer.MAX_VALUE as max thread pool size

It has a default limit of requests in flight.

public final class Dispatcher {
  private int maxRequests = 64;
  private int maxRequestsPerHost = 5;
  private Runnable idleCallback;

With OkHttp you can at least change maxRequestsPerHost and maxRequests.

It's also possible to set maxConnections per host for HttpURLConnection, Apache http client with setting system property:

System.setProperty("http.maxConnections")

Again those numbers needs to be carefully picked depending on a networking situation.

Cache

Another important technique is to avoid downloading duplicate data. You can do this by aggressive caching. Always cache static resources, including on-demand downloads such as full size images, for as long as reasonably possible. On-demand resources should be stored separately to enable you to regularly flush your on-demand cache to manage its size.

Android 4.0 added a response cache to HttpURLConnection. You can enable HTTP response caching on supported devices using reflection as follows:

private void enableHttpResponseCache() {
  try {
    long httpCacheSize = 10 * 1024 * 1024; // 10 MiB
    File httpCacheDir = new File(getCacheDir(), "http");
    Class.forName("android.net.http.HttpResponseCache")
         .getMethod("install", File.class, long.class)
         .invoke(null, httpCacheDir, httpCacheSize);
  } catch (Exception httpResponseCacheNotAvailable) {
    Log.d(TAG, "HTTP response cache is unavailable.");
  }
}

For OkHttp this would look like:

private final OkHttpClient client;

public CacheResponse(File cacheDirectory) throws Exception {
  int cacheSize = 10 * 1024 * 1024; // 10 MiB
  Cache cache = new Cache(cacheDirectory, cacheSize);

  client = new OkHttpClient.Builder()
      .cache(cache)
      .build();
}

Picasso as another popular http imaging library uses two levels of cache: it will always try to load the image from the memory cache first. If the image was not requested recently, and consequently the image is not in memory cache, Picasso will check the disk cache next (with OkHttpClient underneath). If it's not available on disk, only then Picasso will start the network request.

Wrap up

To summarize everything said above - default configuration of your http client won't give you optimal performance. Tweaking timeouts, concurrency and cache for particular network type should help but brings extra overhead and complexity to your code. We at PacketZoom think that we can help developers finding right balance between performance and complexity by bringing our best in class mobile sdk and cloud service. By processing billions requests a day and collecting dozen of data points per request we utilize that knowledge to give you optimal http configuration at any location, network type or carrier whichever your customers may experience at the moment...

Another useful links

  1. Introducing http optimizer and analytics service https://www.packetzoom.com/blog/introducing-http-optimizer-and-analytics-service.html
  2. Herding HTTP Requests, or Why Your Keep-Alive Connection May Already Be Dead https://mttkay.github.io/blog/2013/03/02/herding-http-requests-or-why-your-keep-alive-connection-may-be-dead/
  3. AsyncTask Threading Regression Confirmed https://commonsware.com/blog/2012/04/20/asynctask-threading-regression-confirmed.html

Share On:


Comments

comments powered by Disqus