访问Tomcat服务器返回数据乱码
Posted 第二人格—影
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了访问Tomcat服务器返回数据乱码相关的知识,希望对你有一定的参考价值。
前序:
在网络中,数据的传输,最常用的格式有两种:XML和JSON 。
今天在做一个app版本更新检查。流程是:
1、Andriod客户端 向 Tomcat服务器 发起Http请求。
2、服务器响应并返回数据。返回的数据中,包含了新版app的特性和更新内容。并通过一个Dialog 对话框的形式,来告知用户,新版的app作了那些方面的改进。也就是调用dialog.setMessage()来设置消息内容,结果发现全是乱码。
3、之前一直没遇到这种情况,后来在QQ群了问了才知道,原来这个涉及到了编码的问题。
客户端和服务端发送数据的过程:
先贴个Tomcat webapps目录底下的升级文件内容图:
这个文件,我是直接在桌面新建一个txt文档,然后强制修改文件类型为json的。之后用记事本打开,在里面编写内容。最后经过个人验证,这种做法是很有问题的,也是埋下乱码的一个伏笔。
再贴个乱码的图:
一、先梳理一下:客户端访问网络的流程:
首先一定要清楚的一点:网络传递的是字节流,所以从服务器到android的转换过程如下:
在分析这个图之前。我们先来,理一理app访问网络的一个思路:
1、客户端使用 HttpURLConnection 向服务器请求响应。
2、服务器接收到请求后,响应请求,并返回数据。
3、客户端接收到数据。此时,客户端一般都是接受到一个InputStream对象(输入流)。
4、用InputStream 生产各种对象,最后转成一个String 对象,也就是一个字符串。然后开始对这个字符串进行解析(这时候 XML和JSON 这两个格式 的解析方法 就派上用场了)。
5、数据解析完成后,各个变量拿到自己的对象后。各干各的去了。
但是,大家有没有想过,我们拿到的数据,是用什么编码方式得出了的数据?这也是为什么会出现乱码的关键点了。
需要再清楚的一点:
1、在进行XML 或者JSON 数据解析之前,我们能利用的资源,有且只有一个,也就是服务器返的唯一的一样东西—-InputStream(输入流)。
2、 我们拿到这个输入流之后,经过一连串处理,得到一个字符串。具体怎么处理,等下看代码就行了。
3、 接着根据字符串的格式,xml格式?还是Json格式?进行数据解析。然后拿到我们想要的东西。
好了,废话不多说,我们还是来贴代码吧。
有人说中文是最美的语言,但我认为是最操蛋的语言。因为无论你说什么,都他妈的可以有各种意思!
这里给大家提供一个比较标准的Android访问网络的代码:
private void sendRequestWithHttpURLConnection() {
//访问网络,首要就是--开启子线程。
new Thread(new Runnable() {
@Override
public void run() {
//下面4个变量定义在try catch块外面,是因为如果定义在try里面,finally里面就拿不到变量了,
//关闭不了对象,会造成内存泄露。
HttpURLConnection connection = null;
InputStream is = null;
BufferedReader buffer = null;
String result = null;
try {
//这里我访问的是我Tomcat的服务器数据。
URL url = new URL("http://1r667695p8.iok.la:37179/mydata/get_data.json");
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
is = connection.getInputStream();
buffer = new BufferedReader(new InputStreamReader(is));
StringBuilder response = new StringBuilder();
String line;
//打开连接后,子线程会循环读buffer的数据,直到null为止。不符合条件,才会往下执行。
while ((line = buffer.readLine()) != null) {
response.append(line);
}
//转换为字符串
result = response.toString();
//字符串解析(用什么方法解析,主要看:字符串是什么格式 xml 还是json格式)
parseJSONWithJSONObject(result);
} catch (Exception e) {
Log.d("异常捕获", "http post error");
} finally {
//这几个一定要关闭,否则会造成内存泄漏。
if (buffer != null) {
try {
buffer.close();
} catch (IOException ignored) {
}
}
if (is != null) {
try {
is.close();
} catch (IOException ignored) {
}
}
if (connection != null) {
connection.disconnect();
}
}
}
}).start();
}
上面的代码,估计大家都很熟悉。但是不知道大家有没有想过这几行代码:
从14行到18行:
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod(“GET”);
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
is = connection.getInputStream();
我们一起来看一看,14行代码打开一个连接(connection),接着设置一些参数,18行就去接收输入流了。问题来了,我们都知道,程序都是一行一行执行的。而访问网络,肯定也是需要时间的。你凭什么在18行,就可以把输入流赋值给is变量?也就是说CPU执行到
is = connection.getInputStream();
的时候,按照函数的返回值的思路,我们都认为,它只是一个执行connection.getInputStream()完成后的一个返回值,但是其实,is 相当于一个盒子,connection.getInputStream()应该是启动了某个操作,这个操作则不断的去获取数据,直到数据没有了。线程只是启动这个操作,之后就不管它了,线程接着往下走。
is = connection.getInputStream();
//这行代码是先执行getInputStream()这个方法,应该是开启了一个线程(有待验证!)
//数据解析,代码很简单。
private void parseJSONWithJSONObject(String result) {
try {
JSONObject obj = new JSONObject(result);
String apkUrl = obj.getString("url"); //APK下载路径
String updateMessage = obj.getString("updateMessage");//版本更新说明
int apkCode = obj.getInt("versionCode"); //新版APK对于的版本号
//取得已经安装在手机的APP的版本号 versionCode
int versionCode = getCurrentVersionCode();
//对比版本号判断是否需要更新
if (apkCode > versionCode) {
showDialog(updateMessage, apkUrl);
}
} catch (JSONException e) {
LogX.d(TAG, "parse json error");
}
}
对话框的方法,下图
private void showDialog(String content, final String downloadUrl) {
//字符串 content 可能需要先转码
AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
builder.setTitle(R.string.dialog_choose_update_title);
builder.setMessage(content)
.setPositiveButton(R.string.dialog_btn_confirm_download, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
//下载apk文件
goToDownloadApk(downloadUrl);
}
})
.setNegativeButton(R.string.dialog_btn_cancel_download, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
}
});
AlertDialog dialog = builder.create();
//点击对话框外面,对话框不消失
dialog.setCanceledOnTouchOutside(false);
dialog.show();
}
其中Sting类型的参数content 。就是我们要显示的中文了,只不过它现在还是经过编码的字符串。当我们显示到手机屏幕的时候,Android会帮我们解码。拿到对应的字符集的字。如果我们拿到的不是utf-8的编码,那么此时,我们是需要进行转码的,转成utf-8。然后给Android显示端。 (主要是要明白,解码在那个阶段地方,上面的代码没能体现出转码的地方,因为我们拿到的就是utf-8)
二、现在来谈谈编码方式
1、编码的问题
在谈编码方式之前,大家先来做一个小实验:
大家在电脑上新建一个txt类型的文档,然后双击打开,在里面输入:联通2字 。
接着保存并退出。然后直接双击打开刚才的txt文档,你会发现,咦,怎么是乱码了!!!
这是为什么呢?这就涉及到了编码方式的问题了。这是因为我们的保存和打开的方式并不是同一种。这样的话,解码的过程就出错了,看到的就是乱码了。我们的电脑上都有很多不同格式类型的文件,比如: txt文档、记事本、word文档等等,那么这个编码方式是由谁来确定的呢?是我们系统平台Windows、Linux,还是我们编辑的软件呢。回想一下刚才的小实验,和Android studio 中常用的xml 文件。答案肯定是编辑软件了。
也就是说,我们在保存的时候,当前的编辑软件是先把我们的内容通过编码的方式,打包成ByteArray,然后交给系统,系统帮我们保存数据包。如果是由系统编码,那系统不累死了,而且市场上那么多类型的文件,系统怎么知道你这个软件要使用那一种编码?所以系统的责任只是保存。它不管你里面是什么,只要是数据包就ok。
整个流程,我们可以用下面一个图来描述:
对同一个字符,如果采用了不同字符集来编码,那么生成的值可能是不一样。比如,对同一个中文,采用不同的字符集来编码,得到的数值都不一样。那么解码的时候,如果用另一套字符集,那肯定会乱码。
2、解码的问题
好了,既然知道了保存的时候使用的是某一种编码方式,那么打开的时候,肯定也要使用相应的解码方式,这样才能获得正确的数据。
三、本次乱码的问题的原因
由上面的知识知道,保存的时候会有一个编码的过程,打开的时候会有一个解码的过程。但是,Tomcat并不提供一个打开过程,而是在启动的时候,自动去加载webapps 底下目录的资源。
1、也就是说,这些资源(其实就是ByteArray)是被加载到内存中的,并不像我们直接使用软件去打开,所以我们不能从视觉上,直观的看到加载到内存中的数据有没有变化,或者说存不存在乱码的问题。换句话说,我们并不知道,Tomcat有没有对系统交给它的ByteArray进行解码。
2、Tomcat在响应请求的时候,发送数据之前会不会对自己内存的数据进行编码呢?
换句话说,如果Tomcat在加载资源的时候有进行解码,而它用了却用了自己特有的方式去解码,那么Tomcat加载到内存中的数据本身就已经成乱码了,如果在发送的时候,Tomcat再进行一次编码,那就是乱上加乱,火上浇油了。这样,客户端完全没法解码了!
如果我们能搞清楚上面的两个问题,那么一切都会迎刃而解了。然而,理想是美好的,现实却是残酷的。由于知识所限,我们根本没法验证这些东西。
Tomcat的整个加载流程我们可以这么来描述:
四、问题的解决。
虽然以上的大多数问题,我们都了解了。但是,我们根本就无法去验证。
比如:你写了一个json类型的文件,你怎么知道你当前的编辑软件用的是什么编码格式呢?
如下图所示:
这时候,我想起了,我们Android Studio 中使用的编码方式是utf-8。那么我们可不可以用Android Studio 去打开我们这个json类型的文件呢?之后修改并保存。这样的话,我们保存在系统上的数据,就是采用utf-8这种编码方式了。这时候,只好动手来试一试了。
然后我们用Android Studio去打开我们的json升级文件。
什么鬼,怎么中文全是乱码?到这里,大家应该明白了吧,Android Studio 是使用utf-8 来编码的,解码当然也用utf-8了。换个角度来说,这也证明了,我们之前用记事本保存数据的时候,它的编码并不是utf-8。那么解码方式不对,也就乱码了!所以我们只好在Android Studio打开的方式下,手动修改下。
修改好之后,保存。然后启动我们的Tomcat,再用APP端向服务端申请数据。
耶,终于不是乱码了!
从这个实验中,我们可以得出两个结论:
1、Tomcat在发送数据的时候,是会对数据进行编码的,用的是utf-8编码。因为Android在显示的时候,是一定会对数据进行解码的。如果Tomcat不进行编码,那Android端得到的就是乱码。
2、Tomcat加载资源的时候,肯定是会对资源进行解码的。它的解码方式也是utf-8,为什么说肯定会解码呢,因为不解码的话,数据在内存中做了各种加减乘除运算后,鬼知道你是什么了。
小结:保存数据要编码—加载数据要解码—发送服务要编码—接收数据要解码 。四个环节的方式都要一样,这样才不会乱码。
通过这件事情,我明白了,原来我们系统上保存的资源,都是经过编码的ByteArray
最后给大家一个,android端向服务端post数据时,编码的转换流程图:
张鸿洋的博客:
Android访问服务器(TOMCAT)乱码引发的问题。链接:http://blog.csdn.net/lmj623565791/article/details/21789381
一定要注意这句:服务器(Tomcat)默认使用iso-8859-1解码。Iso-8859-1是不支持中文的,也就是说不做处理,中文是一定乱码的。
这里说的是:解码用的是iso-8859-1,并不是编码。很多人的博客都是乱写的。小编用的是Tomcat6.0。就没有去设置这个玩意。
在TOMCAT的配置文件的server.xml中更改:
以上是关于访问Tomcat服务器返回数据乱码的主要内容,如果未能解决你的问题,请参考以下文章