在移動(dòng)互聯(lián)網(wǎng)如此發(fā)達(dá)的今天,Emoji 已無(wú)處不在,并成為我們?nèi)粘=涣髦胁豢苫蛉钡囊徊糠帧?jù)統(tǒng)計(jì),Emoji 的使用率在過(guò)去 10 年內(nèi)不斷攀升,2021 年更是達(dá)到了歷史新高,每天有超過(guò)五分之一的推文中包含了 Emoji,一些應(yīng)用上的用戶每天發(fā)送的 Emoji 數(shù)量更是達(dá)到了數(shù)十億。然而用戶在 Android 平臺(tái)上使用 Emoji 時(shí)卻存在著一些問(wèn)題,本文將針對(duì)這些問(wèn)題進(jìn)行探討,并向您介紹 Emoji 的工作原理以及 Android 平臺(tái)近期關(guān)于 Emoji 的更新。
Emoji 在 Android 平臺(tái)的現(xiàn)狀
Emoji 在 Android 平臺(tái)存在的問(wèn)題
Unicode 每年都會(huì)對(duì) Emoji 標(biāo)準(zhǔn)進(jìn)行更新,用戶越來(lái)越頻繁地使用各種 Emoji,但卻存在有約 96% 的 Android 用戶無(wú)法正確查看新發(fā)布 Emoji 的問(wèn)題,而 iOS 平臺(tái)只有 16%,這一比例明顯高出許多。
另外根據(jù)統(tǒng)計(jì)排名,前 100 位的 Emoji 占據(jù)了所有用戶日常使用 Emoji 總量的 82%,但在約 20% 的情況下,當(dāng)用戶發(fā)送一個(gè) Emoji 后,對(duì)方看到的卻是豆腐塊或一張損壞的圖像,這種情況直接導(dǎo)致用戶無(wú)法正確通過(guò) Emoji 來(lái)傳遞自己的本意。如下圖所示,用戶發(fā)送了一張含淚的笑臉,但對(duì)方卻只收到一個(gè)中間有 X 的方塊 (我們稱(chēng)其為豆腐塊)。
△Emoji 在發(fā)送和接受方的不同顯示效果
隨著用戶的增多,Unicode 也在不斷增加新的 Emoji 來(lái)體現(xiàn)多元化和包容性,但是 Android 卻并不能完全兼容這些新版的 Emoji,不同的 Android 版本對(duì) Emoji 的支持程度也不同。比如以下的幾個(gè)例子:
△Emoji 無(wú)法在不同 Android 版本間正確表達(dá)多樣性和包容性
- 在 Android 7.0 Nougat 和更早版本上無(wú)法正確通過(guò) Emoji 表示膚色。用戶發(fā)送了一個(gè)表示深膚色手臂的 Emoji,但對(duì)方收到的卻是一個(gè)手臂和深色方塊的分解版本。
- 在 Android 8.0 Oreo 和更早版本上無(wú)法正確顯示代表中性的 Emoji。
- 在 Android 9 Pie 和更早版本上不支持顯示多人多膚色的 Emoji。
Android 平臺(tái)針對(duì) Emoji 的解決方案
以上問(wèn)題顯然會(huì)導(dǎo)致非常糟糕的用戶體驗(yàn),并且不利于用戶之間通過(guò) Emoji 進(jìn)行交流。我們的目標(biāo)就是確保所有 Android 用戶無(wú)論是使用哪種應(yīng)用,都能夠正常地使用每個(gè) Emoji。為此,從 Android 12 開(kāi)始,我們引入了可更新系統(tǒng)字體 (Updatable System Fonts),首先引入的便是 Emoji 字體,這也意味著開(kāi)發(fā)者再也不需要在搭載 Android 12 以及 12 以上版本的設(shè)備中考慮 Emoji 適配的問(wèn)題了,系統(tǒng)將默認(rèn)支持新的 Emoji。
但是考慮到時(shí)光機(jī)還沒(méi)有被發(fā)明出來(lái),我們也沒(méi)辦法穿越到過(guò)去把可更新系統(tǒng)字體塞到舊版本的設(shè)備中去。如果一直等待時(shí)光機(jī)被發(fā)明出來(lái)而什么也不做的話,Android 12 版本之前的設(shè)備就會(huì)一直顯示豆腐塊,或者以其他錯(cuò)誤的方式進(jìn)行渲染。因此我們還是做了些改進(jìn),讓您可以通過(guò)更新 Jetpack 庫(kù)
EmojiCompat
解決這一問(wèn)題:
https://developer.android.google.cn/guide/topics/ui/look-and-feel/emoji-compat
EmojiCompat 早在幾年前就已經(jīng)發(fā)布,2021 年我們對(duì)其做了很多改進(jìn),并將其整合至AppCompat 1.4版本中。為此我們開(kāi)發(fā)了一個(gè)新庫(kù) androidx.emoji2,并添加了自動(dòng)配置選項(xiàng),它可自行配置以加載正確的字體。我們將這個(gè)庫(kù)集成到了 AppCompat 1.4 中,也就是說(shuō),您僅需升級(jí)至 AppCompat 1.4 版本,便可在 API 19 及更高的版本上正常顯示新加入的 Emoji,開(kāi)箱即用,無(wú)需任何額外配置。
-
AppCompat 1.4 版本
https://developer.android.google.cn/jetpack/androidx/releases/appcompat#1.4.1 -
androidx.emoji2
https://developer.android.google.cn/jetpack/androidx/releases/emoji2
AppCompat 1.4 針對(duì) Emoji 的優(yōu)化
△Emoji2 和 Emoji 對(duì)比
Emoji2 庫(kù)是 AppCompat 庫(kù)的一個(gè)新的依賴項(xiàng),雖然它會(huì)代替現(xiàn)有的 androidx.emoji 庫(kù),但是 API 幾乎相同。在此次更新中,我們使用 androidx.startup 添加了新的初始化程序 (EmojiCompatInitializer),添加了新的默認(rèn)配置,并且全部支持了 nullability 注解。另外,相較于 androidx.emoji,我們還刪除了一些在使用 AppCompat 時(shí)不再需要的 TextView 子類(lèi),這使得在 RA 之后節(jié)省了約 14KB 的大小。
- Emoji2https://developer.android.google.cn/jetpack/androidx/releases/emoji2
- androidx.emojihttps://developer.android.google.cn/jetpack/androidx/releases/emoji
△Emoji2 加載 Emoji 的步驟
在這次更改中,一大新特性便是 EmojiCompatInitializer,它是一個(gè)使用了 androidx.startup 庫(kù)的初始化程序,在應(yīng)用啟動(dòng)時(shí)會(huì)自動(dòng)配置 EmojiCompat。我們已對(duì)該初始化程序的性能進(jìn)行了大量的調(diào)整,對(duì)于大多數(shù)應(yīng)用來(lái)說(shuō)使用默認(rèn)配置已經(jīng)完全足夠,但如果您需要對(duì)應(yīng)用啟動(dòng)做納秒級(jí)別的優(yōu)化,則可以考慮移除 startup 庫(kù)和創(chuàng)建線程所帶來(lái)的消耗。首先,確保先初始化 EmojiCompat,再執(zhí)行 Activity.onCreate,這可以保證每個(gè) TextView 都能顯示新版 Emoji。然后,可以像 EmojiCompatInitializer 一樣將 Emoji 字體加載延遲到首屏繪制之后,這樣做是因?yàn)殡m然加載過(guò)程是在后臺(tái)線程中進(jìn)行的,但它還是執(zhí)行大量的網(wǎng)絡(luò)和磁盤(pán) I/O 操作,這些操作會(huì)同首屏加載一起搶奪資源。
這里再次強(qiáng)調(diào),我們已對(duì)該初始化程序的性能進(jìn)行了大量的調(diào)整和優(yōu)化,除非必要,請(qǐng)使用 EmojiCompatInitializer 的默認(rèn)實(shí)現(xiàn)。
另一個(gè)重要的功能是默認(rèn)配置,在 androidx.emoji 中您需要從文檔的示例代碼中復(fù)制一些模版配置 (類(lèi)似于以上代碼) 到應(yīng)用中,而 Emoji2 中我們添加了可以直接用于 EmojiCompatInitializer 的 DefaultEmojiCompatConfig,如下代碼所示,只需一行簡(jiǎn)單配置即可,當(dāng)然也支持手動(dòng)配置的需求。
在 AppCompat 中,我們將 Emoji2 集成到了所有的視圖中,這意味著所有視圖都可以支持新版 Emoji,如果您的 Activity 繼承了 AppCompatActivity,在 XML 中直接使用 TextView 或 EditView 即可。AppCompatActivity 安裝了一個(gè)布局填充器 (LayoutInflater),它會(huì)用 AppCompatTextView 來(lái)替換 TextView 等視圖,在代碼中無(wú)論何時(shí)創(chuàng)建 TextView,都應(yīng)該確保創(chuàng)建的是 AppCompatTextView,并且自定義視圖應(yīng)該繼承相應(yīng)的 AppCompat 子類(lèi)。
所有集成了 Emoji2 的視圖都有一個(gè) EmojiCompatEnabled 屬性,通過(guò)它可以控制是否開(kāi)啟 EmojiCompat,該屬性還提供了 getter 和 setter 方法。EmojiCompatEnabled 屬性有助于在知道文本絕不可能包含 Emoji 的情況下,來(lái)規(guī)避執(zhí)行 Emoji 的處理邏輯,雖然即使不規(guī)避該邏輯所帶來(lái)的成本也是極低的,但在某些情況下每一納秒都至關(guān)重要,此屬性便是為了支持這種情況。另外,該屬性對(duì)于在后臺(tái)線程上處理 Emoji 也很有幫助,AppCompat 對(duì)于 Emoji2 的集成會(huì)在 setText 之后的適當(dāng)時(shí)間調(diào)用 EmojiCompat.process,您可以通過(guò) EmojiCompatEnabled 屬性禁用此方法調(diào)用,并對(duì) Emoji 的處理移至后臺(tái)線程。但通常這種優(yōu)化沒(méi)必要,除非是在 RecyclerView 中展示大量的文本導(dǎo)致卡頓,那么可以考慮采用這一優(yōu)化方案。
測(cè)試新版 Emoji
由于集成了可下載字體,對(duì)于測(cè)試新版的 Emoji 并不是那么容易。要?jiǎng)?chuàng)建一個(gè)不會(huì)導(dǎo)致誤報(bào)或漏報(bào)的通用自動(dòng)化測(cè)試庫(kù)很難,而大多數(shù)開(kāi)發(fā)者在實(shí)際情況下會(huì)直接手動(dòng)測(cè)試 EmojiCompat 的集成,因此最好的選擇還是使用一個(gè)記錄了用于測(cè)試的 Emoji 列表,同樣此方式對(duì)于手動(dòng)測(cè)試或?qū)ζ聊唤貓D進(jìn)行測(cè)試也都非常有用。
如果您希望了解更多信息,請(qǐng)查看文檔:支持新式表情符號(hào)。我們?cè)谖臋n中為您提供了一些關(guān)于配置測(cè)試模擬器和設(shè)備所需要的一些操作建議:
https://developer.android.google.cn/guide/topics/ui/look-and-feel/emoji2
Emoji 渲染原理
△一組碼點(diǎn)
Emoji 屬于一種圖形字符,是字符串的一部分。它就像字母 "I"一樣,只是繪制方式和從屬的字體文件不同而已。但是對(duì)于計(jì)算機(jī)來(lái)說(shuō),它并不會(huì)特意關(guān)心什么是 Emoji 或字母 "I",一個(gè)字符串本質(zhì)上就只是一組碼點(diǎn),其中的數(shù)字通過(guò) Unicode 進(jìn)行分配,代表著計(jì)算機(jī)上會(huì)出現(xiàn)的每一個(gè)字符。
現(xiàn)在 Unicode 并不僅僅只是一種格式了,它還代表了制定該標(biāo)準(zhǔn)的委員會(huì),委員會(huì)會(huì)決定一些事情,比如數(shù)字 7 代表字母 "I" (實(shí)際上 7 并非真正代表字母 I 的碼點(diǎn),此處僅僅是舉個(gè)例子)。那么當(dāng)您試圖在 Android 上渲染上述表示字符串的碼點(diǎn)時(shí)會(huì)發(fā)生什么呢?
首先,Android 會(huì)根據(jù)碼點(diǎn)和應(yīng)用要求使用的字體樣式為每個(gè)字符找出最佳字體。當(dāng)前 Android 上非斜體且正常粗細(xì) "V"的默認(rèn)字體是 roboto-regular.ttf,Android 會(huì)對(duì)字符串進(jìn)行遍歷,檢查每個(gè)字符并查找最佳字體。它會(huì)檢查碼點(diǎn)和樣式,您可以對(duì)字符串進(jìn)行樣式的定制操作,比如對(duì)一些字符進(jìn)行加粗等等。對(duì)于上述簡(jiǎn)單的字符串來(lái)說(shuō),它就只是會(huì)選擇 roboto-regular.ttf 字體。
△遍歷碼點(diǎn)查找正確的字符串
但是,當(dāng)遇到 Emoji 字符時(shí),您可能會(huì)覺(jué)得它會(huì)進(jìn)行完全不同的渲染方式,畢竟它看起來(lái)不像任何其他的字母。但實(shí)際上,Emoji 就是個(gè)文本,由碼點(diǎn)表示,同字母 "I"和 "I"一樣沒(méi)什么區(qū)別,繪制它的方式就存儲(chǔ)到了字體中。Android 會(huì)首先嘗試在字體中查找無(wú)斜體且正常粗細(xì)的 "融化臉",但這一次發(fā)現(xiàn)在 roboto-regular 中并沒(méi)有想要的結(jié)果,便會(huì)去 NotoColorEmoji 中進(jìn)行查找,這是 AOSP 上預(yù)裝的 Emoji 字體,它包含了每個(gè) Emoji 的圖像,在 Android 平臺(tái)上通過(guò)這種字體繪制 Emoji 和繪制字母 "I"的方式完全相同,都是查找字體文件后在屏幕上繪制出來(lái)。
△通過(guò) NotoColorEmoji 對(duì) Emoji 字符進(jìn)行繪制
在 Android 12 及以上版本中,平臺(tái)可以確保 Emoji 會(huì)正常顯示,因?yàn)榭筛孪到y(tǒng)字體會(huì)將新版 Emoji 添加到字體文件中。但對(duì)于 Android 12 之前的版本,我們沒(méi)有任何方法可以更新字體,這意味著 Android 不知道用什么字體來(lái)繪制 "融化臉",此時(shí)它會(huì)改為繪制一個(gè)稱(chēng)為豆腐塊的備用字形。這里就是 Emoji2 開(kāi)始大展身手的地方了。
△Emoji2 對(duì) Emoji 字符的繪制過(guò)程
在將字符串發(fā)送到 Android 系統(tǒng)之前,在字符串上會(huì)調(diào)用 EmojiCompat.process 方法,此調(diào)用將遍歷并查找那些系統(tǒng)不知道如何繪制的 Emoji,并為每個(gè) Emoji 添加一個(gè) EmojiSpan,這是一個(gè)替換 Span,這意味著它將只替換該段字符串中對(duì)應(yīng)的內(nèi)容。系統(tǒng)會(huì)直接使用 roboto-regular.ttf 正常繪制,但當(dāng)找到 EmojiSpan 時(shí)它會(huì)將繪制權(quán)轉(zhuǎn)交給 Span。
在該 Span 中 Android 使用了兩個(gè)方法,首先,它會(huì)獲取字符尺寸并告訴 Android 要在文本布局中為此 Span 保留多少空間,然后,當(dāng)需要繪制字符串時(shí),它將調(diào)用 EmojiSpan 上的 draw 而非自行繪制。在 EmojiSpan 中,它知道 Compat 版的 Emoji 字體位置,并能直接從中繪制出 "融化臉"。再返回到渲染階段,平臺(tái)將調(diào)用 EmojiSpan.draw,整個(gè)區(qū)域?qū)⒂?EmojiSpan 進(jìn)行繪制,而非平臺(tái)。實(shí)際上,從平臺(tái)的角度來(lái)看 EmojiSpan 只是在字符串中間繪制了一張圖片,并沒(méi)有別的特殊操作。
總結(jié)
本文通過(guò)分析 Emoji 在 Android 平臺(tái)存在的問(wèn)題,針對(duì)不同版本的 Android 系統(tǒng)介紹了兩種解決方案:
- Android 12 及以上的版本使用可更新系統(tǒng)字體,無(wú)需開(kāi)發(fā)者手動(dòng)適配;
-
Android 12 以下的版本集成 AppCompat 1.4 也可自動(dòng)適配新版 Emoji,無(wú)需額外操作。
此外,我們還介紹了 Emoji 的渲染原理,讓您更進(jìn)一步了解 Emoji 是如何呈現(xiàn)在屏幕上的。請(qǐng)記得升級(jí) AppCompat 到 1.4 版本,為用戶提供支持新版 Emoji 的最佳體驗(yàn)。
來(lái)源:谷歌開(kāi)發(fā)者
-
Andorid
+關(guān)注
關(guān)注
0文章
7瀏覽量
7024
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
上能電氣埃及Abydos儲(chǔ)能項(xiàng)目順利發(fā)貨
[迅為RK3568開(kāi)發(fā)板]非科班也能玩轉(zhuǎn)Android應(yīng)用,體驗(yàn)QT跨平臺(tái)能力
![[迅為RK3568開(kāi)發(fā)板]非科班<b class='flag-5'>也</b>能玩轉(zhuǎn)<b class='flag-5'>Android</b>應(yīng)用,體驗(yàn)QT跨<b class='flag-5'>平臺(tái)</b>能力](https://file1.elecfans.com/web3/M00/02/F1/wKgZO2diirGAPoENAACpt0CV3xc346.png)
Android XR:耳機(jī)和眼鏡進(jìn)入 Gemini 時(shí)代
非科班也能玩轉(zhuǎn)Android應(yīng)用,體驗(yàn)QT跨平臺(tái)能力-迅為RK3568開(kāi)發(fā)板
TLV320AIC3263有Android平臺(tái)的驅(qū)動(dòng)代碼及寄存器配置表嗎?
Android案例分享,基于瑞芯微RK3568國(guó)產(chǎn)平臺(tái)!
![<b class='flag-5'>Android</b>案例分享,基于瑞芯微RK3568國(guó)產(chǎn)<b class='flag-5'>平臺(tái)</b>!](https://file1.elecfans.com/web2/M00/F0/C4/wKgZomZzjdOAGrLFAAQr6pizg1o478.png)
Android案例分享,基于瑞芯微RK3568國(guó)產(chǎn)平臺(tái)!
![<b class='flag-5'>Android</b>案例分享,基于瑞芯微RK3568國(guó)產(chǎn)<b class='flag-5'>平臺(tái)</b>!](https://file.elecfans.com/web2/M00/7E/BA/poYBAGOHAv6AbvjgAAA8o4Btlss933.png)
鴻蒙ArkUI-X跨語(yǔ)言調(diào)用說(shuō)明:【平臺(tái)橋接開(kāi)發(fā)指南(Android)Bridge API】
![鴻蒙ArkUI-X跨語(yǔ)言調(diào)用說(shuō)明:【<b class='flag-5'>平臺(tái)</b>橋接開(kāi)發(fā)指南(<b class='flag-5'>Android</b>)Bridge API】](https://file1.elecfans.com/web2/M00/C5/D1/wKgZomYChGOAUaiiAADe1d8SeRY102.jpg)
鴻蒙ArkUI-X跨語(yǔ)言調(diào)用說(shuō)明:【平臺(tái)橋接開(kāi)發(fā)指南(Android)】
![鴻蒙ArkUI-X跨語(yǔ)言調(diào)用說(shuō)明:【<b class='flag-5'>平臺(tái)</b>橋接開(kāi)發(fā)指南(<b class='flag-5'>Android</b>)】](https://file1.elecfans.com/web2/M00/C5/D1/wKgZomYChGOAUaiiAADe1d8SeRY102.jpg)
晶能半導(dǎo)體順利通過(guò)鄧白氏(Dun & Bradstreet)權(quán)威認(rèn)證
![晶<b class='flag-5'>能</b>半導(dǎo)體<b class='flag-5'>順利</b>通過(guò)鄧白氏(Dun & Bradstreet)權(quán)威認(rèn)證](https://file1.elecfans.com/web2/M00/E9/13/wKgaomZOmPSAS56rAAAWLgAzcpY063.jpg)
鴻蒙ArkUI-X跨平臺(tái)開(kāi)發(fā):【bility開(kāi)發(fā)說(shuō)明(Android平臺(tái))】
![鴻蒙ArkUI-X跨<b class='flag-5'>平臺(tái)</b>開(kāi)發(fā):【bility開(kāi)發(fā)說(shuō)明(<b class='flag-5'>Android</b><b class='flag-5'>平臺(tái)</b>)】](https://file1.elecfans.com/web2/M00/E7/51/wKgZomZLTACALdbCAAF8cNlebLs566.jpg)
評(píng)論