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对象,保证每次取到的都是正确的值。

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


小结

此坑到此为止~~~!

目录
相关文章
|
4天前
|
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配置以确保顺利运行。
24 1
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
|
3月前
|
安全 API Android开发
Android网络和数据交互: 解释Retrofit库的作用。
Android网络和数据交互: 解释Retrofit库的作用。
39 0
|
4月前
|
XML Android开发 数据安全/隐私保护
Android 自定义开源库 EasyView
Android 自定义开源库 EasyView
|
1月前
|
存储 JSON 安全
【C++ JSON库 json值的创建手段】深入探究C++中JSON对象定位与操作:从引用到回调函数
【C++ JSON库 json值的创建手段】深入探究C++中JSON对象定位与操作:从引用到回调函数
66 0
|
1月前
|
存储 JSON 算法
C++ JSON库 nlohmann::basic_json::boolean_t 的用法
C++ JSON库 nlohmann::basic_json::boolean_t 的用法
35 0
|
9天前
|
JSON API 数据格式
python的request库如何拿到json的返回值
python的request库如何拿到json的返回值
10 0
|
1月前
|
JSON JavaScript 数据格式
【深入探究C++ JSON库】解析JSON元素的层级管理与遍历手段
【深入探究C++ JSON库】解析JSON元素的层级管理与遍历手段
91 2
|
1月前
|
XML JSON API
深入解析C++ JSON库:nlohmann::json:: parse的内部机制与应用
深入解析C++ JSON库:nlohmann::json:: parse的内部机制与应用
52 0
|
1月前
|
存储 JSON 算法
C++ JSON库 nlohmann::basic_json::binary_t的用法
C++ JSON库 nlohmann::basic_json::binary_t的用法
25 0
|
1月前
|
JSON 数据格式 C++
C++ JSON库 nlohmann::basic_json::binary 的用法
C++ JSON库 nlohmann::basic_json::binary 的用法
24 0