布局为什么会卡顿
- IO(解析XML)
- 反射(创建View)
- 遍历(View的测量)
- 重绘
优化工具
- Systrace
- 关注Frames
- 正常:绿色圆点,丢帧:黄色或红色
- Alerts栏
- 使用:App卡顿优化工具
- Layout Inspector
- Android Studio 自带
- 查看视图层次结构
- Choreographer
布局加载
布局加载流程
setContentView - > LayoutInflater - > inflate - > getLayout - > createViewFromTag - > Factory - > createView - > 反射
性能瓶颈
- 布局文件解析:IO过程
- 创建View对象:反射
LayoutInflater.Factory
- LayoutInflater.Factory是layoutInflater中创建View的一个Hook。
- 定制创建View的过程:全局替换自定义TextView等。
Factory与Factory2
- Factory2继承与Factory。
- Factory2比Factory的onCreateView方法多一个parent的参数,即当前创建View的父View。
获取界面布局耗时
- 常规方式(埋点)
- 不够优雅。
- 代码有侵入性。
- AOP/ArtHOOK
@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();
}
LogHelper.i(name + " cost " + (System.currentTimeMillis() - time));
}
获取每一个控件加载耗时
//MainActivity extends AppCompatActivity
@Override
protected void onCreate(Bundle savedInstanceState) {
//Hook 每一个控件加载耗时
LayoutInflaterCompat.setFactory2(getLayoutInflater(), new LayoutInflater.Factory2() {
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
//①
long startTime = System.currentTimeMillis();
//②
View view = getDelegate().createView(parent, name, context, attrs);
//③
long cost = System.currentTimeMillis() - startTime;
Log.d(TAG, "加载控件:" + name + "耗时:" + cost);
return view;
}
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
return null;
}
});
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
「性能优化2.1」LayoutInflater Hook控件加载耗时
异步Inflate实战
AsynclayoutInflater异步创建View
- 优点:工作线程加载布局,回调主线程,节省主线程时间。
- 缺点:不能设置LayoutInflater.Factory(导致不能向下兼容)
- 解决: Android AsyncLayoutInflater 限制及改进
- implementation 'com.android.support:asynclayoutinflater:28.0.0'
// 使用AsyncLayoutInflater进行布局的加载
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);
// findViewById、视图操作等
}
});
super.onCreate(savedInstanceState);
X2C介绍
- 保留XML优点,解决其性能问题
- 开发人员写XML,加载java代码。
- 原理:APT编译期翻译XML为Java代码。
- 缺点:
- 部分Java属性不支持。
- 失去了系统的兼容(AppCompat,我们需要修改X2C框架的源码,当发现是TextView等控件时,需要直接使用new的方式去创建一个AppCompatTextView等兼容类型的控件)。
- merge标签 ,在编译期间无法确定xml的parent,所以无法支持。
- 系统style,在编译期间只能查到应用的style列表,无法查询系统style,所以只支持应用内style。
X2C的使用
annotationProcessor 'com.zhangyue.we:x2c-apt:1.1.2'
implementation 'com.zhangyue.we:x2c-lib:1.0.6'
@Xml(layouts = "activity_main")
public class MainActivity extends AppCompatActivity implements OnFeedShowCallBack {
...
X2C.setContentView(MainActivity.this, R.layout.activity_main);
...
}
视图绘制优化实战
- 优化布局层级及复杂度
- ConstraintLayout。
- 不嵌套使用RelativeLayout。
- 不在嵌套LinearLayout中使用weight。
- merge标签(只能用于根View)。
- 过度绘制
- 一个像素最好只被绘制一次。
- 调试GPU过度绘制。
- 蓝色可接受。
- 避免过度绘制方法
- 去掉多余背景色,减少负责shape使用。
- 避免层级叠加
- 自定义View使用clipRect屏蔽被遮盖的View。
- 其他技巧
- Viewstub:高效占位符,延迟初始化。
- onDraw中避免:创建大对象,耗时操作。
- TextView优化。
布局优化进阶方案
Litho