访问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中更改:

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在Android Studio中,当你查看git历史记录时,可能会遇到中文乱码的问题。这个问题通常是由于编码方式不匹配导致的。在你的引用中,提到了一种解决方法,可以尝试一下。 首先,打开Android Studio并点击"git",然后选择"Log Main"来查看git的历史记录。如果你发现其中一些中文commit和git user_name显示乱码,那么问题很可能是由于编码方式不正确引起的。 根据引用中提到的解决办法,在Stack Overflow上有一个简单的解决方案。这个解决方案建议将Android Studio的编码方式更改为UTF-8。 你可以按照以下步骤进行操作: 1. 在Android Studio中,点击"File"菜单,然后选择"Settings"。 2. 在设置窗口中,选择"Editor",然后选择"File Encodings"。 3. 在"Global Encoding"和"Project Encoding"下拉菜单中,选择"UTF-8"作为编码方式。 4. 点击"OK"保存更改。 通过更改Android Studio的编码方式为UTF-8,可以解决git历史记录中显示中文乱码的问题。这种更改可以确保Android Studio正确地读取和显示中文字符。 总结一下,要解决Android Studio中git显示中文乱码的问题,你可以通过更改Android Studio的编码方式为UTF-8来解决。这样可以确保正确读取和显示中文字符。希望这个解决方案对你有帮助!如果你还有其他问题,请随时提问。 - 引用来源: Android Studio git显示中文乱码问题 - 引用来源: Android Studio 3.0之后git log命令查看历史记录乱码问题解决方法 - 引用来源: Android Studio编码方式设置为UTF-8解决git显示中文乱码问题

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值