> 文章列表 > android 布局优化

android 布局优化

android 布局优化

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延迟加载

<ViewStubandroid: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布局优化,多套方案全面解析