4.布局优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 1. 绘制时长检测: 1.AOP(面向切面编程,在指定的地方执行特定的代码)的方式获取每个界面的绘制耗时; 1.Systrace查看整体绘制情况,查看是否存在绘制耗时等情况; 2.as自带的布局检测工具Inspector,查看视图层次结构; 2.如存在io加载布局文件慢 1.使用AsyncLayoutInflater/X2C异步加载布局 3. 布局层级:减少布局层级,减少过度绘制 1.减少View树层级 2.宽而浅,避免窄而深 3.尽量使用 ConstraintLayout布局控件 2.gpu过度绘制 1.一个像素最好只被绘制一次 2.调试GPU过度绘制 3.蓝色可接受 3. 卡顿的原因就是CPU数据处理不过来,比如层级过深,CPU太忙,GC,之类的,可以通过systrace 工具进行检测, 然后通过编舞者监测帧率定位时间,再然后可以通过looper的日志功能定位到卡顿点。网上也有库blockcanary 3.在Systrace 报告中,你可以查看应用程序布局绘制的相关事件,包括布局计算、绘制命令的发送、GPU 图形呈现等阶段所花费的时间
|
一、绘制原理及工具选择
绘制原理
1 2 3 4
| 1.CPU负责计算显示内容 2.GPU负责栅格化(UI元素绘制到屏幕上) 3.16ms发出VSync信号触发UI渲染 4.大多数的Android设备屏幕刷新频率:60Hz
|
优化工具
1 2 3 4
| Systrace - 关注Frames - 正常:绿色圆点,丢 :黄色或红色 - Alerts栏
|
![image-20230919111203321](/../../../images/image-20230919111203321.png)
Layout Inspector
1 2 3
| 1.AndroidStudio自带工具 2.查看视图层次结构 androidStudio顶部tools-Layout Inspector-选择对应进程-生成当前界面的检测信息
|
![image-20230919111522587](/../../../images/image-20230919111522587.png)
Choreographer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| 获取FPS,线上使用,具备实时性 - Api 16之后 - Choreographer.getInstance().postFrameCallback 使用示例:
@TargetApi(Build.VERSION_CODES.JELLY_BEAN) private void getFPS() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { return; } Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() { @Override public void doFrame(long frameTimeNanos) { if (mStartFrameTime == 0) { mStartFrameTime = frameTimeNanos; } long interval = frameTimeNanos - mStartFrameTime; if (interval > MONITOR_INTERVAL_NANOS) { double fps = (((double) (mFrameCount * 1000L * 1000L)) / interval) * MAX_INTERVAL; mFrameCount = 0; mStartFrameTime = 0; } else { ++mFrameCount; } Choreographer.getInstance().postFrameCallback(this); } }); }
|
获取fps的日志打印![image-20230919111909078](/../../../images/image-20230919111909078.png)
二、布局加载原理
![image-20230919113039306](/../../../images/image-20230919113039306.png)
性能瓶颈
LayoutInflater.Factory
1 2
| LayoutInflater创建View的一个Hook 定制创建View的过程:全局替换自定义TextView等
|
Factory与Factory2
1 2
| Factory2继承于Factory 多了一个参数:parent
|
三、优雅获取界面布局耗时
常规方式
1 2
| 背景:获取每个界面加载耗时 实现:覆写方法、手动埋点
|
AOP实现
1 2
| 切Activity的setContentView @Around("execution(*android.app.Activity.setContentView(..))")
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package com.optimize.performance.aop; import com.optimize.performance.utils.LogUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; @Aspect public class PerformanceAop { @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(); } LogUtils.i(name + " cost " + (System.currentTimeMillis() - time)); } }
|
随意点击2个页面后查看日志:![image-20230919114258052](/../../../images/image-20230919114258052.png)
ARTHook实现:切Activity的setContentView
获取每一个控件加载耗时
1 2
| 1.低侵入性 2.LayoutInflater.Factory
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @Override protected void onCreate(Bundle savedInstanceState) { LayoutInflaterCompat.setFactory2(getLayoutInflater(), new LayoutInflater.Factory2() { @Override public View onCreateView(View parent, String name, Context context, AttributeSet attrs) { if (TextUtils.equals(name, "TextView")) { } long time = System.currentTimeMillis(); View view = getDelegate().createView(parent, name, context, attrs); LogUtils.i(name + " cost " + (System.currentTimeMillis() - time)); return view; } @Override public View onCreateView(String name, Context context, AttributeSet attrs) { return null; } }); ... setTheme(R.style.AppTheme); super.onCreate(savedInstanceState); ... }
|
四、异步Inflate实战
背景介绍
1 2
| 1.布局文件读取慢:IO过程 2.创建View慢:反射(比new慢3倍)
|
AsyncLayoutInflater
1 2 3 4
| 简称异步Inflate - WorkThread加载布局 - 回调主线程 - 节约主线程时间
|
AsyncLayoutInflater使用(相当于侧面解决方案,从子线程加载xml资源文件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| com.android.support:asynclayoutinflater
@Override protected void onCreate(Bundle savedInstanceState) { new AsyncLayoutInflater(MainActivity.this).inflate(R.layout.activity_main, null, new AsyncLayoutInflater.OnInflateFinishedListener() { @Override public void onInflateFinished(@NonNull View view, int i, @Nullable ViewGroup viewGroup) { setContentView(view); mRecyclerView = findViewById(R.id.recycler_view); mRecyclerView.setLayoutManager(new LinearLayoutManager(MainActivity.this)); mRecyclerView.setAdapter(mNewsAdapter); mNewsAdapter.setOnFeedShowCallBack(MainActivity.this); } }); setTheme(R.style.AppTheme); super.onCreate(savedInstanceState); ... }
|
AsyncLayoutInflater缺点
1 2
| 1.不能设置LayoutInflater.Factory (自定义解决 2.注意View中不能有依主线程的操作
|
Java代码写布局
1 2
| 1.本质上解决了性能问题 2.引入新问题:不便于开发、可维护性差
|
X2C介绍
1 2 3
| 保留XML优点,解决其性能问题 - 开发人员写XML,加载Java代码 - 原理:APT编译期翻译XML为Java代码(相当于自动将xml布局转为java代码后再加载)
|
X2C使用
1 2 3 4 5 6 7 8 9 10 11 12 13
| 1.AnnotationProcessor 'com.zhangyue.we:x2c-apt:1.1.2'(appMoudle的build.gradle中添加) 2.implementation 'com.zhangyue.we:x2c-lib:1.0.6' 3.@Xml(layouts = "activity_main") (类名上面,如: @Xml(layouts = "activity_main") public class MainActivity extends AppCompatActivity){ setTheme(R.style.AppTheme); super.onCreate(savedInstanceState); //setContentView(R.layout.activity_main); X2C.setContentView(MainActivity.this, R.layout.activity_main); } ) //build中可以看到出现了2个MainActivity,一个是x2c将xml转为java代码的类
|
X2C问题
1 2
| 1.部分属性Java不支持 2.失去了系统的兼容(AppCompat)
|
五、视图绘制优化
视图绘制流程
1 2 3
| 测量:确定大小(自顶向下遍历确定每个布局的大小) 布局:确定位置(根据测量大小确定控件位置) 绘制:绘制视图
|
性能瓶颈
1 2 3
| 1.每个阶段耗时 2.自顶而下的遍历 3.触发多次
|
布局层级及复杂度
1 2 3
| 准则 1.减少View树层级 2.宽而浅,避免窄而深
|
ConstraintLayout
1 2 3
| 1.实现几乎完全扁平化布局 2.构建复杂布局性能更高 3.具有RelativeLayout和LinearLayout特性
|
布局层级及复杂度
1 2 3
| 1.不嵌套使用RelativeLayout 2.不在嵌套LinearLayout中使用weight 3.merge标签:减少一个层级,只能用于根View
|
过度绘制
1 2 3 4
| 1.一个像素最好只被绘制一次 2.调试GPU过度绘制 3.蓝色可接受
|
避免过度绘制方法
1 2 3
| 1.去掉多余背景色,减少复杂shape使用 2.避免层级叠加 3.自定义View使用clipRect屏蔽被遮盖View绘制
|
其它技巧
1 2 3
| 1.Viewstub:高效占位符、延迟初始化 2.onDraw中避免:创建大对象、耗时操作 3.TextView优化
|
六、问题
1 2 3 4 5 6 7 8 9 10 11
| 你在做布局优化过程中用到了哪些工具 - Choreographer - AOP、Hook - Systrace、Layout Inspector 布局为什么会导致卡顿,你又是怎么优化的 ? - IO、反射、遍历、重绘 - 异步Inflate、X2C、减少层级、重绘 做完布局优化有哪些成果产出 - 体系化监控手段:线下+线上 - 指标:FPS、加载时间、布局层级 - 核心路径保障
|