HttpURLConnection 因 Lollipop 上的 NullPointerException 而失败

HttpURLConnection failes with NullPointerException on Lollipop

我注意到有时,不确定在什么情况下,在使用 Universal-Image-Loader (https://github.com/nostra13/Android-Universal-Image-Loader) 下载图像时,我会收到以下 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)

这似乎是 okhttp 中的错误,而不是 Universal-Image-Loader 中的错误。该代码执行类似这样的操作,并在调用 getResponseCode:

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

我设法通过在调用 getResponseCode:

之前禁用连接缓存来解决这个问题
conn.setUseCaches(false);

有谁知道为什么会发生这种情况以及发生在何种确切条件下?这可能与服务器设置有关,也许与缓存有关?

原因是 ResponseCache 的自定义实现。在实现CacheResponse.getHeaders()时,需要将状态行放在multimap的null key中。参见:http://developer.android.com/reference/java/net/CacheResponse.html#getHeaders().

如果不这样做,会导致 okhttp 失败并出现 NullPointerException,这是 okhttp 中的一个错误。

我正在从 https://code.google.com/p/android/issues/detail?id=160522#c5 的 okhttp 问题跟踪器中复制完整的解释,因为 google 代码即将关闭...


状态行周围的 HttpURLConnection APIs 有一些奇怪之处。当我实现 ResponseCacheAdapter / JavaApiConverter 时,我假设了某些行为,而这些行为有时在 JavaDocs 中没有明确指定。

根本原因是 Android 假定状态行(例如,指示响应代码的响应部分,一条消息)作为带有空键的 header 保存。当您查看索引为零的 header 时,API 文档中提到了这一点,但在 return 地图或采用字符串的方法中并不明确。

看这里:http://developer.android.com/reference/java/net/URLConnection.html#getHeaderFields()
并且:http://developer.android.com/reference/java/net/CacheResponse.html#getHeaders()

OkHttp 的 HttpURLConnection 实现就是这样做的,Android 上的许多版本都是如此。

这段代码: https://github.com/appcelerator/titanium_mobile/blob/master/android/titanium/src/java/org/appcelerator/titanium/util/TiResponseCache.java#L416

表明缓存实现无法正确处理带有空键的响应 header。带有空键的 header 被写为带有字符串 "null" 的键的 header,所以它不是 round-tripped。当缓存的 header 被反序列化时,我们最终没有带有空键的 header,这意味着 OkHttp 代码无法从 CacheResponse 重建状态行。