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,例如 <TextView />;正文45 的代码示意创立的是自定义的 View,例如 <com.xr.MyView /> ,该办法 [createView(String name, String prefix, AttributeSet attrs) 办法] 的第二个参数示意标签的前缀,为 null 是因为不须要增加前缀,<com.xr.MyView /> 标签的前缀是 com.xr. ,所以不须要再增加前缀了;正文45 的代码调用的是 LayoutInflater 的 createView(String name, String prefix, AttributeSet attrs) 办法,正文44 的代码也最终还是调用到 LayoutInflater 的 createView(String name, String prefix, AttributeSet attrs) 办法,所以咱们跟踪正文44 的代码就好,看一下 LayoutInflater 的 onCreateView(View parent, String name, AttributeSet attrs) 办法;
图片
看正文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 {
@Overridepublic 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;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTv = findViewById(R.id.tv); Log.d("MainActivity","TextView----" + mTv);}@Overridepublic 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 :
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
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"><CustomTextView android:id="@+id/tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" android:textColor="#FF0000" />
</FrameLayout>
程序运行后的界面如下所示:
图片
日志打印如下所示:
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 ”。