布局优化

布局加载优化

graph LR
正面解决(正面解决)-->去IO,去解析-->|solution|frontSolution("X2C,Anko,Compose")
正面解决-->去反射-->|solution|frontSolution
侧面解决-->异步加载-->|solution|AsyncLayoutInflater

图片

我们可以看到,在setContentView中主要有两个耗时操作:

1.解析xml,获取XmlResourceParser,这是IO过程。

2.通过createViewFromTag,创建View对象,用到了反射。

以上两点就是布局加载可能导致卡顿的原因,也是布局的性能瓶颈。

耗时监听

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));
}

上面用的Aspectj,比较简单,上面的注解的意思是在setContentView方法执行内部去调用我们写好的getSetContentViewTime方法。 这样就可以获取相应的耗时。我们可以看下打印的日志:

I/aop inflate: AppCompatActivity.setContentView(..) cost 69
I/aop inflate: AppCompatActivity.setContentView(..) cost 25

这样就可以实现无侵入的监控每个页面布局加载的耗时。

获取任一控件耗时

有时为了更精确的知道到底是哪个控件加载耗时,比如我们新添加了自定义View,需要监控它的性能。 我们可以利用setFactory2来监听每个控件的加载耗时。首先我们来回顾下setContentView方法:

public final View tryCreateView(@Nullable View parent, @NonNull String name,
    ...
    View view;
    if (mFactory2 != null) {
        view = mFactory2.onCreateView(parent, name, context, attrs);
    } else if (mFactory != null) {
        view = mFactory.onCreateView(name, context, attrs);
    } else {
        view = null;
    }
    ...
    return view;
}

在真正进行反射实例化xml结点前,会调用mFactory2的onCreateView方法。 这样如果我们重写onCreateView方法,在其前后加上耗时统计,即可获取每个控件的加载耗时。

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
        }
    })
}

如上所示:真正的创建View的方法,仍然是调用delegate.createView,我们只是其之前与之后做了埋点。

注意,initItemInflateListener需要在onCreate之前调用。这样就可以比较方便地实现监听每个控件的加载耗时。

参考

//编译生成xml–>javaView

https://github.com/iReaderAndroid/X2C

//仅仅优化反射

android “退一步"的布局加载优化

Android布局优化(三)使用AsyncLayoutInflater异步加载布局

Android 布局优化真的难,从入门到放弃