支持Google Emoji字体策略

谷歌推出了一个Android: Android Emoji策略,强制要求应用开发者支持Unicode Emoji的最新版本。策略说明

使用自定义emoji实现的应用程序,包括第三方库提供的应用程序,必须在新的Unicode Emoji发布后4个月内,在Android 12+设备上完全支持最新的Unicode版本。

本指南展示了如何通过捆绑emoji字体或使用Android: Google下载字体来支持此策略。

捆绑emoji字体与Google下载字体

对于支持最新emoji,这两种方法都有一些优缺点。最佳选项取决于每个应用程序。以下是两种方法的优缺点:

捆绑字体的优点

  • 字体加载速度更快
  • 用户没有互联网时也能工作
  • 在所有操作系统上都能工作
  • 独立(没有除Qt之外的其他依赖项)
  • 解决方案简单

捆绑字体的缺点

  • 增加应用程序大小(NotoColorEmoji约为10MB)
  • 需要更新较新版本中的字体
  • 旧版本的应用程序不会自动更新emoji

Google下载字体的优点

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

Google下载字体的缺点

  • 依赖于谷歌移动服务
  • 仅限Android
  • 如果没有预先缓存将会下载字体
  • 没有预先缓存且没有互联网时不工作
  • 比捆绑字体更复杂

如何捆绑字体

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

获取字体

在本指南中,我们将使用谷歌的NotoColorEmoji字体。NotoColorEmoji字体是根据SIL OPEN FONT LICENSE许可的。

注意:如果从存储库下载,请下载NotoColorEmoji_WindowsCompatible.ttf字体而不是NotoColorEmoji.ttf。NotoColorEmoji.ttf使用不同的格式构建,并且只能在Android/Chrome/Chromium OS上得到良好支持。由于Qt运行在其他平台,Qt字体加载器需要一个标准格式的TrueType/OpenType字体。

添加字体

正确捆绑字体的方法是将其添加到Qt资源系统 文件中。例如,您可以创建一个单独的资源文件用于字体 - “font.qrc”,其中包含NotoColorEmoji_WindowsCompatible.ttf。要在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字体为emoji字体提供了一种自动更新的emoji字体,而不会增加应用程序的大小。使用可下载字体功能的下载字体的过程可以在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 14的较早API。

注意:Android文档中使用的是Android:支持库而不是AndroidX。但由于支持库不再维护且被AndroidX取代,我们遵循Google的建议使用AndroidX。

自定义Android包模板

首先,必须自定义Android包模板。为此,在Qt Creator中,转到“项目”选项卡,然后在“构建设置”中搜索“构建Android APK”。它应位于“构建步骤”中,展开详细信息,将出现一个名为“创建模板”的按钮。

"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: 字体提供者证书。要使用GMS字体提供者,下载Android: GMS字体提供者证书。如果使用其他字体提供者,需要从提供者本身获取证书。

下载文件后,通过将其复制到android模板文件夹中的values文件夹,将其添加到Android资源(不是Qt资源系统)。以下图片展示了正确的文件夹位置(1)。

"Android Templates Folder"

Java代码

好的,我们现在深入代码!

我们需要将Java/Kotlin代码添加到我们的Android模板中。将其放置在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);

获取字体信息和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 Android Notifier示例。

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

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

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

© 2024 The Qt Company Ltd。本文件中的文档贡献是各自所有者的版权。本文件中的文档是根据Free Software Foundation发布的GNU自由文档许可版1.3许可的。Qt及其相关标志是The Qt Company Ltd在芬兰和其他国家/地区的商标。所有其他商标均为其各自所有者的财产。