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过度绘制//开发者选项-调试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

Layout Inspector

1
2
3
1.AndroidStudio自带工具
2.查看视图层次结构
androidStudio顶部tools-Layout Inspector-选择对应进程-生成当前界面的检测信息

image-20230919111522587

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

二、布局加载原理

image-20230919113039306

性能瓶颈

1
2
布局文件解析:IO过程
创建View对象:反射

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

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")) {
// 生成自定义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;
}
});
...//需要在下面2句代码之前执行否则无效
setTheme(R.style.AppTheme);
super.onCreate(savedInstanceState);
...
}

四、异步Inflate实战

背景介绍

1
2
1.布局文件读取慢:IO过程
2.创建View慢:反射(比new3倍)

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
//过度绘制-开发者选项-调试gpu过度绘制打开设置
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、加载时间、布局层级
- 核心路径保障