1.绘制和布局加载原理
本文仅供个人学习记录,详细介绍可查看下面链接
Android布局优化,多套方案全面解析
布局优化的原因:布局嵌套过深,或者其他原因导致布局渲染性能不佳,可能会导致应用卡顿。
android绘制原理:
-
CPU:执行应用层的measure、layout、draw等操作,绘制完成后将数据提交给GPU
-
GPU:进一步处理数据,并将数据缓存起来
-
屏幕:由一个个像素点组成,以固定的频率(16.6ms,即1秒60帧)从缓冲区中取出数据来填充像素点
总结一句话就是:CPU 绘制后提交数据、GPU 进一步处理和缓存数据、最后屏幕从缓冲区中读取数据并显示。
-
双缓冲机制
GPU只向Back Buffer中写入绘制数据,且GPU会定期交换Back Buffer和Frame Buffer(频率也是60次/秒),
掉帧当布局复杂或设备性能较差,CPU并不能保证在16.6ms内就完成绘制数据的计算,系统会将Back Buffer锁定,到了GPU交换两个Buffer的时间点,应用还在往Back Buffer中填充数据,GPU会发现Back Buffer被锁定了,它会放弃这次交换。
导致掉帧的原因是CPU无法在16.6ms内完成绘制数据的计算。
-
布局加载原理
setContentView中主要有两个耗时操作
1. 解析xml,获取XmlResourceParser,这是IO过程
2.通过createViewFromTag,创建View对象,用到了反射
2.获取布局文件加载耗时的方法
-
常规获取
val start = System.currentTimeMillis()
setContentView(R.layout.activity_layout_optimize)
val inflateTime = System.currentTimeMillis() - start
setContentView是同步方法,直接将前后时间计算相减
优点:简单;
缺点:不够优雅,代码有侵入性,监听所有麻烦。
-
AOP(Aspectj,ASM)
@Around("execution(* android.app.Activity.setContentView(..))")
public void getSetContentViewTime(ProceedingJoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
String name = signature.toShortString();
long time = System.currentTimeMillis();
try {
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
Log.i("aop inflate",name + " cost " + (System.currentTimeMillis() - time));
}
-
获取任一控件耗时
利用setFactory2来监听每个控件的加载耗时
private fun initItemInflateListener(){
LayoutInflaterCompat.setFactory2(layoutInflater, object : Factory2 {
override fun onCreateView(
parent: View?,
name: String,
context: Context,
attrs: AttributeSet
): View? {
val time = System.currentTimeMillis()
val view = delegate.createView(parent, name, context, attrs)
Log.i("inflate Item",name + " cost " + (System.currentTimeMillis() - time))
return view
}
override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {
return null
}
})
}
initItemInflateListener需要在onCreate之前调用
布局加载优化的一些方法介绍
-
AsyncLayoutInflater方案
帮助做异步加载 layout 的,inflate(int, ViewGroup, OnInflateFinishedListener) 方法运行结束之后 OnInflateFinishedListener 会在主线程回调返回 View;这样做旨在 UI 的懒加载或者对用户操作的高响应。
优点在于将UI加载过程迁移到了子线程,保证了UI线程的高响应 缺点在于牺牲了易用性,同时如果在初始化过程中调用了UI可能会导致崩溃
-
X2C方案
// this.setContentView(R.layout.activity_main);
X2C.setContentView(this, R.layout.activity_main);
优点:
1.在保留xml的同时,又解决了它带来的性能问题
2.据X2C统计,加载耗时可以缩小到原来的1/3
缺点:
1.部分属性不能通过代码设置,Java不兼容
2.将加载时间转移到了编译期,增加了编译期耗时
3.不支持kotlin-android-extensions插件(已被废弃),牺牲了部分易用性
-
Anko方案
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
MyActivityUI().setContentView(this)
}
}
class MyActivityUI : AnkoComponent<MyActivity> {
override fun createView(ui: AnkoContext<MyActivity>) = with(ui) {
verticalLayout {
val name = editText()
button("Say Hello") {
onClick { ctx.toast("Hello, ${name.text}!") }
}
}
}
}
Anko使用kotlin DSL实现布局,它比我们使用Java动态创建布局方便很多,主要是更简洁,它和拥有xml创建布局的层级关系,能让我们更容易阅读,去除了IO与反射过程,性能更好
-
Compose方案
主要优点就在于它的简单好用
1.它的声明式 UI
2.去掉了 xml,只使用 Kotlin 一种语言
3.优化布局层级及复杂度
1.使用ConstraintLayout,可以实现完全扁平化的布局,减少层级
2.RelativeLayout本身尽量不要嵌套使用
3.嵌套的LinearLayout中,尽量不要使用weight,因为weight会重新测量两次
4.推荐使用merge标签,可以减少一个层级
//假定自定义View为RelativeLayout
public class LoginButton extends RelativeLayout {
。。。
}
//在xml标签中使用merge作为根标签,而不要再次使用RelativeLayout作为根标签,可以省去一个层级
5.使用ViewStub延迟加载
<ViewStub
android:id="@+id/contentPanel"
android:inflatedId="@+id/inflatedStart"
android:layout="@layout/delayInflateLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
/>
6.去掉多余背景色,减少复杂shape的使用
7.避免层级叠加
8.自定义View使用clipRect屏蔽被遮盖View绘制
4布局查看工具
1.使用Layout Inspater检查布局层级
1、在连接的设备或模拟上运行你的应用
2、点击Tools > Layout Inspector
3、在出现的Choose Process对话框中,选择你想要检查的应用进程,然后点击OK。
或者直接在布局文件查看
2.使用调试GPU过度绘制功能检查过度绘制
从开发者模式中找到调试GPU过度绘制
功能开关
检查 GPU 渲染速度和过度绘制 | Android 开发者 | Android Developers
参考
Android性能优化(三)-绘制优化 - 掘金
检查 GPU 渲染速度和过度绘制 | Android 开发者 | Android Developers
Android布局优化,多套方案全面解析