关于android:Android中的LayoutInflater分析三

42次阅读

共计 8245 个字符,预计需要花费 21 分钟才能阅读完成。

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 {

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

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

图片

日志打印如下所示:

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