ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

第 47 章:国际化

第 47 章:国际化 Android 运行在全球近所有国家的三十亿台以上设备上。用户使用阿拉伯语、汉语、天城文、泰语、韩语以及数百种其他文字阅读文本。用户期望日期、数字、货币以及排序规则遵循本地习惯,还会在一次使用过程中切换多种语言。正确、高效地支持全部这些能力,同时不让应用开发者成为 Unicode 专家,是平台技术难度最高的领域之一。本章将深入实现这一切的国际化(i18n)底层基础设施。我们将完整梳理整个调用链路:提供 Unicode 算法的 ICU 库、跟踪用户偏好的区域设置管理系统、选择区域专属资源的资源限定符机制、从右向左(RTL)布局系统、为全球各类文字生成字形并做光栅化的文本渲染管线,以及提供实际字形轮廓的字体系统。47.1 AOSP 中的 ICU国际 Unicode 组件库(ICU)是 Android 几乎全部国际化能力的基础。它提供 Unicode 字符属性、标准化、排序、日期 / 时间格式化、数字格式化、转写、文本分段迭代器以及正则表达式支持。如果没有 ICU,Android 将无法正确对德语姓名列表排序、把泰语句子拆分为词语,或是格式化日式日期。Android 17 搭载 ICU 78.3,该版本实现了 Unicode 17.0 以及 CLDR 48.2 区域数据集。版本常量定义在external/icu/icu4c/source/common/unicode/uvernum.h:// external/icu/icu4c/source/common/unicode/uvernum.h #define U_ICU_VERSION_MAJOR_NUM 78 #define U_ICU_VERSION_MINOR_NUM 3 #define U_ICU_VERSION_PATCHLEVEL_NUM 0 #define U_ICU_VERSION "78.3" #define U_ICU_VERSION_SHORT "78"Unicode 版本在external/icu/icu4c/source/common/unicode/uchar.h中固定:// external/icu/icu4c/source/common/unicode/uchar.h #define U_UNICODE_VERSION "17.0"对比上一个版本(搭载 ICU 77),本次版本升级幅度很大。47.8 节会详细介绍升级带来的改动:新增 Unicode 17.0 码点与表情符号、更新 CLDR 排序与格式化数据、更新时区规则。由于 ICU 被打包在 i18n APEX 模块中(参见 47.1.3),新数据可以通过 Mainline 更新推送到设备,无需完整平台 OTA 升级。47.1.1 源码目录结构ICU 在 AOSP 中位于external/icu/,该目录体量庞大:external/icu/ icu4c/ # C/C++ 实现(libicuuc、libicui18n) source/ common/ # Unicode 基础能力:属性、标准化、文本分段迭代 unicode/ # 公共头文件(uchar.h, ustring.h, ubidi.h, unorm2.h, …) i18n/ # 高层服务:排序、格式化、转写 data/ # 编译后的 ICU 数据(.dat 文件) io/ # ICU IO(Android 上极少使用) icu4j/ # Java 实现(上游 ICU4J 项目) android_icu4j/ # Android 裁剪/维护的 ICU4J 子集 src/main/java/android/icu/ text/ # BreakIterator, Collator, Normalizer2, DateFormat, NumberFormat, … util/ # ULocale, Calendar, TimeZone, … lang/ # UCharacter(字符属性) number/ # 新版数字格式化(NumberFormatter) message2/ # MessageFormat 2.0(技术预览版) segmenter/ # 新版文本分段 API(Android 内部/@hide) impl/ # 内部实现类 android_icu4c/ # Android 专属 ICU4C 封装层 libandroidicu/ # 向 NDK 暴露稳定 ICU4C API 的共享库 libandroidicuinit/ # libandroidicu 的初始化适配层 libicu/ # 供平台内部使用的薄封装层 build/ # ICU 数据裁剪编译规则 tools/ # ICU 版本升级脚本源码路径:external/icu/47.1.2 双实现:ICU4C 与 ICU4JAndroid 同时提供 C/C++(ICU4C)和 Java(ICU4J)两套实现:库语言AOSP 路径使用者libicuuc.soC/C++external/icu/icu4c/source/common/Minikin、HarfBuzz、Skia、原生系统服务libicui18n.soC/C++external/icu/icu4c/source/i18n/原生格式化、排序逻辑android.icu.*Javaexternal/icu/android_icu4j/框架层、通过 SDK 调用的应用libandroidicu.soC(稳定接口)external/icu/libandroidicu/NDK 应用原生库属于关键路径依赖。每一次文本布局操作,从测量 TextView 到段落换行,都会调用 HarfBuzz,而 HarfBuzz 又会调用 ICU4C 获取 Unicode 字符属性与双向文本分析结果。47.1.3 ICU 数据ICU 的运行行为由一份编译好的数据文件驱动,其中包含区域规则、字符属性表、分段迭代规则、排序定制规则以及转写转换规则。在 AOSP 中该数据存放位置:external/icu/icu4c/source/data/编译阶段,数据会编译为.dat文件,安装到设备路径/apex/com.android.i18n/etc/icu/icudtmajorl.dat—— Android 17 上为icudt78l.dat,对应 ICU 主版本 78。该确切路径由external/icu/android_icu4j/testing/src/android/icu/extratest/platform/AndroidDataFilesTest.java断言校验,代码拼接路径为"/apex/com.android.i18n/etc/icu/icudt" + VersionInfo.ICU_VERSION.getMajor() + "l.dat"。 自 Android 10 起,ICU 作为 i18n APEX 模块(com.android.i18n)的一部分交付,允许 ICU 代码和数据独立于完整平台 OTA 进行更新。使用者:i18n APEX 模块ICU 数据(.dat)libicuuc.so + libicui18n.soandroid.icu.*(Java)libandroidicu.so(NDK)HarfBuzz(文本整形)Minikin(字体选择 / 布局)Skia(渲染)Java 框架(DateFormat 等)NDK 应用SDK 应用47.1.4 Unicode 字符属性查询ICU 最基础的服务就是字符属性查询。给定一个 Unicode 码点,ICU 可以返回其通用分类(字母、数字、标点)、双向类别(从左到右、从右到左、阿拉伯数字)、文字体系(拉丁语系、汉字、天城文)、是否为表情符号,以及数十种其他属性。C 语言 API 定义在external/icu/icu4c/source/common/unicode/uchar.h。关键函数:// 获取码点的通用分类 int8_t u_charType(UChar32 c); // 判断码点是否具备某个布尔属性 UBool u_hasBinaryProperty(UChar32 c, UProperty which); // 获取双向文本类别 UCharDirection u_charDirection(UChar32 c); // 获取码点所属文字体系 UScriptCode uscript_getScript(UChar32 c, UErrorCode *pErrorCode);Java 对应实现为android.icu.lang.UCharacter:// 获取通用分类 int type = UCharacter.getType(codePoint); // 查询双向类别 int dir = UCharacter.getDirection(codePoint); // 判断字符是否为字母 boolean isLetter = UCharacter.isLetter(codePoint);属性查询对性能要求极高。一段阿拉伯语与拉丁语混合的文本,在双向分析与文本整形阶段可能需要执行成千上万次属性查询。ICU 使用紧凑的字典树结构(UTrie2)存储数据,实现 O (1) 时间复杂度查询。47.1.5 文本标准化同一个视觉文本在 Unicode 中可以存在多种编码形式。字母 ä(U+00E4)也可以表示为字符 a(U+0061)加上组合分音符(U+0308)。标准化将文本转换为标准范式,保证等价的字符序列比较结果相等。ICU 提供四种标准化范式:范式名称说明NFC规范分解 + 组合尽可能对字符做组合(最常用)NFD规范分解将全部字符拆解为基字符 + 组合标记NFKC兼容分解 + 组合同时拆解兼容类字符NFKD兼容分解完整分解,包含兼容字符C API 位于external/icu/icu4c/source/common/unicode/unorm2.h:const UNormalizer2 *nfc = unorm2_getNFCInstance(status); int32_t len = unorm2_normalize(nfc, src, srcLen, dst, dstCap, status); UBool isNormalized = unorm2_isNormalized(nfc, src, srcLen, status);Minikin 的 FontCollection 在执行字体回退逻辑时会使用标准化。当首选字体找不到某个字符时,Minikin 会使用 NFD 将字符分解,再分别尝试查找基字符和组合标记。可以在 FontCollection 实现头文件看到该依赖:// frameworks/minikin/libs/minikin/FontCollection.cpp #include unicode/unorm2.h47.1.6 排序(Collation)文本正确排序远比直接比较字节值复杂。德语电话簿排序中 ä 等价于 ae;瑞典语中 ö 排在字母 z 之后;日语根据汉字读音存在多种排序规则。ICU 的排序引擎实现位于external/icu/icu4c/source/i18n/,通过区域专属定制规则支持上述全部场景。Java API:import android.icu.text.Collator; Collator collator = Collator.getInstance(Locale.GERMAN); int result = collator.compare("Muller", "Mueller"); // 具备区域感知的比较47.1.7 文本分段迭代文本分段迭代用于识别文本边界:字符、词语、句子、换行的起止位置。对于英语这类使用空格分词的语言该逻辑十分简单,但对于泰语、老挝语、高棉语、中文、日语这类词语之间没有空格的文字,该能力必不可少。ICU 提供五类分段迭代器:import android.icu.text.BreakIterator; // 词语边界(对泰语、高棉语、老挝语、缅甸语至关重要) BreakIterator wordIter = BreakIterator.getWordInstance(Locale.THAI); wordIter.setText(thaiText); // 换行边界(Minikin 换行器使用) BreakIterator lineIter = BreakIterator.getLineInstance(locale); // 句子边界(三击文本选择功能使用) BreakIterator sentIter = BreakIterator.getSentenceInstance(locale); // 字符(字形簇)边界 BreakIterator charIter = BreakIterator.getCharacterInstance(locale);BreakIterator 源码位置:Java:external/icu/android_icu4j/src/main/java/android/icu/text/BreakIterator.javaC:external/icu/icu4c/source/common/unicode/brkiter.h换行分段迭代器尤为重要,Minikin 在段落布局阶段调用它判断合法换行位置。47.1.8 日期、时间与数字格式化ICU 提供具备区域感知的日期、时间、数字、货币格式化能力:import android.icu.text.DateFormat; import android.icu.text.NumberFormat; import android.icu.number.NumberFormatter; // 日期格式化 DateFormat df = DateFormat.getDateInstance(DateFormat.LONG, Locale.JAPAN); String formatted = df.format(new Date()); // "2026年3月18日" // 数字格式化 NumberFormat nf = NumberFormat.getInstance(Locale.GERMANY); String num = nf.format(1234567.89); // "1.234.567,89" // 新版数字格式化器(ICU 60+) String currency = NumberFormatter.withLocale(Locale.US) .unit(Currency.getInstance("USD")) .format(42.99) .toString(); // "$42.99"相关类位于external/icu/android_icu4j/src/main/java/android/icu/text/以及external/icu/android_icu4j/src/main/java/android/icu/number/。47.1.9 ICU 版本管理ICU 会定期更新,跟进新版 Unicode 发布。升级流程文档写在external/icu/icu_version_upgrade.md,包含:导入上游新版 ICU 发布包重新生成 Android 裁剪数据集更新 android_icu4j 和 android_icu4c 封装层执行 CTS 与 ICU 兼容性测试更新 i18n APEX 模块由于 ICU 通过 APEX 交付,设备可以在不做完整平台 OTA 的前提下完成更新。这对于新增表情符号、新增文字体系、修正排序规则这类 Unicode 版本升级至关重要。47.2 区域设置管理区域设置(Locale)是语言、文字体系、地区、变体的组合,决定文本如何处理、格式化、展示。Android 的区域设置管理系统保存用户偏好、将配置应用到框架层,并对外提供 API,供应用查询和响应区域变更。47.2.1 LocaleList:有序区域偏好列表从 Android 7.0(API 24)开始,平台支持有序的区域偏好列表,不再局限于单一区域。例如用户优先法语,其次英语,再是德语。当资源没有法语版本,系统会回退到英语,再尝试德语。LocaleList 类定义位置: 源码路径:frameworks/base/core/java/android/os/LocaleList.java// frameworks/base/core/java/android/os/LocaleList.java public final class LocaleList implements Parcelable { private final Locale[] mList; private final String mStringRepresentation; public Locale get(int index) { return (0 = index index mList.length) ? mList[index] : null; } public int size() { return mList.length; } public boolean isEmpty() { return mList.length == 0; } // …… }LocaleList 是不可变、支持序列化的 java.util.Locale 对象列表。字符串表达形式为逗号分隔的 BCP‑47 语言标签,例如"fr‑FR,en‑US,de‑DE"。47.2.2 系统区域与应用区域Android 区分两类区域作用域:系统区域:用户在设置中设定,保存在persist.sys.locale(旧版)以及系统 Configuration,默认对全部应用生效。单应用区域:Android 13(API 33)通过 LocaleManager 引入,允许单个应用使用与系统不同的区域。47.2.3 LocaleManager 与 LocaleManagerServiceLocaleManager API 允许应用查询、设置单应用区域:// 设置单应用区域(API 33+) LocaleManager localeManager = getSystemService(LocaleManager.class); localeManager.setApplicationLocales(LocaleList.forLanguageTags("ja-JP,en-US")); // 获取单应用区域 LocaleList appLocales = localeManager.getApplicationLocales();LocaleManager 同时对外暴露系统区域列表(getSystemLocales()/setSystemLocales()),并且允许应用通过setOverrideLocaleConfig(LocaleConfig)在运行时覆盖自身声明的支持区域。该覆盖能力让应用无需重新打包,就可以扩大或缩小设置界面展示给该应用的语言列表;静态声明定义在frameworks/base/core/java/android/app/LocaleConfig.java。服务端实现源码路径:frameworks/base/services/core/java/com/android/server/locales/LocaleManagerService.java// frameworks/base/services/core/java/com/android/server/locales/LocaleManagerService.java package com.android.server.locales; /** * ILocaleManager.aidl 的实现。 * * 该服务作为API入口,用于存储应用专属UI区域,以及指定应用的LocaleConfig覆盖配置。 */ public class LocaleManagerService extends SystemService { // …… }该服务承担多项职责:职责说明单应用区域存储持久化保存区域偏好到磁盘配置覆盖应用启动时加载区域覆盖配置备份 / 恢复通过 LocaleManagerBackupHelper 完成区域偏好备份包监听通过 LocaleManagerServicePackageMonitor 监听应用安装卸载LocaleConfig 覆盖允许系统覆盖应用声明的支持区域同包下配套文件:LocaleManagerBackupHelper.java — 备份代理集成LocaleManagerServicePackageMonitor.java — 监听包变更LocaleManagerShellCommand.java — cmd locale_manager shell 命令接口
返回列表