Description
我在 #13 中提到过,由于 DisplayMetrics#density 在整个程序运行期间都是 public 公有的,所有不光是 AndroidAutoSize 可以修改它的值,系统、三方库、以及其他代码,也都有权限修改它的值,一旦在某个页面绘制之前,DisplayMetrics#density 的值不是经过 AndroidAutoSize 计算过的正确值,那就会出现屏幕适配失效的问题。
只要 DisplayMetrics#density 的值在某个页面绘制之前,保证是经过 AndroidAutoSize 计算过的正确值,那就能大概率保证屏幕适配能够正常完成,所以我们现在的终极解决方案就是在页面显示到屏幕上的 0.0000000000000000000000000001 秒之前,将 DisplayMetrics#density 修改为正确的值,所以我们的解决方案是在 getResources() 中,重新设置 DisplayMetrics#density。
@Override
public Resources getResources() {
//需要升级到 v1.1.2 及以上版本才能使用 AutoSizeCompat
AutoSizeCompat.autoConvertDensityOfGlobal(super.getResources());//如果没有自定义需求用这个方法
AutoSizeCompat.autoConvertDensity(super.getResources(), 667, false);//如果有自定义需求就用这个方法
return super.getResources();
}
由于在系统中所有参与单位转换的场景中都会调用 getResources() 方法,所以这就能保证 DisplayMetrics#density 的值始终是正确的。
但是接到一些 issues 的反馈,在实际开发中,即使重写 getResources() 也可能会导致屏幕适配失效,这里先抛开一些新手,由于不理解今日头条屏幕适配最基本的知识,而导致的适配问题。
我在这就谈谈即使在页面绘制的 0.0000000000000000000000000001 秒之前,保证 DisplayMetrics#density 的值为正确值,绘制出来的页面可能会屏幕失效吗?
答案是,有可能的,使用 XML 编写布局的项目可能会有这个问题。
关于适配失效的原因,这里就要谈到,关于系统源码层的东西,我在这里就简单说一下。
在 Activity#onCreate 中将页面的 Layout ID 传给 setContentView 方法后,其实不是立即进行页面绘制的,系统需要将 XML 的节点全部解析出来,并通过反射生成对应的 View 对象,在创建 View 对象的过程中需要创建 LayoutParams,并将 XML 中声明的 Width、Height、Margin 等属性全部存储到 LayoutParams 当中,在 View OnMeasure 时,会用到这个 LayoutParams 来确定 View 各个属性最终的值。
所以在 Activity#onCreate setContentView() 时,只是保证将 XML 中声明的 View 节点,全部转化为 View 对象,并将 XML 中声明的 View 属性全部保存在这个 View 对象中,这个页面的绘制还是要等到 ViewRootImpl 执行 performTraversals() 时才能完成。
View 对象的生成和页面完成绘制是有时间间隔的,在生成 View 对象,创建 LayoutParams 时,保存的 Width、Height、Margin 等属性是按照当时的 DisplayMetrics#density 计算的,如果在创建 LayoutParams 时 DisplayMetrics#density 的值不是正确值,这会导致 LayoutParams 中保存的 Width、Height、Margin 等属性的值也不是正确值,但是在 View onMeasure 中的默认逻辑,又会直接使用 LayoutParams 中保存的属性值,所以即使你在 onMeasure 之前保证 DisplayMetrics#density 是正确的值,也可能会出现适配失效的问题。
所以如果你的项目是通过 XML 来编写布局,你应该保证在 LayoutParams 创建时,DisplayMetrics#density 为正确值,所以解决方案是,重写 父布局 的 generateLayoutParams(AttributeSet),并在方法中设置正确的 DisplayMetrics#density, 注意是 父布局
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
AutoSizeCompat.autoConvertDensityOfGlobal(getResources());//如果没有自定义需求用这个方法
AutoSizeCompat.autoConvertDensity(getResources(), 667, false);//如果有自定义需求就用这个方法
return super.generateLayoutParams(attrs);
}
以上是我分析系统源码时,发现的可能会导致屏幕失效的问题,给的这个解决方案,可能会解决你的问题,也可能解决不了,如果能解决你的问题,望留言告知,这个解决方案配合 getResources() 应该就比较稳定了。
如果还是解决不了你的问题,那就需要你自己打断点,具体问题具体分析了,看看是从 setContentView 到 onMeasure 的哪个阶段出现了问题,今日头条屏幕适配方案也就 DisplayMetrics#density 是公有可被随时修改,这一个问题,当然这个特性也给我们带来了很多便利,你们反馈的所有问题其实都是这一个问题导致的,只不过是不同的呈现方式而已,如果所有方案都解决不了适配失效,那就重写 onMeasure,自己测量,重写 onMeasure 是可以百分百解决所有适配问题的,只不过需要你有一定基础。