HarmonyOS鸿蒙Next中自定义字体注册成功后小概率文字不展示,求解

HarmonyOS鸿蒙Next中自定义字体注册成功后小概率文字不展示,求解

MainAbility.onCreate -> AppFontManager.getInstance().initialize(context) 只保存 UIAbilityContext,不提前做重事务

MainAbility.onWindowStageCreate -> loadContent(‘pages/Index’) -> loadContent 成功回调内执行原有事务: WindowManager.initialize setKeyboardAvoidMode persistAuthSessionStorage persistAccountStorage ThemeControl.setDefaultTheme -> 异步预注册启动字体 -> 注册内置字体 -> 注册本地已下载/导入字体 -> 失败只记录日志,不阻塞首页

点击进入练习页 -> 下载/解析目标字体 -> 注册目标字体 -> 注册成功后进入 LatticePage 或 Jianjia 练习界面 -> 注册失败 -> 提示:字体注册失败,请重试 -> 不进入练习页

这边能做的都做过了,每次不展示只有重新启动应用之后,才可能正常,尝试过把并发关了,加延时,都无效,还是有概率出现


更多关于HarmonyOS鸿蒙Next中自定义字体注册成功后小概率文字不展示,求解的实战教程也可以访问 https://www.itying.com/category-93-b0.html

13 回复

尊敬的开发者您好,

感谢您的反馈!为了更好地定位您遇到的问题,我们尝试在本地进行了复现,目前尚未成功。为了高效推进问题排查,烦请您协助提供以下关键信息:

  1. 可复现问题的完整Demo
  2. 问题发生的设备信息:设备型号、设备HarmonyOS版本号
  3. 复现场景描述: 如果问题非稳定出现,请尽可能详细描述操作步骤、环境条件等,也可提供问题复现时的屏幕录屏 (视频)

更多关于HarmonyOS鸿蒙Next中自定义字体注册成功后小概率文字不展示,求解的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


知道根因了,是因为字体注册失败之后,我没有去调用unloadFontSync取消它,导致我第二次注册成功,它拿的还是第一次的字体。但是有个疑问,这算不算系统bug啊,都没注册成功为什么能拿到。

开发者您好,如果您这边字体注册失败还能拿到对应字体信息的话。可以参考6楼需要的信息将相关信息提供给我们便于我们进一步上升处理,如有附件可以在评论区上传然后告知,我们这边会及时为您处理。

这种“小概率文字不展示”更像字体注册时序问题,不太像 Text/Canvas 本身随机丢字。重点看三点:字体是否在首帧绘制前已经注册、是否存在并发注册、Canvas 自绘是否在字体可用后主动重绘。

ArkUI 的 Font.registerFont 是异步注册思路,并且不适合多个页面/多个组件同时反复注册同一个 familyName。建议做成单例:同一字体只注册一次;页面使用前先确认路径存在;沙箱文件路径用 file:// 前缀;familyName 保持唯一,不要注册后又 unload/覆盖。

// ArkUI Text 场景:尽量在页面首帧前完成一次性注册
private fontRegistered: boolean = false;

ensureArkUIFont(uiContext: UIContext, src: string) {
  if (this.fontRegistered) {
    return;
  }
  uiContext.getFont().registerFont({
    familyName: 'ArticleFont',
    familySrc: src // 沙箱文件建议使用 file://xxx.ttf
  });
  this.fontRegistered = true;
}

Text(this.title).fontFamily('ArticleFont')

如果是 Canvas/NodeController 自绘文字,建议优先走 ArkGraphics2D 的 FontCollection,并在字体注册后 invalidate/redraw。这样比“页面已经 draw 了再等字体异步注册”更稳定。

import { text } from '@kit.ArkGraphics2D';

const fontCollection = text.FontCollection.getGlobalInstance();
fontCollection.loadFontSync('ArticleFont', $rawfile('article.ttf'));
// 注册后刷新使用该 fontCollection 的节点
renderNode.invalidate();

所以你的 ensureFontRegistered 可以加日志确认:首次进入时是否多次并发调用、失败机型上 src 是否可读、Canvas draw 是否早于注册完成。把注册提前并串行化后,偶现空白一般会明显减少。

参考文档:

https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-uicontext-font

https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-graphics-text

我这边是用的单例注册,而且也验证过跟并发没有关系,跟时序没有关系

开发者您好,您可以参考6楼需要的信息将相关信息提供给我们便于我们进一步分析,如有附件可以在评论区上传然后告知,我们这边会及时为您处理。

registerFont 成功只能说明字体注册请求完成,不一定代表当前帧的 Text/Canvas 已经稳定命中该 fontFamily。你这个“小概率不展示、重启后可能恢复”更像字体加载和渲染时序问题。

建议重点验证:

  1. 注册用的 familyName 和使用处 fontFamily 是否完全一致,包括大小写和字体文件内部 family 名。
  2. 字体文件下载完成后先校验文件大小、可读性,再注册。
  3. 注册成功后再切换一个状态 key,让使用字体的组件重建或重新绘制,不只靠延时。
  4. Canvas 绘制文字时,确认绘制发生在字体注册完成后的下一帧之后。
  5. 对失败字体做 fallback,不要让页面直接空白。

如果最小 demo 也能复现偶发空白,建议带脱敏 demo 和设备/API 版本提工单,这类问题可能在字体引擎或渲染缓存层。

这个问题建议再把“字体注册成功”和“渲染侧真正命中字体”分开验证。registerFont 成功只说明字体文件被接收,不代表 Text/Canvas 当前帧已经能稳定使用到该 font family。

可以重点查这几项:

  1. 注册时的 familyName 和页面使用的 fontFamily 是否完全一致,尤其注意字体文件内部 family 名称和你传入名称不一致的情况。
  2. 不要同一时间并发注册多个同名字体;同名覆盖容易出现缓存命中旧字体或空字体。
  3. 字体下载完成后先落到沙箱固定路径,再注册该路径,避免临时文件被释放或路径变化。
  4. 进入练习页前可以做一次极小 Text/Canvas 预热渲染,并在下一帧再展示正式内容,用来验证是不是渲染线程缓存时序问题。
  5. 如果是 Canvas 绘制字形,建议用 drawing.Typeface.makeFromRawFile/makeFromFile 方向单独验证,判断问题是在 ArkUI Text 组件还是 Graphics2D 绘制链路。

如果最小 demo 里“注册成功但同一字体偶现空白”也能复现,建议带上 API 版本、字体格式、注册路径、familyName 和脱敏最小代码提工单,这类更像字体缓存/渲染时序问题。

根据你描述的现象,我感觉这已经不像业务代码问题,而更像 HarmonyOS 字体引擎或 Canvas 渲染链路的时序问题

你现在已经做了这些:

  • 字体下载完成
  • registerFont 返回成功
  • 注册成功后才进入页面
  • 进入页面又延时120ms
  • 关闭并发
  • 重启应用后有概率恢复
  • 小概率复现
  • Canvas绘制时缺字,而不是全部缺失

这几个特征非常关键。


第一判断:registerFont 成功 ≠ 字体真正可用

HarmonyOS 的 font.registerFont() 返回成功,实际只表示:

字体文件已被系统接受

并不一定表示:

字体已经完成解析
字体缓存已经建立
字体已经进入渲染线程可见范围

官方和社区里都出现过:

registerFont成功
↓
立即渲染
↓
Text不显示
Canvas不显示
IconFont不显示

的问题。


第二判断:Canvas比Text更容易出问题

你提到:Canvas绘制某个字可能缺失。这里要特别注意。

如果是:

Text()
  .fontFamily(...)

CanvasRenderingContext2D.fillText(...)

实际上走的是两套不同渲染路径。

很多平台(不只是鸿蒙)都出现过:

Text正常
Canvas缺字
Canvas回退字体
Canvas空白

的问题。

所以我建议先确认:

是否只有Canvas异常?

做一个实验:

Column() {
  Text('测')
    .fontFamily('YourFont')

  Canvas(...)
}

如果:

Text正常
Canvas丢字

那基本可以确定:

不是字体注册问题
而是Canvas字体缓存问题

第三判断:字体文件本身是否有问题

我见过几个鸿蒙项目踩坑:

字体来源:

Google Font
网络字体
第三方字库
在线裁剪字体

然后:

Windows正常
Photoshop正常
鸿蒙偶发缺字

原因是:

subset字体
variable字体
字体表不完整

Canvas解析时会随机失败。

尤其:

OTF
Variable Font
WOFF转TTF

最容易出问题。


第四判断:字体缓存竞争

你的流程里有一句:异步预注册启动字体,这个我反而觉得危险。

例如:

启动
↓
注册字体A
↓
注册字体B
↓
注册字体C
↓
进入页面
↓
再注册练习字体D

如果多个字体:

familyName相同
或者频繁registerFont

我怀疑可能存在:

字体缓存覆盖
字体句柄失效

的问题。

建议检查:

familyName

是否绝对唯一。

例如:

HarmonySans
HarmonySans
HarmonySans

不要这样。

改成:

HarmonySans_001
HarmonySans_002
HarmonySans_003

测试。


第五判断:Canvas创建早于字体生效

这个是我最怀疑的。

例如:

字体注册成功
↓
进入页面
↓
Canvas创建
↓
Canvas第一次绘制
↓
字体缓存尚未同步
↓
出现空白

而之后:

没有触发重绘

所以永远是空白。

这就解释了:

重启APP有时恢复

因为重启后时序变了。


我建议做一个验证

注册成功后:

await font.registerFont(...)

然后:

setTimeout(() => {
  this.canvasReady = true
}, 1000)

不是为了修复,而是验证。

如果:

120ms仍然复现
1000ms完全不复现

那么可以直接锁定:

字体注册完成
≠
字体渲染缓存完成

第六判断:Canvas强制重绘测试

很多时候第一次绘制失败。

测试:

onAppear() {
  this.draw()

  setTimeout(() => {
    this.draw()
  }, 500)
}

如果第二次必定出现字体:

第一次空白
第二次正常

那么几乎可以100%确定:

字体缓存同步问题

而不是字体文件问题。


我个人认为最可疑的是

根据你的描述:

注册成功
进入页面
延时也没用
重启APP可能恢复
Canvas绘制缺字
概率性发生

最像:

HarmonyOS字体服务缓存与Canvas渲染线程同步异常

而不是:

registerFont失败
字体文件损坏
代码逻辑错误

因为如果是这些问题:

应该100%复现

不会表现为:

小概率缺失
重启恢复

这种典型时序问题。

我建议你重点验证两个结论:

  1. 同一字体用 Text 组件是否永远正常,而 Canvas 偶发缺失。
  2. Canvas 在 500ms 后强制重绘一次,缺失字体是否会出现。

这两个实验的结果基本就能把问题范围缩小到字体引擎还是 Canvas 渲染链路。

这个描述,可以贴出你的代码。
可以对比下:自定义字体的注册和使用《指南》《关键技术方案》
还有一种Typeface用法。

import { RenderNode, DrawContext, NodeController, FrameNode } from '@kit.ArkUI';
import { drawing, text } from '@kit.ArkGraphics2D';

// 自定义文本渲染节点
class MyTextRenderNode extends RenderNode {
  async draw(context: DrawContext) {
    const canvas = context.canvas;
    const myTypeFace = drawing.Typeface.makeFromRawFile($rawfile('font/HarmonyOS_Sans_Black.ttf'));//替换自己的ttf
    let font = new drawing.Font();
    font.setSize(50);
    font.setTypeface(myTypeFace);
    // 创建文本块并绘制
    const textBlob = drawing.TextBlob.makeFromString("Hello World", font, drawing.TextEncoding.TEXT_ENCODING_UTF8);
    canvas.drawTextBlob(textBlob, 60, 100);
  }
}

// 使用 NodeController 管理节点
class MyNodeController extends NodeController {
  private rootNode: FrameNode | null = null;
  private textNode: MyTextRenderNode = new MyTextRenderNode();

  makeNode(uiContext: UIContext): FrameNode {
    this.rootNode = new FrameNode(uiContext);
    const renderNode = this.rootNode.getRenderNode();
    if (renderNode != null) {
      // 设置节点位置和大小
      this.textNode.frame = { x: 0, y: 0, width: 400, height: 600 };
      renderNode.appendChild(this.textNode);
    }
    return this.rootNode;
  }
}

// 在页面中使用 NodeContainer 显示

@Entry
@Component
struct MyPage {
  private controller: MyNodeController = new MyNodeController();

  build() {
    Column() {
      NodeContainer(this.controller)
        .width('100%')
        .height(300)
    }
  }
}

开发者您好,建议您根据文档中的代码实例进行检查:
https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/custom-font-arkts
使用示例代码进行测试,观察问题是否复现。

问题可能因字体文件异步加载未完成、内存缓存竞争或字体家族名称与系统默认字体冲突引起。建议检查resourceManager获取字体文件路径的正确性,确保Font.registerFont在UI线程调用,并确认字体文件未损坏。若使用自定义字体名称,避免与系统预置字体重名。

字体注册成功仅表示系统字体管理器接受了该字体,但渲染引擎的字体缓存与文本布局尚未同步更新,导致首帧可能仍使用回退字体。这是 HarmonyOS Next 中字体注册的异步生效特性。建议在注册回调中,不要立即加载页面,而是使用 setTimeout(fn, 0)requestAnimationFrame 延迟一帧,让系统处理完字体刷新再进入练习页。添加的延时若放在阻塞任务中会被跳过,需确保延迟代码运行于 UI 空闲时。此外,可在目标页面 aboutToAppear 中二次校验字体是否存在,并用 @State 强制重建。如果使用字体的组件初次因缓存未命中而绘制错误,后续不会再自动更新,这解释了重启后可能正常。务必避免在注册期间进行重绘制或复杂运算。

回到顶部