Emoji表情在Android JNI中的兼容性問題詳解
起因
最近遇到一個(gè)問題,把某個(gè)字符串計(jì)算MD5,之后把該字符串加密與MD5一起上傳到服務(wù)端,服務(wù)端解密后重新計(jì)算md5發(fā)現(xiàn)與上傳的MD5不一致,而出問題的字符串中無一例外都有Emoji表情。但我自己弄個(gè)帶表情的字符串上傳卻沒有什么問題。
最終確認(rèn)這是在Android 5.1以下 jstring -> char數(shù)組 時(shí)出的問題。下面通過一個(gè)示例來還原這個(gè)過程。
事件還原
假設(shè)有一個(gè)字符串s,String s = "\uD83D\uDC8B";
,對應(yīng)表情💋
。通過調(diào)用getBytes()
方法,會(huì)看到對應(yīng)的byte數(shù)組為[-16, -97, -110, -117]
,按16進(jìn)制輸出為[f0, 9f, 92, 8b]
。
定義一個(gè)參數(shù)為String的native方法,public native String test(String str);
,在對應(yīng)的C/C++代碼中,通過env->GetStringUTFChars
獲取傳入的String對應(yīng)的char數(shù)組,把char數(shù)組的每一個(gè)元素按16進(jìn)制輸出。
在Android 7.1.2的測試機(jī)上,native層輸出的結(jié)果為[f0, 9f, 92, 8b]
,與Java的byte數(shù)組是一樣的,但是在Android 4.4.4的測試機(jī)上,輸出結(jié)果為[ed, a0, bd, ed, b2, 8b]
。從而導(dǎo)致加密后的結(jié)果不一樣。
服務(wù)端收到舊版Android的數(shù)據(jù)解密后得到[ed, a0, bd, ed, b2, 8b]
,計(jì)算MD5自然無法與[f0, 9f, 92, 8b]
計(jì)算MD5一樣。
Unicode、UTF-8、UTF-16
可能有人不是很清楚上面那2種byte數(shù)組是怎么來的。首先我們要知道,UTF-8和UTF-16都是Unicode的實(shí)現(xiàn)。\uD83D\uDC8B
其實(shí)是UTF-16大端的表現(xiàn)形式,對于大于0xFFFF(0x10000~0x10FFFF)的Unicode,轉(zhuǎn)換為UTF-16的步驟如下:
- 將Unicode減去0x10000,結(jié)果將是一個(gè)長度為20bit的值。
- 將第一步的20bit的高10bit與0xD800進(jìn)行或運(yùn)算,得到UTF-16的高位代理。
- 將第一步的20bit的低10bit與0xDC00進(jìn)行或運(yùn)算,得到UTF-16的低位代理。
- 高位代理+低位代理即Unicode對應(yīng)的UTF-16的大端形式。
按照這個(gè)步驟反推:
- \uD83D\uDC8B的二進(jìn)制位1101 1000 0011 1101 1101 1100 1000 1011,則高位代理為1101 1000 0011 1101,低位代理為1101 1100 1000 1011。
- 高位代理由高10bit與0xD800進(jìn)行或運(yùn)算得到,因此高10bit為00 0011 1101。
- 低位代理由低10bit與0xDC00進(jìn)行或運(yùn)算得到,因此低10bit為00 1000 1011。
- 所有20bit的值為0000 1111 0100 1000 1011。
- 加上0x10000,為0001 1111 0100 1000 1011,即0x1F48B。
所以,表情💋對應(yīng)的Unicode為0x1F48B。
UTF-8的規(guī)則是,對于占N個(gè)字節(jié)的符號(hào)(N>1),第一個(gè)字節(jié)前N位都是1,N+1位是0,后面的字節(jié)前2位為10,然后把Unicode的二進(jìn)制位填入空缺的二進(jìn)制位中,空出的位置補(bǔ)0。因此,上面的Unicode 0x1F48B轉(zhuǎn)為UTF-8需要占4個(gè)字節(jié),為:
11110 000
10 011111
10 010010
10 001011
即0xF09F928B,這也就是[f0, 9f, 92, 8b]這個(gè)byte數(shù)組的由來。
那么[ed, a0, bd, ed, b2, 8b]這個(gè)byte數(shù)組又是怎么來的呢?這是把\uD83D\uDC8B當(dāng)成2個(gè)單獨(dú)的字符處理了,按照上面Unicode轉(zhuǎn)UTF-8的邏輯,Unicode 0xD83D轉(zhuǎn)為UTF-8為1110 1101 10 100000 10 111101,即0xEDA0BD,Unicode 0xDC8B轉(zhuǎn)為UTF-8為1110 1101 10 110010 10 001011,即0xEDB28B。
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對腳本之家的支持。
相關(guān)文章
Android啟動(dòng)頁面定時(shí)跳轉(zhuǎn)的三種方法
這篇文章主要介紹了Android啟動(dòng)頁面定時(shí)跳轉(zhuǎn)的三種方法,實(shí)現(xiàn)打開一個(gè)Android手機(jī)APP的歡迎界面后跳轉(zhuǎn)到指定界面的效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11Compose開發(fā)之動(dòng)畫藝術(shù)探索及實(shí)現(xiàn)示例
這篇文章主要為大家介紹了Compose開發(fā)之動(dòng)畫藝術(shù)探索及實(shí)現(xiàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09Android中實(shí)現(xiàn)監(jiān)聽ScrollView滑動(dòng)事件
這篇文章主要介紹了Android中實(shí)現(xiàn)監(jiān)聽ScrollView滑動(dòng)事件,本文用重寫ScrollView類的方法實(shí)現(xiàn)了一些擴(kuò)展功能,需要的朋友可以參考下2015-05-05利用Jetpack Compose實(shí)現(xiàn)經(jīng)典俄羅斯方塊游戲
你的童年是否有俄羅斯方塊呢,本文就來介紹如何通過Jetpack Compose實(shí)現(xiàn)一個(gè)俄羅斯方塊!感興趣的小伙伴快跟隨小編一起動(dòng)手嘗試一下吧2022-05-05android音樂播放簡單實(shí)現(xiàn)的簡單示例(MediaPlayer)
本篇文章主要介紹了android音樂播放簡單實(shí)現(xiàn)的簡單示例(MediaPlayer),具有一定的參考價(jià)值,有興趣的可以了解一下2017-08-08關(guān)于Android輸入法彈窗bug的優(yōu)雅處理
在Android應(yīng)用中,當(dāng)跳轉(zhuǎn)到某個(gè)Activity時(shí),該Activity顯示頁面的EditText獲得焦點(diǎn),在某些機(jī)器中會(huì)觸發(fā)軟鍵盤的自動(dòng)彈出,這篇文章主要給大家介紹了關(guān)于Android輸入法彈窗bug的優(yōu)雅處理,需要的朋友可以參考下2021-10-10設(shè)置界面開發(fā)Preference Library數(shù)據(jù)重建機(jī)制詳解
這篇文章主要為大家介紹了設(shè)置界面開發(fā)利器Preference Library數(shù)據(jù)重建機(jī)制詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10