Android自帶Json庫使用引發的問題
在Andriod系統應用層開發通常json協議解析使用Gson、jackson當然還公司的fastjson庫等,Andriod其實也自帶json解析庫,整合的是apache的,在一些特定的場景用自帶庫解析也很方便。
但是,不得不說自帶庫有個坑踩進去了就會被坑的挺慘,而且很難發現到問題;
一、背景
我們的專案部分模組在http請求時涉及到對引數key value計算出md5,通過json協議資料傳輸,到了服務端再做md5的校驗,正常來說計算md5的規則雙方都做了統一保證,滿足了一致性的條件。理論上,只要通訊過程資料未發生篡改,100%能保證是一致的;但是問題來了,即使中間的通訊資料資料未被篡改,雙方計算出來的md5還是存在不匹配的情形,而且出現的問題斷斷續續,一直沒有得到有效定位和解決。
然而並沒有想的那樣100%md5計算相同
二、排查路徑
2.1 分析現象
問題出現時會一直提示md5校驗失敗,說明兩邊的md5計算結果確實不一樣,然而發生的概率很低,低到幾乎可以忽略不計,但只要出現問題就能穩定復現。
2.2 定位問題
首先,很容易想到的是雙方計算規則不同,計算的層級不同,畢竟Android端對庫的依賴和服務端庫的依賴存在這差別;然而,將演算法統一校準後問題並沒有得到解決~
再來從有問題的請求json串入手分析,發現帶問題的json資料給到服務端解析後–出現json轉化的值一些些特定字元都會被去掉,那問題其實就定位到了,但這個服務端的問題嗎?畢竟它每次都會將值裡邊的某個字元給丟掉。查下json規範,http://www.rfc-editor.org/rfc/rfc4627.txt(RFC 4627)轉義符號會被當作無效字元給丟棄,說的也很清楚。
很明顯編譯器也過不了這種規則,但是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 ` `:
out.append("\t");
break;
case ``:
out.append("\b");
break;
case `
`:
out.append("\n");
break;
case `
`:
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 == `
`) {
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";
REPLACEMENT_CHARS[``] = "\b";
REPLACEMENT_CHARS[`
`] = "\n";
REPLACEMENT_CHARS[`
`] = "\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物件,保證每次取到的都是正確的值。
方案一需要重寫去除轉義的方法,方案二會一定程度的影響到效能。那還是使用方案一來修復;
小結
此坑到此為止~~~!
相關文章
- 使用Android sdk自帶的jsonReader來解析jsonAndroidJSON
- oracle自帶的sql developer使用問題OracleSQLDeveloper
- Mac 開發環境 MAMP 自帶 Redis 問題Mac開發環境Redis
- 「前端 BUG 錄」變更 UI 庫主題引發的問題前端UI
- 使用DBMS_RANDOM過程引發的問題random
- 解決json顯示日期帶T的問題。JSON
- Android開發之自帶下載器DownloadManager的使用Android
- IOS 7 利用系統自帶庫進行 POST JSON 非同步訪問操作iOSJSON非同步
- SQL SERVER資料庫datediff函式引發的效能問題SQLServer資料庫函式
- Django ORM 引發的資料庫 N+1 效能問題DjangoORM資料庫
- @AllArgsConstructor與@Value共同使用引發的報錯問題Struct
- php使用mysqlnd引發的一些問題處理PHPMySql
- 使用Spring MVC 的 @RequestBody 對映json請求引數時報異常問題SpringMVCJSON
- Python自動生成10000個java類使用APT註解後引發的問題PythonJavaAPT
- reflow和repaint引發的效能問題AI
- 專案叢集引發的問題
- Oracle使用觸發器實現ID自增的問題Oracle觸發器
- 使用Jenkins自動構建Android問題總結JenkinsAndroid
- linux定時任務url帶引數的問題Linux
- 記一個 Android 14 適配引發的Android 儲存許可權問題Android
- [android]android自動化過程遇到的問題Android
- 記一次Java自動拆箱引發的空指標問題Java指標
- 解決Url帶中文引數亂碼問題
- Android系統自帶主題和樣式Android
- 使用Javascript 開發個JSON解析庫JavaScriptJSON
- 記錄Android學習-遇到的第一個問題,AS自帶AVD無法啟動Android
- 遷移後帶庫備份問題
- JS語法: 由++[[]][+[]]+[+[]] = 10 ?引發的問題JS
- spring呼叫帶引數的oracle函式應注意的問題SpringOracle函式
- android自帶的api的例子很多AndroidAPI
- 關於excelize庫的使用問題Excelize
- JSON簡介(java中的json庫使用)JSONJava
- 使用EJB遠端介面帶來的效能問題
- 遷移後帶庫備份問題(二)
- 微信二次分享的問題 帶有引數的php頁面PHP
- Mybatis 一級快取和引發的問題MyBatis快取
- JS中缺少分號可能引發的問題JS
- sql server datediff函式引發的效能問題SQLServer函式