Android中的LayoutInflater分析(三)

文章收藏的好句子:读书才是给自己最好的投资。

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) 方法;

0ec83a0f11e3df8231fd00c260bd9968.png

看注释53 的代码,这里调用的是 LayoutInflater 的 onCreateView(String name, AttributeSet attrs) 方法吗?显然不是,从Android中的LayoutInflater分析(一)这篇文章可以知道,Activity、Service 和 Application 作为环境上下文拿到的 LayoutInflater 其实是 PhoneLayoutInflater,所以我们看的是 PhoneLayoutInflater 的 onCreateView(String name, AttributeSet attrs) 方法

fc54f4d6be7da8ea809e756891088ba3.png

看注释54 的代码,也就是通过 LayoutInflater 的 createView(String name, String prefix, AttributeSet attrs) 方法创建 View,该方法最终通过反射的机制创建 View;我们看看注释54 的代码外围的 for 循环里的 sClassPrefixList 是什么,且看它的定义;

4cbaae099f93bb68884511792e7c33a5.png

哦原来是存放 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) 方法;

b714974473345e923cb8f28bb1d559d3.png

看注释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 :

<?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>

程序运行后的界面如下所示:

c43ef91a561a4930bc93b19ea8d6666a.png

日志打印如下所示:

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 ”。

本篇文章写到这里就结束了,由于技术水平有限,文章中难免会出错,欢迎大家批评指正。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值