当前位置: 首页 > news >正文

没错,我给androidx修了一个bug!

不容易啊,必须先截图留恋😁

这个bug是发生在xml中给AppcompatTextView设置textFontWeight,但是却无法生效。修复bug的代码也很简单,总共就几行代码,但是在找引起这个bug的原因和后面给androidx提pr却花了很久。

//AppcompatTextHelper  
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P&& mFontWeight != TEXT_FONT_WEIGHT_UNSPECIFIED && mFontTypeface != null) {mFontTypeface = Api28Impl.create(mFontTypeface, mFontWeight,(mStyle & Typeface.ITALIC) != 0);

事情的起因是这样的,在Android开发中,我们经常会遇到设计需要我们让TextView支持mediumbold等不同字重样式,原生TextView给了一个textStyle的属性,但是这个属性只支持bolditalicnormal三种样式,这肯定是满足不了设计小姐姐的需求的,而且很多时候bold的样式都比设计出的样式要粗一些。

以前的老办法就是导入不同字重样式的字体文件到app里面,但是很多字体包要支持的语言比较多的话,字体包文件又会比较大,会造成Apk包体积增大。裁剪字体包也可以,但是样式字体包文件多了,也是一件麻烦事,这些杂事很多又不会算到开发时间里面,我就想还有没有其他的解决办法。

网上搜索了下,大多数都是让使用textFontWeight属性的,这个属性倒是可以支持0-1000的字重设置,但是就是兼容性不好,只支持大于api28的手机使用。

Attribute textFontWeight is only used in API level 28 and higher (current min is 21)

但是从我实际使用来看,好像这个属性在TextView上也并没起作用。

<TextViewstyle="@style/TextStyle"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Font100"android:textFontWeight="100" />
...
<TextViewstyle="@style/TextStyle"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Font100"android:textFontWeight="900" />

我用一个LinearLayout装了9个TextView,textFontWeight设置从100-900,然后运行在api34的模拟器上,可以看到每个TextView都没应用上textFontWeight属性。
在这里插入图片描述
然后我就去Read The Fucking Source Code,看看TextViewtextFontWeiget的相关源码。TextView设置textFontWeiget的相关源码就是从setTypefaceFromAttrsresolveStyleAndSetTypeface,最后就是setTypeface

private void setTypefaceFromAttrs(@Nullable Typeface typeface, @Nullable String familyName,@XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style,@IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) {if (typeface == null && familyName != null) {// Lookup normal Typeface from system font map.final Typeface normalTypeface = Typeface.create(familyName, Typeface.NORMAL);resolveStyleAndSetTypeface(normalTypeface, style, weight);} else if (typeface != null) {resolveStyleAndSetTypeface(typeface, style, weight);} else {  // both typeface and familyName is null.switch (typefaceIndex) {case SANS:resolveStyleAndSetTypeface(Typeface.SANS_SERIF, style, weight);break;case SERIF:resolveStyleAndSetTypeface(Typeface.SERIF, style, weight);break;case MONOSPACE:resolveStyleAndSetTypeface(Typeface.MONOSPACE, style, weight);break;case DEFAULT_TYPEFACE:default:resolveStyleAndSetTypeface(null, style, weight);break;}}
}
private void resolveStyleAndSetTypeface(@NonNull Typeface typeface, @Typeface.Style int style,@IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) {if (weight >= 0) {weight = Math.min(FontStyle.FONT_WEIGHT_MAX, weight);final boolean italic = (style & Typeface.ITALIC) != 0;setTypeface(Typeface.create(typeface, weight, italic));} else {setTypeface(typeface, style);}
}

从代码可以看出来,设置textFontWeight就是靠Typeface.create(typeface, weight, italic)
这样来说,我们的代码应该是没有问题的呀,可是为啥没有起效呢?我想了很久,期间我想用hook的办法,把每个TextViewtextFontWeight都打印出来看看,然后我就写了一个WeightPrinterTextView工具类。

class WeightPrinterTextView @JvmOverloads constructor(context: Context,attrs: AttributeSet? = null
) : TextView(context, attrs) {
}

然后我把xml中的TextView都替换成这个WeightPrinterTextView,意想不到的是,textFontWeight这个属性竟然又起效果了😂。

<LinearLayoutstyle="@style/WeightLL"android:layout_height="match_parent"android:orientation="vertical"><demo.simple.fontweighttextview.WeightPrinterTextViewandroid:id="@+id/textView1"style="@style/TextStyle"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Font100"android:textFontWeight="100" /><demo.simple.fontweighttextview.WeightPrinterTextViewstyle="@style/TextStyle"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Font200"android:textFontWeight="200" />...<demo.simple.fontweighttextview.WeightPrinterTextViewstyle="@style/TextStyle"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Font800"android:textFontWeight="800" /><demo.simple.fontweighttextview.WeightPrinterTextViewstyle="@style/TextStyle"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Font900"android:textFontWeight="900" />
</LinearLayout>

在这里插入图片描述
啊,这?又把我秀到了,不愧是Android啊!不信邪的我,还重写clean了项目,然后xml又换回了TextView,再运行,结论就是TextView就是不行,但是自定义继承TextViewWeightPrinterTextView就是可以,这就奇怪了。

一次偶然的断点调试让我发现了端倪,在xml中声明的TextView竟然变成了AppcompatTextView,我写了几个方法验证了下,发现了如下规律:在xml中定义的TextView会转换成AppcompatTextView,但是自定义继承的TextView是不会转换类型的。
在这里插入图片描述
其实到这里,我大概就明白了应该是AppcompatTextView的问题,因为我之前也看过相关源码,知道如果Activity是继承自AppcompatActivity的,在createView的时候,会自动把相关的基础控件转成Appcompat的相关关联控件。
在这里插入图片描述
接下来又是喜闻乐见的Read The Fucking Source Code时间,首先肯定从View构造函数看起,因为View属性一般都是在构造函数中读取的。

public AppCompatTextView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(TintContextWrapper.wrap(context), attrs, defStyleAttr);ThemeUtils.checkAppCompatTheme(this, getContext());mBackgroundTintHelper = new AppCompatBackgroundHelper(this);mBackgroundTintHelper.loadFromAttributes(attrs, defStyleAttr);mTextHelper = new AppCompatTextHelper(this);mTextHelper.loadFromAttributes(attrs, defStyleAttr);mTextHelper.applyCompoundDrawablesTints();mTextClassifierHelper = new AppCompatTextClassifierHelper(this);AppCompatEmojiTextHelper emojiTextViewHelper = getEmojiTextViewHelper();emojiTextViewHelper.loadFromAttributes(attrs, defStyleAttr);
}

从代码来看好像也只有loadFromAttributes方法关联性比较大,然后再进入这个方法里面,但是这里嵌套的方法就更多了,就不详细阐述了。如果光是看代码去找问题就可大海捞针了,这里推荐我最喜欢的断点单步调试方法,在你觉得有关系的相关代码上打上断点,然后等着代码执行,一步一步的看调试结果就行了。

但是有一个注意的点就是:你要选择和运行代码匹配的库版本才行,比如你导入了两个appcompat的库,断点打在了1.0.0版本的源码上,但是运行的却是2.0.0的库,或者就是断点打在了Android14的源码上,但是运行的手机或模拟器却是Android13的系统。

反正经过我的各种调试,各种跳转,最终定位到了AppCompatTextHelperupdateTypefaceAndStyle方法。大致解释下造成这个bug的原因:就是updateTypefaceAndStyle被调用了两次,第一次的mFontWeight值是对的,但是mFontTypeface却是fontWeiget不对的,所以第二次就设置成了错误的FontTypeface,所以我们只需要像下面的代码,重新创建一个新的mFontTypeface就好了。

//AppcompatTextHelper  
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P&& mFontWeight != TEXT_FONT_WEIGHT_UNSPECIFIED && mFontTypeface != null) {mFontTypeface = Api28Impl.create(mFontTypeface, mFontWeight,(mStyle & Typeface.ITALIC) != 0);

本来到这里就应该结束了,我只需要用hook或者反射等相关方法解决掉自己的需求就好了,但是突然想到我好像从来没给Google的仓库提交过代码,之前也就给AndroidUtilCodeijkplayer这种国内开发者的仓库提过pr,不管了,试试就逝世😂!

我去仔细读了androidxreadme文档,大致流程就是先在Android Issue Tracker创建一个issue,这一点很坑,我从它这链接点进去,说我没有create issue的权限,我还以为不用完成这一步,这也导致了最开始这个pr搁置了很久也没人来code review。
在这里插入图片描述其实是需要在下拉框,重新选择子模块的。
在这里插入图片描述

然后再是添加自己的Test方法,覆盖测试用例,完善文档说明,最后push代码,new一个新的pr,选择一个code reviewer。

 @Testpublic void testTextFontWeight() {final AppCompatTextView textView = mActivity.findViewById(R.id.text_view_text_font_weight);if (textView.getTypeface() == null) return;final int textFontWeight = textView.getTypeface().getWeight();assertEquals(textFontWeight, 900);}

还有就是在选择code reviewer可能需要注意一下,能多选几个就多选几个吧,我选择了一个对appcompat贡献最多的一个老哥,结果到现在都还在pending review状态,期间都打算发邮件去沟通一下了,我看其他的pr都是同时选择了好几个code reviewer,最后还是在issue tracker创建了一个issue,才有好心的Google老哥帮忙review了一下,thank you bro😁。

提交pr后,google-cla-bot就会让你同意一个Contributor License,同意之后就等着code review就行了。

code review期间liutikas (Aurimas)大佬帮我完善了测试用例,nona-google (Seigo Nonaka)大佬approved了我的code changes,谢谢他们,Thanks(・ω・)ノ。
在这里插入图片描述
在这里插入图片描述
总的来说,这次给androidx提交代码还是很开心,因为AOSP里面也有我写的代码啦,哈哈哈~ 从readme得知,github上这个androidx仓库只是AOSP里面的androidx一个镜像,在github上合并的pr都会同步到AOSP里面去。

好了这篇文章就到这里结束了~假的,我骗你的。因为我在看androidx里面源码的时候,发现了一个神奇的类,那就是TypefaceCompat。它里面有个create的方法,可以适配不同的系统版本,创建带有weightTypeface

public static Typeface create(@NonNull Context context, @Nullable Typeface family,@IntRange(from = 1, to = 1000) int weight, boolean italic) {if (context == null) {throw new IllegalArgumentException("Context cannot be null");}Preconditions.checkArgumentInRange(weight, 1, 1000, "weight");if (family == null) {family = Typeface.DEFAULT;}return sTypefaceCompatImpl.createWeightStyle(context, family, weight, italic);
}

在这个类初始化的时候,static代码块里面会创建自适应的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();}
}

本来我是打算再提交个pr,让AppcompatTextView能不区分版本的支持textFontWeight,但是在自己写代码的时候好像发现为啥Google的大佬不这样做的原因了,可能是因为Typeface.getWeight这个方法是在Api28添加的,这样就不好写单元测试了。其实要做感觉也能做,就是给AppcompatTextView增加getWeight的方法,但是这样又感觉有点抽象😂。

所以就此,我又搞了一个新的仓库😄,用来适配全版本的textFontWeight
在这里插入图片描述
simplepeng/FontWeightTextView: 让textFontWeight属性支持Api29(Android9-p)以下

代码确实很简单,就读取了下textFontWeight的属性,然后调用了下TypefaceCompat.create,以至于群里老哥都吐槽:就这么几行代码还要搞一个库😂?

就此,本篇文章确实结束啦,完结撒花✿✿ヽ(°▽°)ノ✿。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 2024PDF内容修改秘籍:工具推荐与技巧分享
  • SpringBoot框架之KOB项目 - 配置Mysql与注册登录模块(上)
  • K8s容器运行时,移除Dockershim后存在哪些疑惑?
  • SpringBoot中基于Mybatis-Plus多表联查(无xml,通过注解实现)
  • WEB应用服务器TOMCAT
  • 【力扣】2376. 统计特殊整数
  • GO GIN SSE DEMO
  • 【Flink实战】flink消费http数据并将数组展开多行
  • AI健身之俯卧撑计数和姿态矫正-角度估计
  • 【JavaEE初阶】多线程7(面试要点)
  • 亚马逊IP关联揭秘:发生ip关联如何处理
  • [Python学习日记-27] 文件操作练习题解析
  • python新手的五个练习题
  • 计算机毕业设计 基于SpringBoot的小区运动中心预约管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解
  • SpringBoot+Vue技术框架开发的ADR智能监测系统源码,Java语言的药品不良反应智能监测系统源代码
  • [nginx文档翻译系列] 控制nginx
  • “大数据应用场景”之隔壁老王(连载四)
  • 【vuex入门系列02】mutation接收单个参数和多个参数
  • 2018以太坊智能合约编程语言solidity的最佳IDEs
  • Angular 2 DI - IoC DI - 1
  • co.js - 让异步代码同步化
  • Debian下无root权限使用Python访问Oracle
  • Quartz初级教程
  • Spring核心 Bean的高级装配
  • Twitter赢在开放,三年创造奇迹
  • VuePress 静态网站生成
  • 如何实现 font-size 的响应式
  • 通信类
  • 微信小程序--------语音识别(前端自己也能玩)
  • 我与Jetbrains的这些年
  • 你对linux中grep命令知道多少?
  • ​猴子吃桃问题:每天都吃了前一天剩下的一半多一个。
  • (1)(1.8) MSP(MultiWii 串行协议)(4.1 版)
  • (2)MFC+openGL单文档框架glFrame
  • (20)docke容器
  • (3)STL算法之搜索
  • (32位汇编 五)mov/add/sub/and/or/xor/not
  • (el-Date-Picker)操作(不使用 ts):Element-plus 中 DatePicker 组件的使用及输出想要日期格式需求的解决过程
  • (zz)子曾经曰过:先有司,赦小过,举贤才
  • (二十三)Flask之高频面试点
  • (分布式缓存)Redis持久化
  • (附源码)ssm基于jsp的在线点餐系统 毕业设计 111016
  • (十三)Maven插件解析运行机制
  • (原創) 物件導向與老子思想 (OO)
  • (转载)深入super,看Python如何解决钻石继承难题
  • (自用)仿写程序
  • *算法训练(leetcode)第四十七天 | 并查集理论基础、107. 寻找存在的路径
  • .NET 中什么样的类是可使用 await 异步等待的?
  • .net 桌面开发 运行一阵子就自动关闭_聊城旋转门家用价格大约是多少,全自动旋转门,期待合作...
  • .net打印*三角形
  • [APUE]进程关系(下)
  • [bzoj 3124][sdoi 2013 省选] 直径
  • [C#]DataTable常用操作总结【转】
  • [C#]winform基于深度学习算法MVANet部署高精度二分类图像分割onnx模型高精度图像二值化
  • [C]整形提升(转载)