支持 Google Emoji 字体策略

谷歌推出了Android Emoji 字体政策:Android Emoji 政策,强制要求应用程序开发人员支持最新版本的 Unicode Emoji。该政策规定

在新的 Unicode Emoji 发布后 4 个月内,使用自定义表情符号实现(包括第三方库提供的自定义表情符号实现)的应用程序在 Android 12+ 上运行时,必须完全支持最新的 Unicode 版本。

本指南介绍了如何通过捆绑表情符号字体或使用Android 来支持这一政策:Google 可下载字体

捆绑表情符号字体 VS 谷歌可下载字体

这两种支持最新表情符号的方法各有利弊。以下是两种方法的优缺点:

捆绑字体的优点:

  • 字体加载速度更快
  • 用户没有网络时也能使用
  • 适用于所有操作系统
  • 独立(除 Qt 外无其他依赖关系)
  • 更简单的解决方案

捆绑字体的缺点:

  • 增加应用程序大小(NotoColorEmoji 约为 10 MB)
  • 需要在新版本中更新字体
  • 旧版应用程序不会自动更新表情符号

谷歌可下载字体的优点:

  • 不会改变应用程序大小
  • 自动更新
  • 没有任何关系的多个应用程序共享同一字体

谷歌可下载字体的缺点:

  • 取决于谷歌移动服务
  • 仅限安卓系统
  • 如果之前未缓存字体,则会下载字体
  • 如果之前未缓存字体,则在没有互联网的情况下无法使用
  • 比捆绑字体更复杂

如何捆绑字体

有必要获取并捆绑字体,然后使用 QML 或 C++ 加载字体。

获取字体

在本指南中,我们将使用 GoogleNotoColorEmoji 字体。NotoColorEmoji 是一种经SIL OPEN FONT LICENSE 许可的字体。

注意: 如果从版本库下载,请下载 NotoColorEmoji_WindowsCompatible.ttf 字体,而不是 NotoColorEmoji.ttf。NotoColorEmoji.ttf内部采用了不同的格式,只有安卓/Chrome/Chromium操作系统才支持该字体。由于 Qt 可在其他平台上运行,因此 Qt 字体加载器需要标准的 TrueType/OpenType 字体。

添加字体

捆绑字体的正确方法是将其添加到Qt 资源系统文件中。例如,你可以为字体创建一个单独的资源文件--包含 NotoColorEmoji_WindowsCompatible.ttf 的 "font.qrc"。要嵌入新的资源文件,请在 CMakeLists.txt 中使用以下代码:

qt_add_big_resources(PROJECT_SOURCES font.qrc)

用 C++ 加载捆绑字体

要使用 C++ 加载字体,请使用QFontDatabase

// Loading NotoColorEmoji bundled using C++ QFontDatabase
QFontDatabase::addApplicationFont(QStringLiteral(":/NotoColorEmoji_WindowsCompatible.ttf"));

注意: 上述代码应在QQmlApplicationEngine 加载 QML 之前使用,因此当加载 QML 时,字体已经存在并准备就绪。

在 QML 中加载捆绑字体

要在 QML 中加载字体,请使用FontLoader

// Loading NotoColorEmoji using QML FontLoader
FontLoader {
   source:"NotoColorEmoji_WindowsCompatible.ttf"
}

使用 Google 可下载字体:

使用 Google 可下载字体作为表情符号字体,可自动更新表情符号字体,而不会增加应用程序的大小。使用 "可下载字体 "功能下载字体的过程可在Android中查看更多详情:可下载字体流程

本指南的流程如下

  1. C++ 代码启动
  2. C++ 调用 Java 函数
  3. Java 调用 GDF 获取字体
  4. Java 打开字体 URI
  5. Java 向 C++ 返回文件描述符
  6. C++ 使用QFontDatabase

配置

Google 可下载字体适用于 API 级别 26(Android 8.0)。但如果应用程序使用的是 AndroidX,则有可能支持更早的 API,直至 API 14。

注: Android 文档中提到的是Android.Support Library,而不是 AndroidX.Support Library:支持库,而不是 AndroidX。但由于支持库已不再维护并被 AndroidX 取代,因此我们按照 Google 的建议使用 AndroidX。

自定义 Android 软件包模板

首先,需要自定义 Android 软件包模板。为此,请在Qt Creator 中进入 "项目 "选项卡,然后在 "构建设置 "中搜索 "构建 Android APK"。它应该在 "Build Steps(构建步骤)"里面,展开详细信息,就会出现一个名为 "Create Templates(创建模板)"的按钮。

创建模板

点击 "创建模板",按照向导操作,最后会创建一个包含多个 Android 配置文件的文件夹。默认情况下,该文件夹位于项目目录内,名为android

有关如何使用 qmake 自定义Android 模板的信息,请参阅Android 软件包模板

如果你使用的是 CMake 和 Qt 6,就像本指南一样,你需要设置QT_ANDROID_PACKAGE_SOURCE_DIR属性。示例

set_property(TARGET emojiremotefont PROPERTY
         QT_ANDROID_PACKAGE_SOURCE_DIR
         ${CMAKE_CURRENT_SOURCE_DIR}/android)

添加 AndroidX

要添加 AndroidX,请打开上面添加的QT_ANDROID_PACKAGE_SOURCE_DIR文件夹中的build.gradle 文件,并在其中添加依赖关系:

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
    implementation 'androidx.appcompat:appcompat:1.4.1'
}

要使用 Androidx,我们需要设置相应的标志。为此,请在QT_ANDROID_PACKAGE_SOURCE_DIR中创建一个名为gradle.properties 的文件,并添加这一行:

android.useAndroidX=true

添加字体提供程序证书

由于我们使用的是 AndroidX,因此还需要进行另一项配置--添加Android.Font Provider Certificates:字体提供程序证书。要使用 GMS 字体提供程序,请下载Android.GMS 字体提供程序证书:GMS 字体提供程序证书。如果使用其他字体提供商,则需要从提供商处获取证书。

下载文件后,将其复制到 android 模板文件夹中的values 文件夹,添加到 Android 资源(而不是 Qt 资源系统)中。下图显示了 (1) 上的正确文件夹:

安卓模板文件夹

Java 代码

好了,现在让我们开始编写代码!

我们需要在 Android 模板中添加Java/Kotlin 代码。将其放在 android 模板文件夹中的src 文件夹下。您可能需要创建src 文件夹和 Java 文件的文件夹结构。您可以在上一节中的 Android 模板文件夹图片(2)中看到该文件夹结构。

要获取 C++ 中的字体,Java 代码需要

  • 创建字体请求
  • 使用字体请求从 FontsContractCompat 中获取字体
  • 获取字体信息和字体 URI(内容方案文件)
  • 打开 URI 并获取文件描述符
  • 将文件描述符返回给 C++ 代码

要创建字体请求,您需要字体提供者信息(授权、软件包和证书)以及字体搜索查询。关于证书,请使用之前添加到 Android 资源中的 GMS 字体提供者证书文件fonts_cert.xml

// GMS fonts provider data
private static final String PROVIDER_AUTHORITY = "com.google.android.gms.fonts";
private static final String PROVIDER_PACKAGE = "com.google.android.gms";

// Emoji font search query (copied from EmojiCompat source)
private static final String EMOJI_QUERY = "emojicompat-emoji-font";

// Font Certificates resources strings (from fonts_certs.xml)
private static final String FONT_CERTIFICATE_ID = "com_google_android_gms_fonts_certs";
private static final String FONT_CERTIFICATE_TYPE = "array";

(...)

// obtain id for the font_certs.xml
int certificateId = context.getResources().getIdentifier(
                              FONT_CERTIFICATE_ID,
                              FONT_CERTIFICATE_TYPE,
                              context.getPackageName());

// creating the request
FontRequest request = new FontRequest(
                              PROVIDER_AUTHORITY,
                              PROVIDER_PACKAGE,
                              EMOJI_QUERY,
                              certificateId);

现在,使用刚才的请求获取字体:

// fetch the font
FontsContractCompat.FontFamilyResult result =
     FontsContractCompat.fetchFonts(context, null, request);

获取FontInfo 和 URI:

final FontsContractCompat.FontInfo[] fontInfos = result.getFonts();
final Uri emojiFontUri = fontInfos[0].getUri();

从 URI 打开一个新的本地文件描述符:

final ContentResolver resolver = context.getContentResolver();
// in this case the Font URI is always a content scheme file, made
// so the app requesting it has permissions to open
final ParcelFileDescriptor fileDescriptor =
            resolver.openFileDescriptor(fontInfos[0].getUri(), "r");

// the detachFd will return a native file descriptor that we must close
// later in C++ code
int fd = fileDescriptor.detachFd();

// return fd to C++

注: 所有用 Java 编写的代码都可以用 JNI 在 C++ 中完成。本指南中介绍的代码经过了简化。生产就绪的代码必须经过检查,包括异常捕获等...

C++ 代码

好了,Java 方面的工作都完成了。让我们来看看 C++ 方面。

C++ 负责调用 Java 代码,并使用文件描述符将字体加载到 Qt 中。

要深入了解 Qt 6 中 C++ 和 Java 之间的通信是如何工作的,请查看Qt for Android Notifier示例。

从 Java 代码中获取文件描述符后,将文件描述符封装到QFile 类中,然后使用QFontDatabase 加载字体文件:

QFile file;
file.open(fd, QFile::OpenModeFlag::ReadOnly, QFile::FileHandleFlag::AutoCloseHandle);

QFontDatabase::addApplicationFontFromData(file->readAll());

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.