专栏名称: 鸿洋
你好,欢迎关注鸿洋的公众号,每天为您推送高质量文章,让你每天都能涨知识。点击历史消息,查看所有已推送的文章,喜欢可以置顶本公众号。此外,本公众号支持投稿,如果你有原创的文章,希望通过本公众号发布,欢迎投稿。
目录
相关文章推荐
开发者全社区  ·  在中国有个奇怪现象:计算机行业明明是一个技术 ... ·  8 小时前  
开发者全社区  ·  资产上亿的牛散,真实身份竟是南航空姐 ·  昨天  
开发者全社区  ·  北舞身材管理曝光 ·  2 天前  
51好读  ›  专栏  ›  鸿洋

我的天,我真是和androidx的字体加载杠上了

鸿洋  · 公众号  · android  · 2025-03-20 08:35

正文


本文作者


作者: simplepeng

链接:

https://juejin.cn/post/7474375249150001171

本文由作者授权发布。


Caused by: java.lang.IllegalStateException: Unable to load font ResourceFont(resId=2131034115, weight=FontWeight(weight=400), style=Normal, loadingStrategy=Blocking)          at androidx.compose.ui.text.font.FontListFontFamilyTypefaceAdapterKt.firstImmediatelyAvailable(FontListFontFamilyTypefaceAdapter.kt:193)...Caused by: android.content.res.Resources$NotFoundException: Font resource ID #0x7f050003 could not be retrieved.          at androidx.core.content.res.ResourcesCompat.loadFont(ResourcesCompat.java:573)          at androidx.core.content.res.ResourcesCompat.getFont(ResourcesCompat.java:414)

当时看见这个报错信息的时候,我就想是不是 Jetpack Compose 字体加载的问题,看了眼我的代码感觉也没啥特别的, Compose 里面很常见的字体加载方式了。

Text(    text = "1234 $name!",    modifier = Modifier        .align(Alignment.TopCenter)        .padding(top = 50.dp),    fontFamily = FontFamily(Font(R.font.helvetica_roman_semi_b_3)),)

然后就看了下 FontFamily 的源码,感觉应该也不是,就这么简单的代码。

@Stablefun FontFamily(vararg fonts: Font): FontFamily = FontListFontFamily(fonts.asList())

接着又研究 Font 的源码,感觉这里面有些蹊跷。

@Stablefun Font(    resId: Int,    weight: FontWeight = FontWeight.Normal,    style: FontStyle = FontStyle.Normal,    loadingStrategy: FontLoadingStrategy = FontLoadingStrategy.Blocking): Font = ResourceFont(resId, weight, style, FontVariation.Settings(), loadingStrategy)

Font 函数除了接受字体的资源id外,还可以传字重 weight 属性,样式 style 属性和加载策略 loadingStrategy 属性。

因为设计小姐姐说这个字体包是一个粗体,我就思考是不是 weight 不能用 FontWeight. Normal ,要用字体包对应的 字重 。熟悉 Compose 的童鞋都知道, FontWeight 类接受一个 weight:Int 的入参,并且它里面定义了从 W100-W900 的字重伴生对象。

@Immutable




    
class FontWeight(val weight: Int) : Comparable 
image-20250219195625453.png

我依次从 100 设置到 900 ,该闪退的地方还是一样会闪退,该报错的信息还是一样会输出。

接着再研究 FontStyle 参数。FontStyle也简单,只有 Normal Italic 两个属性可以用,那应该也不是它们导致的异常了,但是秉着求真的思想,不能放过一条漏网之鱼,还是改了下参数试了试,也证明了确实不是它导致的。

最后就是 FontLoadingStrategy 参数了。 FontLoadingStrategy 是一个 v alue class ,在它的伴生对象也定义了3个值,分别为 Blocking OptionalLocal Async 。大致含义就是Blocking会阻塞UI线程的去加载字体,OptionalLocal会优先加载本地字体,Async适合加载在线的字体。

image-20250214164004343.png

不管怎样,每个参数都试一下就行了,但是结果还是一样的,到加载这个字体包文件时就异常闪退。

暂时能想到的办法都试过了,十分能确定就是这个字体包有问题了,就反馈给设计小姐姐能不能换个字体,然后得到也是经典的那句话: 为什么啊?iOS都可以啊! ,😂,作为苦逼的Android开发,这句话都快听起茧来了,没其他意思,就是很无奈。

不行!身为经验丰富(踩坑多)的工程师,😎,必须把这个问题解决了,接着继续想办法,什么直接用TextView直接加载这个字体,用 Typeface 类去加载这个字体,新建一个新的项目去加载这个字体,全都是一样,一样的闪退报错,😈。最离谱的是这个字体有很多字重版本,设计小姐姐发我的是 bold 版本,我自己从网上下载的 regular 版本都不报错,真是离谱,😈。不是哥们!就这样一天的时间都过去了,该当天完成的需求都还没做呢,就在这里填坑了,而且坑还没填上,白白的又比iOS哥们少了一天工期,难道又只有加班赶回来了,😭,当然最后的结果还是设计小姐姐换了个其他类型的字体。 没办法,这就是Android! ,别说产品和设计了,🐶都嫌弃。

后面这个版本上线了,终于有点空闲时间来继续研究这个问题了,刚开始我肯定是没怀疑是 androidx 的问题的,但是经过了上一次给androidx修bug的经历,我也才理解了,代码都是人写的,虽然Google工程师很厉害,但是在国内系统这么碎片化的情况下,一不小心就 蕉泥座人 ,😏。所以我就继续研究源码了,从Font加载字体开始,还是使用打断点单步调试法,一层一层的往下走就行了,之后有一次看报错信息才发现,直接看 ResourcesCompat.loadFont 方法就行了,如本文开头的报错信息。

Caused by: android.content.res.Resources$NotFoundException: Font resource ID #0x7f050003 could not be retrieved.          at androidx.core.content.res.ResourcesCompat.loadFont(ResourcesCompat.java:573)          at androidx.core.content.res.ResourcesCompat.getFont(ResourcesCompat.java:414)

很明显的指出了异常发生在 ResourcesCompat 类中的 getFont 方法,然后里面再调用了 loadFont 方法。

private static Typeface loadFont(@NonNull Context context, int id, @NonNull TypedValue value,        int style, @Nullable FontCallback fontCallback, @Nullable Handler handler,        boolean isRequestFromLayoutInflator, boolean isCachedOnly) {    final Resources resources = context.getResources();    resources.getValue(id, value, true);    //    Typeface typeface = loadFont(context, resources, value, id, style, fontCallback, handler,            isRequestFromLayoutInflator, isCachedOnly);    //在这里抛出异常了    if (typeface == null && fontCallback == null && !isCachedOnly) {        throw new NotFoundException("Font resource ID #0x"                + Integer.toHexString(id) + " could not be retrieved.");    }    return typeface;}
private static Typeface loadFont(        @NonNull Context context, Resources wrapper, @NonNull TypedValue value, int id,        int style, @Nullable FontCallback fontCallback, @Nullable Handler handler,        boolean isRequestFromLayoutInflator, boolean isCachedOnly) {			//删除一些无用代码    try {        typeface = TypefaceCompat.createFromResourcesFontFile(                context, wrapper, id, file, value.assetCookie, style);      //删除一些无用代码        return typeface;    } catch (XmlPullParserException e) {        Log.e(TAG, "Failed to parse xml resource " + file, e);    } catch (IOException e) {        Log.e(TAG, "Failed to read xml resource " + file, e);    }    return null;}

loadFont 方法返回的 Typeface 就是 Text 组件要用的字体类型,但是这个typeface又是 TypefaceCompat.createFromResourcesFontFile 方法返回的,继续往里走。

public static Typeface createFromResourcesFontFile(        @NonNull Context context, @NonNull Resources resources, int id, String path, int cookie,        int style) {    Typeface typeface = sTypefaceCompatImpl.createFromResourcesFontFile(            context, resources, id, path, style);    if (typeface != null) {        final String resourceUid = createResourceUid(resources, id, path, cookie, style);        sTypefaceCache.put(resourceUid, typeface);    }    return typeface;}

然后又从上面代码可得知, typeface 来源于 sTypefaceCompatImpl.createFromResourcesFontFile 方法,之前那篇文章讲过, sTypefaceCompatImpl 是分版本创建的。

private static final TypefaceCompatBaseImpl sTypefaceCompatImpl;static {    if (Build.VERSION.SDK_INT >= 29) {        sTypefaceCompatImpl = new TypefaceCompatApi29Impl();    } else if (Build.VERSION.SDK_INT >= 28) {        sTypefaceCompatImpl = new TypefaceCompatApi28Impl();    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {        sTypefaceCompatImpl = new TypefaceCompatApi26Impl();    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N            && TypefaceCompatApi24Impl.isUsable()) {        sTypefaceCompatImpl = new TypefaceCompatApi24Impl();    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {        sTypefaceCompatImpl = new TypefaceCompatApi21Impl();    } else {        sTypefaceCompatImpl = new TypefaceCompatBaseImpl();    }}

我们先从 TypefaceCompatApi29Impl 看着走吧。直接跳转到 TypefaceCompatApi29Impl 中的 createFromResourcesFontFile

public Typeface createFromResourcesFontFile(        Context context, Resources resources, int id, String path, int style) {    FontFamily family = null;    Font font = null;    try {        font = new Font.Builder(resources, id).build();        family = new FontFamily.Builder(font).build();        return new Typeface.CustomFallbackBuilder(family)                // Set font's style to the display style for backward compatibility.                .setStyle(font.getStyle())                .build();    } catch (Exception e) {//注意这里捕获了Exception,但是没输出结果。        return null;    }}

没办法,还是单步一步一步的往下执行,执行 family = new FontFamily.Builder(font).build(); 这行代码时程序就异常 闪退了,我们打断点继续往 build 方面里面进。

public @NonNull FontFamily build(@NonNull String langTags, int variant,        boolean isCustomFallback, boolean isDefaultFallback) {    final long builderPtr = nInitBuilder();    for (int i = 0; i < mFonts.size(); ++i) {        nAddFont(builderPtr, mFonts.get(i).getNativePtr());    }  	//这行代码的锅    final long ptr = nBuild(builderPtr, langTags, variant, isCustomFallback,            isDefaultFallback);    final FontFamily family = new FontFamily(ptr);    sFamilyRegistory.registerNativeAllocation(family, ptr);    return family;}

继续单步继续走,然后就是执行 nBuild 方法闪退了,继续看看 nBuild







请到「今天看啥」查看全文