Introduction
由于 android 系统的碎片化,导致了最新的 emoji 在老的手机上不支持。
微信也做得不好,很多 emoji 表情直接变成了 X ,不知道什么意思。
解铃还须系铃人, Android 官方为此做了不少努力。
Android Official Solution
EmojiCompat 的方案包含如下两种方式
- Downloadable fonts configuration (网络下载)
- Bundled fonts configuration (本地打包)把一个 7.4M 的 ttf 文件塞进 apk 中
这两种方式都有缺陷:
- 网络下载方式依赖于翻墙,门槛比较高
- 本地打包方式对 apk 的体积增大不可接受
有没有一种方式既不用翻墙又不增大 apk 的体积呢?
答案是有的
Best Practice
无论是网络下载还是本地打包,都涉及到读取 font 文件进行渲染,那么这里有两步
- 接管读取的流程
- 接管渲染的流程
Hook Loading Process
首先,我们在 gradle 中导入如下依赖
compile "com.android.support:support-emoji:$version"
compile "com.android.support:support-emoji-appcompat:$version"
compile "com.android.support:support-emoji-bundled:$version"
这三个依赖所包含的东西不同
support-emoji
包含 EmojiCompat 方案所需要的基本实现以及 Downloadable fonts configuration 的代码support-emoji-appcompat
主要是针对 AppCompat 的 UI 组件的支持support-emoji-bundled
是 Bundled fonts configuration 的代码
然后根据官网解决方案,编译打包成 apk 后,解压 apk ,取出 NotoColorEmojiCompat.ttf 文件(建议使用最新版本的 support 包) 放着备用,一会儿下锅(宽油警告?),哦不,上传到七牛或者其他云存储
接下来就不需要 support-emoji-bundled
了,也不需要 support-emoji
,因为 support-emoji-appcompat
包含了
新建一个类
class DownloadEmojiCompatConfig : EmojiCompat.Config(DownloadMetadataLoader()) {
private class DownloadMetadataLoader internal constructor() : EmojiCompat.MetadataRepoLoader {
@RequiresApi(19)
override fun load(loaderCallback: EmojiCompat.MetadataRepoLoaderCallback) {
// 这里只是关键部分,并不表示只有这一行
loaderCallback.onLoaded(MetadataRepo.create(Typeface.createFromFile(file.absolutePath), FileInputStream(file)))
}
}
}
其中 file 就是我们下载好的 ttf 文件
注意事项:
- 整个 EmojiCompat 只适用于 4.4 以上,再往下的就不支持了(也够用了)
- 既然是想通过下载来解决本地安装包增大的问题,那么就好考虑好 wifi 4g 等情况了, 7M 流量呢
然后就是 EmojiCompat 自己的初始化流程了
EmojiCompat.init(
DownloadEmojiCompatConfig()
.setReplaceAll(false)
.registerInitCallback(object : EmojiCompat.InitCallback() {
override fun onInitialized() {
emojiReady = true
}
override fun onFailed(throwable: Throwable?) {
emojiReady = false
}
})
注意事项:
- 放到其他线程来做,这个初始化时间至少 150ms
- 利用
InitCallback
回调来处理 ready 状态, ready 了后面渲染才可以用 - 小于 4.4 就不要初始化了
setReplaceAll
方法这里重点说明下- true 表示所有的 emoji 都替换为 ttf 里的,这样可以保证 emoji 风格统一
- false 表示优先使用系统支持的 emoji ,不支持才用 ttf 里的
此处使用 setReplaceAll(false)
是因为 ttf 里的 emoji 在渲染国旗的时候会出现问题(也包括中国国旗)
到这里,完成了 EmojiCompat 的 loading 流程
Hook Rendering Process
Activity 的 LayoutInflaterCompat.setFactory2 替换 xml 里的 View
新写 EmojiTextView
继承 AppCompatTextView
@Override
public void setText(CharSequence text, BufferType type) {
if (emojiReady) {
super.setText(EmojiCompat.get().process(text), type);
return;
}
super.setText(text, type);
}
而 EditText
就直接替换为 support-emoji-appcompat
里的 EmojiAppCompatEditText
即可
到这里,完成了 EmojiCompat 的 rendering 流程
事情到这里就结束了么?并没有,因为新的 emoji 一直会出来,而 android 的 support 包并不会及时更新
那怎么办,有一个开源的组织叫 emojione 这里 是它的官网
这个组织会发布自己的一套 emoji ttf 来兼容各种设备和统一不同风格的 emoji
也别高兴得太早,上一次更新是 v3 版本,落后于 Android 提供的 support 包的版本(就是支持的 emoji 比官方少)
不过,最近,它出了 v4 了,可以期待下,但目前 ttf 版本还是 coming soon 状态,可以订阅下以便及时收到邮件提醒
Job Ad
欢迎正在刷 即刻app 的你加入我们,一起参与千万级用户产品的研发和运营!
当然也包括 Android 职位啦
工作地点:上海市杨浦区创智天地
简历请发送至 hr@okjike.com 并在邮件标题中注明职位 + 姓名
一起打造明天的即刻 (^U^)ノ~YO
补一下 DownloadEmojiCompatConfig
的源码
class DownloadEmojiCompatConfig : EmojiCompat.Config(MetadataLoader()) {
private class MetadataLoader internal constructor() : EmojiCompat.MetadataRepoLoader {
private val nTtfFile: File = File(StoreUtil.internalFileDir, EMOJI_FILE_NAME)
override fun load(loaderCallback: EmojiCompat.MetadataRepoLoaderCallback) {
Observable.create<Any> {
try {
if (nTtfFile.exists() && nTtfFile.isDirectory) {
clearEmoji()
}
if (!nTtfFile.exists()) {
val downloadSuccess = FileUtil.downloadFile(CdnRes.ILLUSTRATION_EMOJI, nTtfFile)
if (!downloadSuccess) {
throw Exception("download ttf failed")
}
}
loaderCallback.onLoaded(MetadataRepo.create(Typeface.createFromFile(nTtfFile), FileInputStream(nTtfFile)))
it.onNext(Unit)
it.onComplete()
} catch (t: Throwable) {
loaderCallback.onFailed(t)
it.onError(t)
}
}
.compose(RxUtil.io())
.subscribe()
}
}
companion object {
private const val EMOJI_FILE_NAME = "emoji.ttf"
private const val TAG = "emoji"
/**
* 删除旧的 emoji 文件
*/
@JvmStatic
fun clearEmoji() {
File(StoreUtil.internalFileDir, EMOJI_FILE_NAME).deleteRecursively()
}
}
}