這篇文章主要給大家介紹了關於Emoji表情在Android JNI中的兼容性問題,文中通過示例代碼介紹的非常詳細,對大家學習或者使用Android JNI具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧
起因
最近遇到一個問題,把某個字符串計算MD5,之後把該字符串加密與MD5一起上傳到服務端,服務端解密後重新計算md5發現與上傳的MD5不一致,而出問題的字符串中無一例外都有Emoji表情。但我自己弄個帶表情的字符串上傳卻沒有什麼問題。
最終確認這是在Android 5.1以下 jstring -> char數組 時出的問題。下面通過一個示例來還原這個過程。
事件還原
假設有一個字符串s,String s = "\uD83D\uDC8B";
,對應表情💋
。通過調用getBytes()
方法,會看到對應的byte數組爲[-16, -97, -110, -117]
,按16進制輸出爲[f0, 9f, 92, 8b]
。
定義一個參數爲String的native方法,public native String test(String str);
,在對應的C/C++代碼中,通過env->GetStringUTFChars
獲取傳入的String對應的char數組,把char數組的每一個元素按16進制輸出。
在Android 7.1.2的測試機上,native層輸出的結果爲[f0, 9f, 92, 8b]
,與Java的byte數組是一樣的,但是在Android 4.4.4的測試機上,輸出結果爲[ed, a0, bd, ed, b2, 8b]
。從而導致加密後的結果不一樣。
服務端收到舊版Android的數據解密後得到[ed, a0, bd, ed, b2, 8b]
,計算MD5自然無法與[f0, 9f, 92, 8b]
計算MD5一樣。
Unicode、UTF-8、UTF-16
可能有人不是很清楚上面那2種byte數組是怎麼來的。首先我們要知道,UTF-8和UTF-16都是Unicode的實現。\uD83D\uDC8B
其實是UTF-16大端的表現形式,對於大於0xFFFF(0x10000~0x10FFFF)的Unicode,轉換爲UTF-16的步驟如下:
- 將Unicode減去0x10000,結果將是一個長度爲20bit的值。
- 將第一步的20bit的高10bit與0xD800進行或運算,得到UTF-16的高位代理。
- 將第一步的20bit的低10bit與0xDC00進行或運算,得到UTF-16的低位代理。
- 高位代理+低位代理即Unicode對應的UTF-16的大端形式。
按照這個步驟反推:
- \uD83D\uDC8B的二進制位1101 1000 0011 1101 1101 1100 1000 1011,則高位代理爲1101 1000 0011 1101,低位代理爲1101 1100 1000 1011。
- 高位代理由高10bit與0xD800進行或運算得到,因此高10bit爲00 0011 1101。
- 低位代理由低10bit與0xDC00進行或運算得到,因此低10bit爲00 1000 1011。
- 所有20bit的值爲0000 1111 0100 1000 1011。
- 加上0x10000,爲0001 1111 0100 1000 1011,即0x1F48B。
所以,表情💋對應的Unicode爲0x1F48B。
UTF-8的規則是,對於佔N個字節的符號(N>1),第一個字節前N位都是1,N+1位是0,後面的字節前2位爲10,然後把Unicode的二進制位填入空缺的二進制位中,空出的位置補0。因此,上面的Unicode 0x1F48B轉爲UTF-8需要佔4個字節,爲:
11110 000
10 011111
10 010010
10 001011
即0xF09F928B,這也就是[f0, 9f, 92, 8b]這個byte數組的由來。
那麼[ed, a0, bd, ed, b2, 8b]這個byte數組又是怎麼來的呢?這是把\uD83D\uDC8B當成2個單獨的字符處理了,按照上面Unicode轉UTF-8的邏輯,Unicode 0xD83D轉爲UTF-8爲1110 1101 10 100000 10 111101,即0xEDA0BD,Unicode 0xDC8B轉爲UTF-8爲1110 1101 10 110010 10 001011,即0xEDB28B。
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對神馬文庫的支持。