PS:本文系转载文章,阅读原文可读性会更好,文章末尾有原文链接
ps:本篇文章是基于 Android Api 26 来分析的
目录
1、LayoutInflater 创建 View 过程
1、1 LayoutInflater 的 createViewFromTag 方法分析
1、2 创建 View 时不可忽视的耗时
1、3 自定义一个 LayoutInflater.Factory
1、LayoutInflater 创建 View 过程
1、1 LayoutInflater 的 createViewFromTag 方法分析
我们接着Android中的LayoutInflater分析(二)这篇文章继续分析,我们看回Android中的LayoutInflater分析(二)这篇文章中给出的 LayoutInflater 的 createViewFromTag 方法;
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
......
try {
View view;
//41、
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
//42、
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
//43、
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
if (view == null) {
......
try {
if (-1 == name.indexOf('.')) {
//44、
view = onCreateView(parent, name, attrs);
} else {
//45、
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
} catch (InflateException e) {
......
} catch (ClassNotFoundException e) {
......
} catch (Exception e) {
......
}
}
在Android中的LayoutInflater分析(二)这篇文章中,我们写的界面类不是继承于 Activity,而是继承于 AppCompatActivity,所以会走注释41 的代码;如果我们写的界面类是继承于 Activity,那么注释41、42、43 的代码不会执行,而会执行注释44 或者注释45 的代码;注释44 的代码表示创建的是系统 View,例如
图片
看注释53 的代码,这里调用的是 LayoutInflater 的 onCreateView(String name, AttributeSet attrs) 方法吗?显然不是,从Android中的LayoutInflater分析(一)这篇文章可以知道,Activity、Service 和 Application 作为环境上下文拿到的 LayoutInflater 其实是 PhoneLayoutInflater,所以我们看的是 PhoneLayoutInflater 的 onCreateView(String name, AttributeSet attrs) 方法
图片
看注释54 的代码,也就是通过 LayoutInflater 的 createView(String name, String prefix, AttributeSet attrs) 方法创建 View,该方法最终通过反射的机制创建 View;我们看看注释54 的代码外围的 for 循环里的 sClassPrefixList 是什么,且看它的定义;
图片
哦原来是存放 View 前缀的数组,也就是说我们大多数用的系统 View 都是放在 android.widget 、 android.webkit 、 android.app 这3个包下面;那这里有的人可能就有疑问了,假设我在 xml 文件中写的系统 View 是 SurfaceView,SurfaceView 是在 android.view 包下面,为什么能创建成功,而且注释54 的代码最终通过反射机制创建 View,应该会有找不到类的异常信息出现啊;假设我们创建的是 SurfaceView,会尝试循环 sClassPrefixList 数组里前缀并进行拼接,如果通过反射机制创建的 View 时,发现类找不到,本应该抛出类找不到的异常,但是注释54 的代码外围有一个 try catch 语句,只是捕获并没有抛出异常,所以不影响程序往下走;当 sClassPrefixList 循环完了,发现创建的 View 还是空的,就会走注释55 的代码,也就是调用 LayoutInflater 的 onCreateView(String name, AttributeSet attrs) 方法;
图片
看注释56 的代码,LayoutInflater 的 onCreateView(String name, AttributeSet attrs) 方法还是会调用到 LayoutInflater 的 createView(String name, String prefix, AttributeSet attrs) 方法,又回到了通过反射机制创建 View 的方法,这回终于明白 SurfaceView 在 android.view 包下通过反射机制也能创建成功了吧。
好,我们往下看通过反射机制创建 View 的细节,也就是 LayoutInflater 的 createView(String name, String prefix, AttributeSet attrs) 方法;
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
//57、
Constructor extends View> constructor = sConstructorMap.get(name);
//58、
if (constructor != null && !verifyClassLoader(constructor)) {
constructor = null;
//59、
sConstructorMap.remove(name);
}
......
try {
......
if (constructor == null) {
// Class not found in the cache, see if it's real, and try to add it
//60、
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
//61、
if (mFilter != null && clazz != null) {
boolean allowed = mFilter.onLoadClass(clazz);
if (!allowed) {
failNotAllowed(name, prefix, attrs);
}
}
......
//62、
sConstructorMap.put(name, constructor);
} else {
// If we have a filter, apply it to cached constructor
//63、
if (mFilter != null) {
// Have we seen this name before?
Boolean allowedState = mFilterMap.get(name);
if (allowedState == null) {
// New class -- remember whether it is allowed
//64、
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
mFilterMap.put(name, allowed);
if (!allowed) {
//65、
failNotAllowed(name, prefix, attrs);
}
} else if (allowedState.equals(Boolean.FALSE)) {
failNotAllowed(name, prefix, attrs);
}
}
}
......
//66、
final View view = constructor.newInstance(args);
......
return view;
} catch (NoSuchMethodException e) {
......
} catch (ClassCastException e) {
......
} catch (ClassNotFoundException e) {
......
} catch (Exception e) {
......
} finally {
......
}
}
注释57 的代码表示从 HashMap 中根据 name 拿出一个具体 View 的构造器,比如说 TextView 的构造器;注释58 的代码表示如果构造器不为空且不是同一个构造器,那么就执行注释59 的代码;注释60、64 的代码表示通过反射机制创建对应的具体 View 的 Class 对象,比如 TextView 对应的 Class 是 TextView.class;注释61、63 的 mFilter 不为空可以起到拦截是否被允许创建该视图类的对象的作用;注释66 的代码表示通过构造器并传入参数真正实现创建具体的 View 对象,args 是长度为2的数组,所以调用的是具体的 View 的2个参数的构造方法;注释65 的代码表示 !allowed 为 true 时,那么就不允许创建该 View 的类对象,直接抛出异常,不信的话,我们可以看看 LayoutInflater 的 failNotAllowed(String name, String prefix, AttributeSet attrs) 方法;
private void failNotAllowed(String name, String prefix, AttributeSet attrs) {
throw new InflateException(attrs.getPositionDescription()
+ ": Class not allowed to be inflated "+ (prefix != null ? (prefix + name) : name));
}
看到了没,只有抛出异常的一行代码。
1、2 创建 View 时不可忽视的耗时
如果界面类继承的是 Activity,而 LayoutInflater 在 View 对象的创建过程中使用了大量反射,如果某个布局界面内容比较复杂,这过程耗时是不可忽视的;一些情况下可能是某个 View 的创建过程需要执行 4 次,比如前面提到的 SurfaceView,因为系统默认遍历规则依次为 android.weight、android.webkit 和 android.app,但是由于 SurfaceView 属于 android.view 目录下,所以需要第 4 次进行反射创建对应的 Class 才可以正确加载,这个效率会有点慢;界面类直接继承 Activity 并用 PhoneLayoutInflater 对 View 进行创建的过程中简单粗暴,这就给我们留下了很多优化的空间。
1、3 自定义一个 LayoutInflater.Factory
我们知道,如果我们写的界面类继承的是 Activity,解析 xml 布局文件的标签就会走注释44 或者注释45 的代码,就会用到反射机制创建 View,这样的创建 View 就会得简单粗暴;从Android中的AppCompatActivity的偷梁换柱之UI偷换这一篇文章可以知道,当我们写的界面类直接继承于 AppCompatActivity 时,那么就会走这篇文章注释41 的代码,mFactory2 就不为空,mFactory2 本质上是 LayoutInflater.Factory2 接口, AppCompatActivity 通过 AppCompatDelegate 的具体实现类设置好 mFactory2 的值,mFactory2 的 onCreateView(View parent, String name, Context context, AttributeSet attrs) 方法调用到 AppCompatDelegate 具体实现类的 onCreateView(View parent, String name, Context context, AttributeSet attrs)方法,AppCompatDelegate 具体实现类最终会调用到 AppCompatViewInflater 的 createView(View parent, final String name, @NonNull Context context,@NonNull AttributeSet att-rs,boolean inheritContext,boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) 方法(简称 A 方法),A 方法优先根据标签名字直接 new 一个具体的 View,这就优化的具体 View 的创建过程;其本质是 AppCompatActivity 间接的设置了 LayoutInflater.Factory2,LayoutInflater.Factory2 再间接用代理 AppCompatViewInflater 创建具体 View。
那如果我的界面类直接继承的是 Activity,我也想优化具体 View 的创建过程怎么办?我们就自定义一个 LayoutInflater.Factory 并在 Activity 的子类设置 PhoneLayoutInflater 的 mFactory 属性的值,这样不仅优化创建具体 View 的过程,还可以自己写 “自定义的系统 View ” 呢。好,我们现在写一个 demo 测试一下;
(1)自定义一个 View :
public class CustomTextView extends AppCompatTextView {
public CustomTextView(Context context) {
super(context);
}
public CustomTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
(2)写一个 LayoutInflater.Factory 的实现类 MyFactory :
public class MyFactory implements LayoutInflater.Factory {
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
if (name.equals("CustomTextView")) {
return new CustomTextView(context,attrs);
}
return null;
}
}
(3)写一个 Activity,名叫 MainActivity :
public class MainActivity extends Activity {
TextView mTv;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTv = findViewById(R.id.tv);
Log.d("MainActivity","TextView----" + mTv);
}
@Override
public Object getSystemService(@NonNull String name) {
if (name.equals(Context.LAYOUT_INFLATER_SERVICE)) {
LayoutInflater inflater = (LayoutInflater)super.getSystemService(name);
if (inflater.getFactory() == null) {
inflater.setFactory(new MyFactory());
}
return inflater;
}
return super.getSystemService(name);
}
}
(4)MainActivity 对应的 xml 布局文件 activity_main.xml :
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
程序运行后的界面如下所示:
图片
日志打印如下所示:
02-12 13:43:54.309 20564-20564/com.epbox.userrecycle.myapplication D/MainActivity: TextView----com.epbox.userrecycle.myapplication.CustomTextView{bf6a0a9 V.ED..... ......ID 0,0-0,0 #7f07007b app:id/tv}
从日志可以看出,打印的确实是自定义的 View;从 activity_main.xml 布局文件可以看出,我们写的标签确实是自定义的 View 而且是不写前缀的,也就是不写包名出来,这就实现了 “自定义的系统 View ”。