在网络中,数据的传输,最常用的格式有两种: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() {//访问网络,首要就是--开启子线程。
- newThread(newRunnable() {
- @Overridepublic void run() {//下面4个变量定义在try catch块外面,是因为如果定义在try里面,finally里面就拿不到变量了,
- //关闭不了对象,会造成内存泄露。HttpURLConnection connection =null;
- InputStreamis=null;
- BufferedReader buffer =null;
- String result =null;try{//这里我访问的是我Tomcat的服务器数据。URL url =newURL("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 =newBufferedReader(newInputStreamReader(is));
- StringBuilder response =newStringBuilder();
- String line;//打开连接后,子线程会一直死循环读数据,直到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();
的时候,它怎么保证访问网络都完成了?小编想了想,哦!这就是关系到了 Java 的封装性了。也就是说当 JVM 执行到 connection 对象的方法 getInputStream() 时候,jvm 会进入这个方法,这个方法里面肯定会一直循环。直到不符合某种条件后退出,然后把返回值赋值给 is。也就是说
is = connection.getInputStream(); // 这行代码是先进入 getInputStream() 这个方法内执行,至于内部搞不搞死循环我们就不得知了。执行完成后再赋值。 所以大家以后要多用用 Android studio 的 debug 模式,设置断点。跟踪程序怎么走的!!这样才更清晰的知道程序的流程。
// 数据解析,代码很简单。
- private void parseJSONWithJSONObject(String result) {try{
- JSONObject obj =newJSONObject(result);
- String apkUrl = obj.getString("url");//APK下载路径String updateMessage = obj.getString("updateMessage");//版本更新说明
- intapkCode = obj.getInt("versionCode");//新版APK对于的版本号
- //取得已经安装在手机的APP的版本号 versionCode
- intversionCode = 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 =newAlertDialog.Builder(mContext);
- builder.setTitle(R.string.dialog_choose_update_title);
- builder.setMessage(content)
- .setPositiveButton(R.string.dialog_btn_confirm_download,newDialogInterface.OnClickListener() {public void onClick(DialogInterface dialog,intid) {//下载apk文件goToDownloadApk(downloadUrl);
- }
- })
- .setNegativeButton(R.string.dialog_btn_cancel_download,newDialogInterface.OnClickListener() {public void onClick(DialogInterface dialog,intid) {
- }
- });
- 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 中更改:
来源: http://blog.csdn.net/lin353809836/article/details/70306933