Search code examples
androidandroid-5.0-lollipopokhttpuniversal-image-loader

HttpURLConnection failes with NullPointerException on Lollipop


I noticed that sometimes, not sure in which conditions, while downloading an image using Universal-Image-Loader (https://github.com/nostra13/Android-Universal-Image-Loader), I get the following NullPointerException:

E/ImageLoader(27386): Attempt to invoke virtual method 'boolean java.lang.String.startsWith(java.lang.String)' on a null object reference
E/ImageLoader(27386): java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.String.startsWith(java.lang.String)' on a null object reference
E/ImageLoader(27386):   at com.android.okhttp.internal.http.StatusLine.<init>(StatusLine.java:24)
E/ImageLoader(27386):   at com.android.okhttp.Response$Builder.statusLine(Response.java:419)
E/ImageLoader(27386):   at com.android.okhttp.internal.http.JavaApiConverter.createOkResponse(JavaApiConverter.java:116)
E/ImageLoader(27386):   at com.android.okhttp.internal.http.ResponseCacheAdapter.get(ResponseCacheAdapter.java:53)
E/ImageLoader(27386):   at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:269)
E/ImageLoader(27386):   at com.android.okhttp.internal.http.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:373)
E/ImageLoader(27386):   at com.android.okhttp.internal.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:323)
E/ImageLoader(27386):   at com.android.okhttp.internal.http.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:491)
E/ImageLoader(27386):   at com.android.okhttp.internal.http.DelegatingHttpsURLConnection.getResponseCode(DelegatingHttpsURLConnection.java:105)
E/ImageLoader(27386):   at com.android.okhttp.internal.http.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:25)
E/ImageLoader(27386):   at com.nostra13.universalimageloader.core.download.BaseImageDownloader.getStreamFromNetwork(BaseImageDownloader.java:113)
E/ImageLoader(27386):   at com.nostra13.universalimageloader.core.download.BaseImageDownloader.getStream(BaseImageDownloader.java:84)
E/ImageLoader(27386):   at com.nostra13.universalimageloader.core.LoadAndDisplayImageTask.downloadImage(LoadAndDisplayImageTask.java:290)
E/ImageLoader(27386):   at com.nostra13.universalimageloader.core.LoadAndDisplayImageTask.tryCacheImageOnDisk(LoadAndDisplayImageTask.java:273)
E/ImageLoader(27386):   at com.nostra13.universalimageloader.core.LoadAndDisplayImageTask.tryLoadBitmap(LoadAndDisplayImageTask.java:229)
E/ImageLoader(27386):   at com.nostra13.universalimageloader.core.LoadAndDisplayImageTask.run(LoadAndDisplayImageTask.java:135)
E/ImageLoader(27386):   at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
E/ImageLoader(27386):   at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
E/ImageLoader(27386):   at java.lang.Thread.run(Thread.java:818)

This seems like a bug in okhttp, not in Universal-Image-Loader. The code does something like this, and it crashes in the call to getResponseCode:

String encodedUrl = Uri.encode(url, ALLOWED_URI_CHARS);
HttpURLConnection conn = (HttpURLConnection) new URL(encodedUrl).openConnection();
conn.getResponseCode()

I managed to work around this by disabling cache on the connection before calling getResponseCode:

conn.setUseCaches(false);

Does anyone have an idea why this happens and in which exact conditions? Can this be something related to server settings, perhaps something to do with caching?


Solution

  • The cause for this is a custom implementation of ResponseCache. When implementing CacheResponse.getHeaders(), you need to put the status line in the null key of the multimap. See: http://developer.android.com/reference/java/net/CacheResponse.html#getHeaders().

    Failing to do so, causes okhttp to fail with a NullPointerException, which is a bug in okhttp.

    I'm copying the full explanation from the okhttp issue tracker at https://code.google.com/p/android/issues/detail?id=160522#c5, as google code is about to close...


    There's some strangeness in the HttpURLConnection APIs around the status line. When I implemented the ResponseCacheAdapter / JavaApiConverter I did so assuming certain behavior, where the behavior is sometimes poorly specified in the JavaDocs.

    The root cause is that Android assumes that the status line (e.g. the part of the response that indicates the response code, a message) is held as the header with a null key. This is alluded to in the API docs when you look at the header with the index of zero, but is not explicit in the methods that return the Map, or that take a String.

    See here: http://developer.android.com/reference/java/net/URLConnection.html#getHeaderFields()
    And: http://developer.android.com/reference/java/net/CacheResponse.html#getHeaders()

    OkHttp's HttpURLConnection implementation does this, and it has been the case on Android for many releases.

    This code: https://github.com/appcelerator/titanium_mobile/blob/master/android/titanium/src/java/org/appcelerator/titanium/util/TiResponseCache.java#L416

    suggests the cache implementation won't deal properly with a response header with a null key. The header with a null key is being written out as being a header with the key with a string "null", so it is not round-tripped. When the cached headers are deserialized we end up without a header with a null key, which means the OkHttp code is not able to reconstruct the status line from the CacheResponse.