Android 使用 HttpGet 时遇到 IllegalCharsetNameException

发表于:
标签:
编辑 删除

参考:

  1. 《用Apache HttpClient 4.0时强制指定响应的字符编码》- RednaxelaFX

在使用 HttpGet 访问 http://bbs.meizu.cn/archiver 后将页面内容转为字符串时,如下代码:

EntityUtils.toString(response.getEntity());

抛出了异常如下:

IllegalCharsetNameException:

Google 关于此异常的一些文章,明白了原因是 CharsetName 为空导致。可以通过浏览器访问该页面查看 Response 的详细信息如下:

Connection:keep-alive
Content-Encoding:gzip
Content-Type:text/html; charset=
Date:Sat, 01 Dec 2012 02:12:55 GMT
Server:nginx/1.0.2
Transfer-Encoding:chunked
Vary:Accept-Encoding
X-Powered-By:PHP/5.3.2-1ubuntu4.18

上面可以看得到 Content-Type 一行中,charset 确实为空字符串。就导致了 EntityUtils.toString(response.getEntity()) 方法内部抛出了异常。查看 Android 官方文档发现其实该方法是可以传入第二个参数 defaultEncoding,于是改写为:

EntityUtils.toString(response.getEntity(), "UTF-8");

运行后还是抛出了同样的错误。继续 Google 发现了这篇文章 《用Apache HttpClient 4.0时强制指定响应的字符编码》,和我遇到的问题是一类,作者对于在 EntityUtils.toString() 中即使指定了编码但还是没有被使用做了如下解释。

原本要调用的那个HTTP服务返回的响应的头里面没有Content-Type,所以这样去使用EntityUtils.toString(entity, defaultCharset)就已经可以达到指定解析响应内容时使用的字符编码的目的了。
问题是那个HTTP服务现在带上了错误的Content-Type,而EntityUtils.toString(entity, defaultCharset)认为Content-Type中的charset比defaultCharset更优先,此时上面的脚本就达不到强制指定字符编码的目的了。

这样确实能说的通了,但还是想看看源码确实,Google 'EntityUtils source code',很方便找到了源码页面,看看 toString 方法的实现部分如下:

Charset charset = null;
try {
    ContentType contentType = ContentType.getOrDefault(entity);
    charset = contentType.getCharset();
} catch (UnsupportedCharsetException ex) {
    throw new UnsupportedEncodingException(ex.getMessage());
}
if (charset == null) {
    charset = defaultCharset;
}
if (charset == null) {
    charset = HTTP.DEF_CONTENT_CHARSET;
}

可以看的出,其实作为第二个参数传入的 defaultCharset 只有在 charset 为 null 的时候才会生效,但是我们的 charset 为空子符,并不为 null,所以即使制定 defaultCharset,依然没用。

现在只能抛弃 EntityUtils.toString() 方法,和上面 Iteye 文章中指出的一样,先从 response 中获取 byte 数组,然后 new String() 时指定编码:

new String(EntityUtils.toByteArray(response.getEntity()), "UTF-8");

测试 OK!