Android自带Json库使用引发的问题

简介: 在Andriod系统应用层开发通常json协议解析使用Gson、jackson当然还公司的fastjson库等,Andriod其实也自带json解析库,集成的是apache的,在一些特定的场景用自带库解析也很方便。 但是,不得不说自带库有个坑踩进去了就会被坑的挺惨,而且很难发现到问题;

在Andriod系统应用层开发通常json协议解析使用Gson、jackson当然还公司的fastjson库等,Andriod其实也自带json解析库,集成的是apache的,在一些特定的场景用自带库解析也很方便。
但是,不得不说自带库有个坑踩进去了就会被坑的挺惨,而且很难发现到问题;

一、背景

我们的项目部分模块在http请求时涉及到对参数key value计算出md5,通过json协议数据传输,到了服务端再做md5的校验,正常来说计算md5的规则双方都做了统一保证,满足了一致性的条件。理论上,只要通信过程数据未发生篡改,100%能保证是一致的;但是问题来了,即使中间的通信数据数据未被篡改,双方计算出来的md5还是存在不匹配的情形,而且出现的问题断断续续,一直没有得到有效定位和解决。

18_08_39__06_07_2018.jpg

然而并没有想的那样100%md5计算相同

二、排查路径

2.1 分析现象

问题出现时会一直提示md5校验失败,说明两边的md5计算结果确实不一样,然而发生的概率很低,低到几乎可以忽略不计,但只要出现问题就能稳定复现。


2.2 定位问题

首先,很容易想到的是双方计算规则不同,计算的层级不同,毕竟Android端对库的依赖和服务端库的依赖存在这差别;然而,将算法统一校准后问题并没有得到解决~

再来从有问题的请求json串入手分析,发现带问题的json数据给到服务端解析后--出现json转化的值一些些特定字符都会被去掉,那问题其实就定位到了,但这个服务端的问题吗?毕竟它每次都会将值里边的某个字符给丢掉。查下json规范,http://www.rfc-editor.org/rfc/rfc4627.txt(RFC 4627)转义符号会被当作无效字符给丢弃,说的也很清楚。

18_32_19__06_07_2018.jpg
很明显编译器也过不了这种规则,但是json数据传输时这串是能成立的

那是数据获取源头产生的问题吗,它是否在运行过程中就是产生了这种string值?动态调试了一番发现在字段赋值的时候的确是没有转义字符的('')。很明显了,就是在转换成json的时候被加上转义符了,这也很难和md5计算扯上联系对吧?关键的点来了,因为一直以来都是在最后的封装环节把数据封装好了数据才进行md5计算,这个思路和方案都没有问题的(不可能提前知晓所有字段和值吧?),那就说明是使用系统json库取值的时候出了问题,让转义符也参与了计算,看源码部分。


//opt是JSONObject
if (opt.getClass().isPrimitive()) {
    return opt.toString();
 }

问题是定位到了,那这是很神奇的问题啊,让我们从源码来看看~

  • Android系统自带json库
//org.json.JSONStringer
private void string(String value) {
        out.append("\"");
        for (int i = 0, length = value.length(); i < length; i++) {
            char c = value.charAt(i);

            /*
             * From RFC 4627, "All Unicode characters may be placed within the
             * quotation marks except for the characters that must be escaped:
             * quotation mark, reverse solidus, and the control characters
             * (U+0000 through U+001F)."
             */
            switch (c) {
                case '"':
                case '\\':
                case '/':
                    out.append('\\').append(c);//看这
                    break;

                case '\t':
                    out.append("\\t");
                    break;

                case '\b':
                    out.append("\\b");
                    break;

                case '\n':
                    out.append("\\n");
                    break;

                case '\r':
                    out.append("\\r");
                    break;

                case '\f':
                    out.append("\\f");
                    break;

                default:
                    if (c <= 0x1F) {
                        out.append(String.format("\\u%04x", (int) c));
                    } else {
                        out.append(c);
                    }
                    break;
            }

        }
        out.append("\"");
    }

其转义时会将字符'\'插入需要转义的前一位,下面对比Gson的解析和封装。


  • Gson解析json
https://github.com/google/gson/blob/master/gson/src/main/java/com/google/gson/stream/JsonReader.java

 /**
   * Returns the string up to but not including {@code quote}, unescaping any
   * character escape sequences encountered along the way. The opening quote
   * should have already been read. This consumes the closing quote, but does
   * not include it in the returned string.
   *
   * @param quote either ' or ".
   * @throws NumberFormatException if any unicode escape sequences are
   *     malformed.
   */
  private String nextQuotedValue(char quote) throws IOException {
    // Like nextNonWhitespace, this uses locals 'p' and 'l' to save inner-loop field access.
    char[] buffer = this.buffer;
    StringBuilder builder = null;
    while (true) {
      int p = pos;
      int l = limit;
      /* the index of the first character not yet appended to the builder. */
      int start = p;
      while (p < l) {
        int c = buffer[p++];

        if (c == quote) {
          pos = p;
          int len = p - start - 1;
          if (builder == null) {
            return new String(buffer, start, len);
          } else {
            builder.append(buffer, start, len);
            return builder.toString();
          }
        } else if (c == '\\') {//看这
          pos = p;
          int len = p - start - 1;
          if (builder == null) {
            int estimatedLength = (len + 1) * 2;
            builder = new StringBuilder(Math.max(estimatedLength, 16));
          }
          builder.append(buffer, start, len);
          builder.append(readEscapeCharacter());
          p = pos;
          l = limit;
          start = p;
        } else if (c == '\n') {
          lineNumber++;
          lineStart = p;
        }
      }

      if (builder == null) {
        int estimatedLength = (p - start) * 2;
        builder = new StringBuilder(Math.max(estimatedLength, 16));
      }
      builder.append(buffer, start, p - start);
      pos = p;
      if (!fillBuffer(1)) {
        throw syntaxError("Unterminated string");
      }
    }
  }

其写方法

static {

    REPLACEMENT_CHARS = new String[128];

    for (int i = 0; i <= 0x1f; i++) {

      REPLACEMENT_CHARS[i] = String.format("\\u%04x", (int) i);

    }

    REPLACEMENT_CHARS['"'] = "\\\"";

    REPLACEMENT_CHARS['\\'] = "\\\\";

    REPLACEMENT_CHARS['\t'] = "\\t";

    REPLACEMENT_CHARS['\b'] = "\\b";

    REPLACEMENT_CHARS['\n'] = "\\n";

    REPLACEMENT_CHARS['\r'] = "\\r";

    REPLACEMENT_CHARS['\f'] = "\\f";

    HTML_SAFE_REPLACEMENT_CHARS = REPLACEMENT_CHARS.clone();

    HTML_SAFE_REPLACEMENT_CHARS['<'] = "\\u003c";

    HTML_SAFE_REPLACEMENT_CHARS['>'] = "\\u003e";

    HTML_SAFE_REPLACEMENT_CHARS['&'] = "\\u0026";

    HTML_SAFE_REPLACEMENT_CHARS['='] = "\\u003d";

    HTML_SAFE_REPLACEMENT_CHARS['\''] = "\\u0027";

  }

gson是不会对'/'进行转义的,那是否直接使用gson库替换就解决问题?应该还不是这么简单的,这里我们注意到仍然是存在特殊字符需要转义的,最后还是得回到调整取json字符值上面来。到这里问题就定位的很清楚了。


2.3 评估影响面

这个其实影响很大的,一直以来就很难发现潜在的问题(概率低),尤其是作为通信最基础关键的部分。每次请求都可能会触发到特殊字符的转义,那后面再从json对象取到的字符串string值都是带着转义符号过来的。


2.4 解决方案

两个解决方案:
一是,对从json对象去除字符串进行二次加工,调用去除转义的方法把转义符号给去除。
二是,将json对象解析回java对象,保证每次取到的都是正确的值。

方案一需要重写去除转义的方法,方案二会一定程度的影响到性能。那还是使用方案一来修复;


小结

此坑到此为止~~~!

目录
相关文章
|
17天前
|
Linux 编译器 Android开发
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
在Linux环境下,本文指导如何交叉编译x265的so库以适应Android。首先,需安装cmake和下载android-ndk-r21e。接着,下载x265源码,修改crosscompile.cmake的编译器设置。配置x265源码,使用指定的NDK路径,并在配置界面修改相关选项。随后,修改编译规则,编译并安装x265,调整pc描述文件并更新PKG_CONFIG_PATH。最后,修改FFmpeg配置脚本启用x265支持,编译安装FFmpeg,将生成的so文件导入Android工程,调整gradle配置以确保顺利运行。
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
|
18天前
|
Unix Linux Shell
FFmpeg开发笔记(八)Linux交叉编译Android的FFmpeg库
在Linux环境下交叉编译Android所需的FFmpeg so库,首先下载`android-ndk-r21e`,然后解压。接着,上传FFmpeg及相关库(如x264、freetype、lame)源码,修改相关sh文件,将`SYSTEM=windows-x86_64`改为`SYSTEM=linux-x86_64`并删除回车符。对x264的configure文件进行修改,然后编译x264。同样编译其他第三方库。设置环境变量`PKG_CONFIG_PATH`,最后在FFmpeg源码目录执行配置、编译和安装命令,生成的so文件复制到App工程指定目录。
FFmpeg开发笔记(八)Linux交叉编译Android的FFmpeg库
|
2月前
|
存储 JSON 安全
【C++ JSON库 json值的创建手段】深入探究C++中JSON对象定位与操作:从引用到回调函数
【C++ JSON库 json值的创建手段】深入探究C++中JSON对象定位与操作:从引用到回调函数
67 0
|
2月前
|
存储 JSON 算法
C++ JSON库 nlohmann::basic_json::boolean_t 的用法
C++ JSON库 nlohmann::basic_json::boolean_t 的用法
37 0
|
1天前
|
JSON Java Linux
【探索Linux】P.30(序列化和反序列化 | JSON序列化库 [ C++ ] )
【探索Linux】P.30(序列化和反序列化 | JSON序列化库 [ C++ ] )
15 2
|
2天前
|
Android开发 C++
Android S HAL库的编译
Android S HAL库的编译
6 0
|
3天前
|
安全 Linux Android开发
FFmpeg开发笔记(十六)Linux交叉编译Android的OpenSSL库
该文介绍了如何在Linux服务器上交叉编译Android的FFmpeg库以支持HTTPS视频播放。首先,从GitHub下载openssl源码,解压后通过编译脚本`build_openssl.sh`生成64位静态库。接着,更新环境变量加载openssl,并编辑FFmpeg配置脚本`config_ffmpeg_openssl.sh`启用openssl支持。然后,编译安装FFmpeg。最后,将编译好的库文件导入App工程的相应目录,修改视频链接为HTTPS,App即可播放HTTPS在线视频。
FFmpeg开发笔记(十六)Linux交叉编译Android的OpenSSL库
|
22天前
|
JSON API 数据格式
python的request库如何拿到json的返回值
python的request库如何拿到json的返回值
16 0
|
1月前
|
存储 JSON JavaScript
【嵌入式开源库】cJSON的使用,高效精简的json解析库
【嵌入式开源库】cJSON的使用,高效精简的json解析库
|
2月前
|
JSON JavaScript 数据格式
【深入探究C++ JSON库】解析JSON元素的层级管理与遍历手段
【深入探究C++ JSON库】解析JSON元素的层级管理与遍历手段
109 2