App布局优化

405 阅读3分钟

布局为什么会卡顿

  • IO(解析XML)
  • 反射(创建View)
  • 遍历(View的测量)
  • 重绘

优化工具

  • Systrace
  • 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

基本功 | Litho的使用及原理剖析

参考: