1 目录
image2 前言
在了解了布局加载原理之后,我们就要开始布局优化实战了,但是为了能够判断哪些页面布局耗时较长,以及正确的衡量布局优化的成果,我们先来了解一些获取界面布局耗时的方法
3 获取界面布局耗时
3.1 基本方式
布局的过程也就是setContentView
的过程,所以想要获取布局耗时,我们最容易想到的就是下面这种方式,就是在setContentView
的前后分别进行打点,计算时间差
@Override
protected void onCreate(Bundle savedInstanceState) {
long startTime = System.currentTimeMillis();
setContentView(R.layout.activity_main);
long cost = System.currentTimeMillis() - startTime;
Log.i(TAG, "setContentView cost " + cost);
}
这种方式有两个缺点
- 代码侵入性比较强
- 如果有很多Activity需要统计,不太方便
3.2 AOP的方式
我们说到了AOP相关的概念及使用,所以我们可以通过AOP的方式更优雅、侵入性更低的统计页面布局耗时,如下方法能够Hook所有的Activity
的setContentView
方法
- MethodTime.java
/**
* desc : 布局加载耗时注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface MethodTime {
}
- MethodTimeAspect.java
@Aspect
public class MethodTimeAspect {
/**
* 方法切入点
*/
@Pointcut("execution(@com.hjq.base.aop.MethodTime * *(..))")
public void method() {
}
/**
* 在连接点进行方法替换
*/
@Around("method() && @annotation(methodTime)")
public void aroundJoinPoint(ProceedingJoinPoint joinPoint, MethodTime methodTime) {
Signature signature = joinPoint.getSignature();
//开始处打点
String name = signature.toShortString();
long startTime = System.currentTimeMillis();
try {
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
//结束打点,并计算耗时。
long costTime = System.currentTimeMillis() - startTime;
LogUtils.i("方法耗时:", "method- " + name + " 耗时:" + costTime + " 毫秒");
}
- BaseActivity.java
@MethodTime
@Override
public void setContentView(View view) {
super.setContentView(view);
}
4 获取控件加载耗时
有时候我们还希望监测一些自定义控件的加载耗时,那么我们要怎么做呢?
在布局加载原理中我们有介绍过Factory
,Factory2
继承自Factory
接口,在API 11(HONEYCOMB)中引入的。Factory2
的onCreateView(View parent, String name, Context context, AttributeSet attrs)
比Factory
多了一个parent
参数,用来存放构建出的View
系统通过Factory提供了一种hook的方法,方便开发者拦截LayoutInflater
创建View
的过程。应用场景包括
-
在XML布局中自定义标签名称;
-
全局替换系统控件为自定义View;
-
替换app中字体;
-
全局换肤;
-
获取控件加载耗时等
在API Level 26及以后,LayoutInflaterCompat.setFactory
被标记为Deprecated
,故我们这里使用的是LayoutInflaterCompat.setFactory2
我们可以在项目基类BaseActivity
的super.onCreate()
前调用setFactory2
,还可以将控件加载耗时上传到我们自己的数据后台用于控件性能检测
@Override
protected void onCreate(Bundle savedInstanceState) {
setFactory2();
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
private void setFactory2() {
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 endTime = System.currentTimeMillis();
long cost = endTime - startTime;
Log.i("geekholt", name + "cost " + cost + "ms");
return view;
}
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
return null;
}
});
}
运行结果如下所示
image- 扩展
这里思考一个问题,为什么setFactory2
一定要在super.onCreate()
前调用呢?
//AppCompatActivity.java
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
final AppCompatDelegate delegate = getDelegate();
//这里主要关注installViewFactory方法
delegate.installViewFactory();
delegate.onCreate(savedInstanceState);
...
super.onCreate(savedInstanceState);
}
官方提供的AppCompatDelegate
子类实现,如AppCompatDelegateImplN
。帮助我们实现了AppCompat
风格组件的向下兼容,利用AppCompatDelegateImplN
提供的Factory2
将TextView
等组件替换为AppCompatTextView
,这样就可以使用一些新的属性,如autoSizeMinTextSize
//AppCompatDelegateImplV9.java
@Override
public void installViewFactory() {
LayoutInflater layoutInflater = LayoutInflater.from(mContext);
if (layoutInflater.getFactory() == null) {
LayoutInflaterCompat.setFactory2(layoutInflater, this);
} else {
if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImplV9)) {
Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
+ " so we can not install AppCompat's");
}
}
}
/**
* From {@link LayoutInflater.Factory2}.
*/
@Override
public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
// First let the Activity's Factory try and inflate the view
final View view = callActivityOnCreateView(parent, name, context, attrs);
if (view != null) {
return view;
}
// If the Factory didn't handle it, let our createView() method try
return createView(parent, name, context, attrs);
}
上述代码可知,如果AppCompatActivity
未在onCreate
之前设置LayoutInflater
的Factory
,则AppCompatActivity
会尝试设置一个Factory2
,其中Factory2
在AppCompatDelegate
的具体子类代码中实现
//LayoutInflate.java
public void setFactory2(Factory2 factory) {
if (mFactorySet) {
throw new IllegalStateException("A factory has already been set on this LayoutInflater");
}
if (factory == null) {
throw new NullPointerException("Given factory can not be null");
}
mFactorySet = true;
if (mFactory == null) {
mFactory = mFactory2 = factory;
} else {
mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
}
}
在同一个Actiivty
内,setFactory2
不可以重复调用,所以,如果在super.onCreate
之后设置就会抛出异常
这里有一个问题不知道读者有没有注意到,既然setFactory2
不能重复调用,那如果我们主动在onCreate()之前调用了setFactory2
,会不会使系统的AppCompatTextView
等失效呢?答案其实是不会的,我们只需要在执行自己的定制化操作之前调用 getDelegate().createView(parent, name, context, attrs)
就不会有问题了,原因是什么,我想大家看过文章之后再自己翻阅一下源码应该就很容易理解了
如现在有一个全局替换Textview
字体需求,我们可以这么做
LayoutInflaterCompat.setFactory2(LayoutInflater.from(this), new LayoutInflater.Factory2() {
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
//调用兼容类的createView
View view = getDelegate().createView(parent, name, context, attrs);
if (view != null && (view instanceof TextView)) {
//你可以在这里直接new自定义View
//你可以在这里将系统类替换为自定义View
//替换字体示例
((TextView) view).setTypeface(typeface);
}
return view;
}
});
5 总结
本文介绍了如何获取布局加载耗时以及单个控件的加载耗时,同时还介绍了Factory2
的使用,掌握了Factory2的原理和使用方法后,还能帮助我们做很多非常有意思的事情,本文抛砖引玉,更多的使用方法就需要大家自己手动尝试啦!
网友评论