Emoji 讓你這麼頭疼,那 EmojiCompat 是如何解決它的?

190

Hi,你們好,我是承香墨影!android

今天看題目就知道,繼續來分析 Android 下的 Emoji 。Google 新出的 Support 包裏,增長了一個 EmojiCompat,就是爲了解決 Emoji 的問題。算法

上一篇文章,已經分析瞭如何使用 EmojiCompat 的相關細節,不太瞭解的能夠先看看:《Android 開發,你趕上 Emoji 頭疼嗎?》,今天就換一個維度,來從源碼的角度看看 EmojiCompat 有什麼能讓咱們學習的實現細節!設計模式

Emoji 確實讓咱們頭疼,那咱們來分析 Google 給咱們的解決方案 EmojiCompat 。不過呢?分析源碼咱們就帶着問題來看它?本文嘗試弄清楚幾個問題?緩存

  1. EmojiCompat 如何保證兼容。
  2. EmojiCompat 的實現原理。
  3. 能不能幹掉 BundleEmoji 打入包內的 7.4MB 大小的字體包?

主要是分析 EmojiCompat 的一些細節,若是你對 EmojiCompat 有所瞭解,你會知道它會在 assets 裏,打包的時候,嵌入一個 NotoColorEmojiCompat.ttf 字體文件,這個字體包大概有 7.4MB ,會直接增長 Apk 的大小,那麼最後咱們嘗試解決讓它不直接增長在 Apk 裏。安全

1、EmojiCompat 基礎結構

EmojiCompat 使用起來,很是的方便,它包含幾個點。在以前的文章中,就已經說清楚了,建議仍是先看看以前的文章《Android 開發,你趕上 Emoji 頭疼嗎?》,這裏爲了保證文章的完整性,我這裏簡單回顧一下。服務器

EmojiCompat 會使用 ttf 字體,來作到 Emoji 的兼容支持,自己在 2010 年以後,Emoji 就已經在 Unicode 中分配了碼點,在那以後,Emoji 就是一個字體。app

EmojiCompat 使用的字體包,前面也提過,大概會有 7.4MB,而根據 EmojiCompat 使用的字體文件的來源,它被分爲兩種:ide

  1. 可下載的字體。
  2. Apk 包內捆綁的字體。

可下載的字體,是 com.android.support:support-emoji:VersionXXX原生支持的,但是它須要依賴 Google 服務,因此在國內大環境下,你基本上是用不上的。源碼分析

那麼除非你是在作海外產品,不然你只能使用第二種方式,Apk 內捆綁字體,這個時候你就須要使用 com.android.support:support-emoji-bundled:VersionXxx ,它會自動在 Apk 打包的時候,向 assets 目錄下,捆綁嵌入一個 NotoColorEmojiCompat.ttf 字體文件,它大概有 7.4MB。學習

這些,操做的其實都是 Emoji 的字符串,若是你想更方便的使用 EmojiCompat 還提供了一些 EmojiAppCompatXxx 控件,使用它只須要替換項目內須要顯示 Emoji 的 TextView 或者 EditText 等就能夠了,不過使用這些支持 Emoji 的控件,你須要引用 com.android.support:support-emoji-appcompat:Version 依賴。

好了,到這裏 EmojiCompat 的大體結構就已經清晰了,接下來咱們看看 EmojiCompat 的源碼細節。

2、EmojiCompat 的源碼細節

2.1 EmojiCompat.Config

EmojiCompat 在使用以前,須要調用 EmojiCompat.init() 來進行初始化,而 init() 方法,須要傳遞一個 Config 的抽象類,EmojiCompat 就是根據這個 Config 來決定這個字體文件的來源,究竟是從線上下載仍是 Apk 本地捆綁。

先來看 Config 的結構。

EmojiConfig

Config 這個抽象類,提供的幾個抽象方法,做用呢看名字就已經很清晰了。

這裏簡單介紹一下:

一、若是要監聽 EmojiCompat 的初始化狀態,須要使用 registerInitCallback()unregisterInitCallback() ,來增長監聽和解綁監聽。

二、若是要給 Emoji 增長一個背景色,可使用 setEmojiSpanIndicatorColor() 設置一個背景色,並使用 setEmojiSpanIndicatorEnabled() 將這個開關打開。

三、設置匹配度,使用 setReplaceAll() 進行配置。

Config 仍是很簡單的,這裏說一下 setReplaceAll() 方法,EmojiCompat 默認的模式會優先使用當前設備自帶的 System Font,在 System Font 不支持這個 Emoji 的時候,纔會使用 Support Font 來渲染 Emoji。

emoji-support

以前文章中,給的解決方案是直接將 ttf 字體,使用 Typeface 設置的到 TextView 上去,可是閱讀源碼以後,發現 EmojiCompat 實際上是提供了對應的策略的調整的,就是使用 setReplaceAll() 。固然,使用設置字體的方式也能解決問題,可是老是不及官方提供的方法好,這裏糾錯一下。

2.2 初始化不一樣字體的策略區分

前面提到了 Config ,而 EmojiCompat 用來區分不一樣的加載方式,使用的就是不一樣的 Config 實現類。

這裏就涉及到兩個類:

一、FontRequestEmojiCompatConfig

它能夠作到去請求可下載的 Emoji 字體,不過它依賴 Google 服務,在國內進本處於廢棄狀態。

可能這裏簡單看個名字,你會覺得它真的是去下載 Emoji 字體,其實並非。它是依賴 FontProviderHelper 來和系統中的 Google 服務,經過 FileProvider 進行交互,獲取到 Google 服務以前緩存好的字體包資源,若是沒有字體的緩存,就會由 Google 服務來下載字體。

使用這種方式,很是的優雅,在 Android 的生態下,直接使用系統已經提供好的字體資源,不須要咱們再去關心下載的邏輯,惋惜大多數狀況下咱們使用不到,因此這裏再也不過多介紹。

二、BundledEmojiCompatConfig

使用 BundledEmojiCompatConfig ,須要提早引入 support-emoji-bundled 依賴。

EmojiBound

新增長的依賴其實也很是的簡單,只有一個有效類,它就是咱們須要的 BundledEmojiCompatConfig,接下來咱們看看它內部是如何實現的。

BundledEmojiConfig

BundledEmojiCompatConfig 內部實現了 EmojiCompat.MetadataRepoLoader 來作初始化,它其中實際上是開了個新線程來進行初始化,具體邏輯在 InitRunnable 中。

BundleEmojiRunner

InitRunnable 主要是爲了初始化 MetadataRepo ,在此過程當中,會去加載前面提到的 NotoColorEmojiCompat.ttf 字體,這是一個耗時操做,被放在子線程中去完成。

本質上來講 init() 的過程,實際上就是爲了獲得的 MetadataRepo 對象,它是用來維護加載好的全部 Emoji 的數據,這裏記住它,以後會用到。

2.3 EmojiCompat 的初始化

前面提到,EmojiCompat 的初始化是須要調用它的 init() 方法,而 init() 方法內部其實就是一個最多見的單例,最終仍是調用的它本身私有的初始化方法。

EmojiCompatMethod

在 EmojiCompat 的構造方法中,會初始化一些必要的資源和從 Config 中提取一些默認配置。

這裏只想須要關注幾個點:

一、EmojiCompat 使用了 ReentrantReadWriteLock,因此大多數操做都是線程安全的。

二、EmojiCompat 大部分的實際操做,都是經過 mHelper 來實現的,不一樣的 Api Level 有不一樣的實現,也正是經過它,來作到版本兼容的。

EmojiCompat 的構造方法,最後一行會調用 mHelper.loadMetadata() 方法,Api Level 19 如下使用的 CompatInternal,其實就是空實現,沒有什麼有意義的代碼,咱們這裏主要關注 CompatInternal19 。

loadEmoji

在 CompatInternal19 的 loadMetadata() 中,會去調用 MetadataLoader.load() 方法,咱們這裏使用本地捆綁的方式加載 Emoji 字體,因此會調用到前面介紹的 BundledEmojiCompatConfig 中的 BundledMetadataLoader 去,經過監聽回調,來得到初始化的加載的用於存放 Emoji 字體信息的 MetadatatRepo 類。

2.4 EmojiCompat.process() 的過程

EmojiCompat 替換 Emoji,須要使用它的 process() 方法,當你初始化完成以後,就能夠調用它了,接下來咱們來分析一下 process() 是如何工做的。

process

process() 有多個重載方法,最終都會調用到這個參數最多的方法上。我這裏在截圖中隱藏掉了一些不重要的校驗邏輯,不過不影響閱讀。

process() 方法中,能夠經過 replaceStrategy 設置 Emoji 字體的替換規則,這裏和 Config 中的設置同樣。而最終,是藉助 EmojiProcessor.process() 來完成操做。

EmojiProcessor.process() 的實現邏輯仍是很清晰的,大致上作如下兩個事情。

一、首先判斷傳遞進來的 charSequence 是否是一個 Spannable 內,有沒有 EmojiSpan,有的話所有移除。

二、而後用一個循環去檢驗 charSequence 中是否有 Emoji,有的話,使用 EmojiSpan 包裝它。

下面是 EmojiProcessor.process() 的關鍵代碼。

emojiWhile

當 Action 爲 ACTION_FLUSH 的時候,就會使用 EmojiSpan 替換替換它。

2.5 EmojiAppCompatXxx 控件的邏輯

若是咱們不想直接使用 process() 來操做全部的字符串,可使用 EmojiCompat 提供的一些對 Emoji 支持的控件,這就須要引入 support-emoji-appcompat 依賴。

appCompat

在這個包裏,只定義了幾個可能須要顯示字體的控件,接下來咱們看看它們的實現邏輯,這裏拿 EmojiAppCompatTextView 來研究,其餘幾個大體是同樣的。

AppTextView

全部相關的操做都被包裝在了 EmojiTextViewHelper() 中,它須要把當前的 TextView 對象傳遞進去。而在 EmojiTextViewHelper 中,實際上關鍵代碼是它的 wrapTransformationMethod() 方法,在其中對 TransformationMethod 作了一個包裝,將普通的 TransformationMethod 包裝成了 EmojiTransformationMethod 。

TransformationMethod 有些朋友可能不太清楚他是幹嗎的,簡單來講,它會去替換當前顯示的內容,例如若是你設置一個 TextView 須要顯示 password,輸入的字符會被替換成星號(*),就是它乾的。

這裏使用 EmojiTransformationMethod 對其進行包裝,其實是想用 Emoji 字體來替換它本來的顯示,關鍵代碼在 getTransformation() 方法中。

getTrans

能夠看到,它實際上也是去調用的 EmojiCompat.get().process(),和你直接調用並無什麼不同。

好了,到這裏 EmojiCompat 源碼的全部相關細節

4、如何不讓ttf 字體打包在 Apk 中

對於 Emoji 的支持,確定是須要引入一些資源的,這一點是毋庸置疑的。只不過能不能讓這個資源不要被打包在 Apk 裏,我想對於任何 Apk ,一會兒增大 7.4MB,都是有壓力的。

那咱們來討論一下,如何解決這個問題,咱們從線上下載一個 NotoColorEmojiCompat.ttf 字體給 EmojiCompat 可不能夠?這樣雖然會增大服務器下載的流量,可是能夠節省 Apk 的體積。能作到這一步,接下來就是一個方案選擇的問題。

從 BundledEmojiCompatConfig 的源碼中,能夠了解到,它只是須要一個 ttf 的字體文件,而若是咱們參照它,重寫一個實現 Config 的類,就能夠作到從線上下載一個字體文件,使用這個下載的字體文件進行初始化。

好了,這樣思路就明確了,那咱們看看源碼細節,看是否能如此實現。

一、Config 是否可被實現。

一個 Config 中,須要使用的 EmojiCompat.Config 和 EmojiCompat.MetadataRepoLoader 都是 public 的,因此能夠被開發者自行實現的。

二、MetadataRepo.create() 能不能支持別的來源。

MetadataRepo 是 EmojiCompat 初始化的關鍵,初始化就是爲了獲得這個對象。而 MetadataRepo.create() 還提供了其餘幾個重載方法。

create

第一個參數都須要一個 Typeface ,而 Typeface 是能夠加載一個咱們指定目錄下的字體文件的,那就看第二個參數。

第二個參數,是一個 MetadataListReader 對象,它須要的其實就是這個字體文件的 InputStream。字體文件的輸入流,文件都有了,InputStream 必定也能拿到。

到這裏就清晰了,咱們徹底能夠在初始化的時候,從線上下載一個字體文件,而後再使用 MetadataRepo 去初始化它,最終將狀態返回給 EmojiCompat,來作到咱們不將 Emoji 字體打包在 Apk 內的目的。

思路很簡單,固然還須要額外處理一些下載失敗和尚未初始化完成的時候,如何顯示的問題,這裏就不提供示例代碼了。

到此,就分析完 EmojiCompat 的全部細節,不知道對你有什麼幫助?

你在作源碼分析的時候,有什麼技巧?能夠在留言中分析給你們!

今天在承香墨影公衆號的後臺,回覆『成長』。我會送你一些我整理的學習資料,包含:Android反編譯、算法、設計模式、虛擬機、Linux、Kotlin、Python、爬蟲、Web項目源碼。

另外還還維護了一個交流羣,有興趣能夠在公衆號後臺回覆:"加羣"

個人博客即將搬運同步至騰訊雲+社區,邀請你們一同入駐:https://cloud.tencent.com/developer/support-plan

相關文章
相關標籤/搜索