深入理解View知识系列一- setContentView和LayoutInflater源码原理分析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yulong0809/article/details/79277574

这个系列的来由来:

在工作中经常有小伙伴问我,到底怎么自定义控件,什么时候重写这onMeasure啊,onMeasure中的两个参数怎么来的,View的三大流程是怎么回事,等等一系列的问题,后来我发现其实是因为他们对View的知识不是特别的了解,或者说知道但是都是零零碎碎的,没有系统的串下来,所以就有了这一系列的文章在组内每周进行分享,先将每周整理的文档发布出来希望可以帮助更多的朋友。 其实自定义控件说难确实难,说简单又特别简单,难是因为自定义控件是和业务需求相关联的,没有什么统一的标准,所以可能有点摸不着头脑,但是这并不是最主要的难点,最重要的是对View的相关知识掌握不够深,所以才有无从下手的感觉,其实就是只要掌握的View的相关知识,剩下的基本就是按照业务逻辑、相关计算、api调用等了,但是View所设计的知识点太多了,如果想完全弄明白,须要一个系统的学习,所以我整理了这些文章给团队分享。

深入理解View知识系列一- setContentView和LayoutInflater源码原理分析

深入理解View知识系列二- View底层工作原理以及View的绘制流程

深入理解View知识系列三-Window机制、Canvas的由来、Android事件的由来

深入理解View知识系列四-View的测量规则以及三大方法流程


那么我们就要从Activity的setContentView作为我们应用第一次涉及View的地方,所以非常有必要知道它背后做了什么,而且也是我们了解View知识的起点.本文注重的是setContentView和LayoutInflater中的逻辑,期间可能会设计到其他的知识,会在以后深入讲解。

本篇源码基于Android7.0

1. 先来看一下Activity中的setContentView()方法,一共有三个重载的方法,我们去看一下Activity中的这个方法。

源码路径:
/frameworks/base/core/java/android/app/Activity.java

    //传入布局id的
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
//传入一个View的
public void setContentView(View view) {
getWindow().setContentView(view);
initWindowDecorActionBar();
}
//传入View和LayoutParams的
public void setContentView(View view, ViewGroup.LayoutParams params) {
getWindow().setContentView(view, params);
initWindowDecorActionBar();
}

2.上面全部调用了getWindow().setContentView()方法,那么这个Window是谁呢?其实它是PhoneWindow,这个Window再Activity中的attach里被赋值,这个attach方法是在Activity被创建以后在ActivityThread中被调用的,这里我们只分析View的流程,至于Activity和Window以后再说,暂时知道Window的具体实现是PhoneWindow就好了,先看一下这个Window被创建的过程。

  final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback) {
....
//创建PhoneWindow
mWindow = new PhoneWindow(this, window, activityConfigCallback);
//设置回调接口
mWindow.setWindowControllerCallback(this);
//给Window设置回调监听Callback
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
....
//设置WindowManger
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
mWindow.setColorMode(info.colorMode);
}

3. 上边主要逻辑就是创建PhoneWindow,给Window设置一些回调监听和WindowManger,我们知道了Window的实现类是PhoneWindow,下边我们去看一下PhoneWindow的setContentView方法,设置布局的setContentView,PhoneWindow中同样有三个重载的方法,这里我们选择最常用的资源id来分析,至于其他两个看一下就好了,逻辑基本相同。

这里简单说一下WindowManger,我们知道平时可以通过WindowManger的可以在屏幕上添、更新、删除一个View,其实这几个方法并不是WindowManger的,WindowManger实现了ViewManger接口,这几个方法是ViewManger接口中的,至于Window、WindowManger会在以后分享中单独讲说。

源码路径:
/frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java

    @Override
public void setContentView(int layoutResID) {
//1.mContentParent就是一个ViewGroup,第一次进入时肯定为null
if (mContentParent == null) {
//创建DecorView
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
//如果不为空会清空所有的View,
mContentParent.removeAllViews();
}
//是否有过场动画.略过
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
//2.加载我们的布局id后添加到mContentParent中
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
//3.回调Activity
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}

4. PhoneWindow的setContentView中主要做了三件事,

  • 判断mContentParent是否为空,如果为空则执行installDecor方法,否则清空所有的子View
  • 通过LayoutInflater将我们设置的布局添加到mContentParent
  • 通过Window拿到Callback监听对象,也就是Activity,在attach中设置过,并回调它的onContentChanged

5.接着看上边先来看一下installDecor()方法

源码位置:
/frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java

扫描二维码关注公众号,回复: 3056118 查看本文章
    private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
///第一次进来肯定为空,会走到这里,创建DecorView
mDecor = generateDecor(-1);
...
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
//给mContentParent赋值,mContentParent其实就是展示我们设置布局的父View
mContentParent = generateLayout(mDecor);
...
}
}

6.installDecor方法中执行了两个逻辑,一个是给mDecor赋值,一个是给mContentParent赋值,mContentParent其实就是展示我们设置布局的父View,我们先来看一下generateDecor方法,

源码位置:
/frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java

    protected DecorView generateDecor(int featureId) {
Context context;
//根据不同情况创建Context上下文
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, getContext().getResources());
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
//直接new一个DecorView,其实就是一个FrameLayout
return new DecorView(context, featureId, this, getAttributes());
}
//看一下DecorView类的声明
//继承自FrameLayout
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
....
}
  1. 上面我们看到了DecorView就是继承自FrameLayout,那么也就是说它其实就是一个ViewGroup,最终也是继承自View的,下面我们再来看一下generateLayout

源码位置:
/frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java

    protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
TypedArray a = getWindowStyle();
1.设置Window样式
....
//设置是否有title
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
}
2.根据样式选择布局id,然后添加到DecorView中
//布局id
int layoutResource;
int features = getLocalFeatures();
//省略n多判断选择不同布局id
...
if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
...
//有title
layoutResource = R.layout.screen_title;
}
...
//省略n多判断选择不同布局id
else {
//无title
layoutResource = R.layout.screen_simple;
}
mDecor.startChanging();
//将布局文件添加到DecorView中,就是inflater后addview
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
3.查找id为content的view并返回
//ID_ANDROID_CONTENT是在Window中的常量值,代表了装我们自己设置布局的父FrameLayout,id为content
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
.....
//返回id为content的FrameLayout赋值给mContentParent
return contentParent;
}
//再来看一下选择的布局文件
//API 26中的 screen_title.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:fitsSystemWindows="true">
<!-- Popout bar for action modes -->
<ViewStub android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarTheme" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="?android:attr/windowTitleSize"
style="?android:attr/windowTitleBackgroundStyle">
<TextView android:id="@android:id/title"
style="?android:attr/windowTitleStyle"
android:background="@null"
android:fadingEdge="horizontal"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
<FrameLayout android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
//API 26中的 screen_simple.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<ViewStub android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarTheme" />
<FrameLayout
android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundInsidePadding="false"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

7. generateLayout里的代码相等的多,但是主要逻辑就三件事

  • 设置Window的样式
  • 通过样式来选择不同的布局资源文件并添加到DecorView中,用来装载我们设置View的父View是一个FrameLayout,id为content
  • 查找id为content的FrameLayout并返回赋值给mContentParent

8. 到这里setContentView的逻辑就走完了,总结一下,Activity中设置的View其实是通过getWindow来完成的,而Window的实现类是PhoneWindow,然后它创建了一个DecorView,接着又根据Window的样式添加了一个布局到DecorView中,最后通过LayoutInflater将我们设置的View添加到一个id为content中的FrameLayout里,最后获取Callback回调接口并回调onContentChanged方法,Activity中这个方法是空实现,这个Callback接口就是Activity,是在attach中设置的监听,这个Callback接口中有很多的方法,但是有几个是我们非常熟悉的,来看一下

   public interface Callback {
....
//dispatchTouchEvent方法
public boolean dispatchTouchEvent(MotionEvent event);
//onContentChanged方法
public void onContentChanged();
//onWindowFocusChanged方法
public void onWindowFocusChanged(boolean hasFocus);
//onAttachedToWindow方法
public void onAttachedToWindow();
//onDetachedFromWindow方法
public void onDetachedFromWindow();
...
}

记住一点啊,现在只是创建了的DecorView和我们的View,但是View还没有真正的显示出来呢,因为View还没有走绘制的流程呢,至于到底是在哪里开始绘制View并展示的,下一次再说。

9.接下来我们来分析一下LayoutInflater解析机制。再来回顾一下PhoneWindow中setContent中的一句的代码, mLayoutInflater.inflate(layoutResID, mContentParent),解析布局文件并添加到mContentParent这个FrameLayout中,这个LayoutInflater是在PhoneWindow的构造中被创建

    public PhoneWindow(Context context) {
super(context);
//和我们平时使用一样
mLayoutInflater = LayoutInflater.from(context);
}

10. 那么我们去从LayoutInflater.from(context)来分析

源码路径:
/frameworks/base/core/java/android/view/LayoutInflater.java

    public static LayoutInflater from(Context context) {
//通过Context获取了一个系统Service,这个方法只是一个封装而已
LayoutInflater LayoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}

11.和我们平时使用一样,接着我们就来看一下LayoutInflater中的inflate方法,它有4个重载的方法

源码路径:
/frameworks/base/core/java/android/view/LayoutInflater.java

    //两个参数的
//第一个参数:布局id
//第二个参数:父view
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
//调用了三个参数的
return inflate(resource, root, root != null);
}
//三个参数的,
//第一个参数:布局id
//第二个参数:父view
//第三个参数:是否将解析后的View添加到父View中
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
//获取Resources对象
final Resources res = getContext().getResources();
//创建Xml解析器
final XmlResourceParser parser = res.getLayout(resource);
try {
//调用了另一三个参数,带xml解析器的
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
//两个参数的
//第一个参数:xml解析器
//第二个参数:父view
public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {
//调用了传入xml解析器的三个参数的
return inflate(parser, root, root != null);
}
//三个参数的带xml解析的
//第一个参数:xml解析器
//第二个参数:父view
//第三个参数:是否添加到父View
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
...
return result;
}
}

10. 通过上面我们可以看出来,无论调用哪个方法,最终都会走到有xml解析器三参方法中,另外注意这两个参数的方法都会调用自己同类的三参方法,但是第三个参数传入的是root != null,那么也就是说,如果第二个参数传入的父view不为空,那么就会将解析出来的View添加到View中,否则反之。既然最终都会调用到带解析器的我们就直接去看这个方法了

源码位置:
/frameworks/base/core/java/android/view/LayoutInflater.java

    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
//初始化返回值,初始值为父View
View result = root;
try {
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
//如果xml根节点不等于START_TAG,那么说明xml写的有问题,直接抛异常了
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
//获取当前节点的名称
final String name = parser.getName();
...
//判断当前节点是否是merge标签,如果是merge标签,继续判断是否要添加到父View中,如果不添加,
//则抛出异常,因为merge本身不是一个View,它只是为了减少布局嵌套存在的
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
//如果添加到父view,则继续解析,这个方法中的逻辑和下面else逻辑类似
rInflate(parser, root, inflaterContext, attrs, false);
} else {
//1. 根据tag的名称创建View,例如<LinearLayout>
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// 创建LayoutParams
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// 给创建的根View设置LayoutParams
temp.setLayoutParams(params);
}
}
// Inflate all children under temp against its context.
//递归进行创建所有子View,这个方法中也调用的是rInflate,逻辑和这里else类似
rInflateChildren(parser, temp, attrs, true);
//如果第三个参数是true,则将创建的View添加到父View中
if (root != null && attachToRoot) {
root.addView(temp, params);
}
//将返回值指向刚创建的view
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException e) {
final InflateException ie = new InflateException(e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(parser.getPositionDescription()
+ ": " + e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
return result;
}
}

11. 上面逻辑是找到根节点的标签,判断一下是否为merge标签,如果是继续判断第三个参数,如果是false则直接抛出异常,否则直接解析调用了rInflate,如果不是merge则进入else逻辑中,其实else中也会进入rInflate方法,所以我们直接分析else逻辑。else中的逻辑是根据标签的名称创建出View,接着会递归创建所有的子View,最后判断是返回root还是temp,也就是说是返回父view还是返回刚创建的View。接下来我们先来看一下根据tag创建View的方法createViewFromTag,这个方法又会直接跳转到另一个重载的方法中

  1. merge标签 主要是用来减少布局嵌套的,不可以单独作为一个View存在,这一点在源码中有体现
  2. 在后面判断是返回root还是返回temp时,判断了如果root不为空并且要添加temp到root中时则返回root,也就是父View,注意如果你直接使用的两个参数,而且传入的root不为空时,则默认代表需要添加到root中,也就是回返回root。只有root等于空或者第三个参数明确为false时才会返回temp。

源码位置:
/frameworks/base/core/java/android/view/LayoutInflater.java

    private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
//直接跳转了
return createViewFromTag(parent, name, context, attrs, false);
}
//另一个重载的
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
...
try {
View view;
//判断是否存在设置的解析工厂,如果存在则调用设置的解析工厂来创建view
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
//走到这里有两种情况
//1.没有被设置过解析工厂
//2.设置了解析工厂但是并没有返回View
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
//通过标签名中是否.来判断是否为Android自带的控件
if (-1 == name.indexOf('.')) {
//是Android自带的View
view = onCreateView(parent, name, attrs);
} else {
//不是Android自带的,看第二个参数传入的是null
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
//返回创建的view
return view;
} catch (InflateException e) {
throw e;
} catch (ClassNotFoundException e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
}
}

12.createViewFromTag方法就是判断是否设置过其他的解析工厂,如果设置过则直接调用解析工厂的onCreateView来生成view,接下来如果view还是为空则LayoutInflater会自己解析,它会根据标签的名字中是否包含.来判断是否为Android中自带的View,然后进行不同的操作,接下来我们先看一下,如果是Android自带的怎么处理的方法onCreateView,这个方法又会走到两个参数的重载方法中

解析工厂可以在我们的应用中通过LayoutInflaterCompat.setFactory2(LayoutInflater.from(this), new LayoutInflater.Factory2()来设置,然后在onCreateView中就可以通过这个特性来做一些事情,例如View在解析的过程中根据属性或者一些条件干一些事情,或者返回一个其他的View,因为通过上面代码可以看出来,如果我们自己做了创建View的工作后,LayoutInflater的逻辑就不会执行了。

    protected View onCreateView(View parent, String name, AttributeSet attrs)
throws ClassNotFoundException
{
//直接调用了两个参数的重载方法
return onCreateView(name, attrs);
}
//两个参数重载的方法
protected View onCreateView(String name, AttributeSet attrs)
throws ClassNotFoundException
{
//又调用了createView方法,
return createView(name, "android.view.", attrs);
}

13.又转createView这个方法中了,createView在上面的createViewFromTag中判断如果不是Android自带的控件也会调用这个方法,只不过第二个参数传入的是nul,而如果是Android自己的控件传入了android.view.,那么也就是说不管是不是Android自带的控件都会走到这个方法中,只不过第二个参数传值不同,来看一下这个方法

    public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
//查看缓存中是否存在这个标签名字的构造方法,
Constructor<? extends View> constructor = sConstructorMap.get(name);
//如果构造不为空 并且这个构造的ClassLoader不安全则删除缓存中的构造函数
if (constructor != null && !verifyClassLoader(constructor)) {
constructor = null;
sConstructorMap.remove(name);
}
Class<? extends View> clazz = null;
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
//构造为空
if (constructor == null) {
//注意看这里,还记得上面我们说的,不管是不是Android自带的控件最终都会走到这个方法中来,
//都会通过下面的方式加载Class,但是这里有几种情况,在下面会详情说明
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
//检查是否允许创建这个View,实现类有RemoteViews和AppWidgetHostView等
// 都是通过 return clazz.isAnnotationPresent(RemoteView.class);
if (mFilter != null && clazz != null) {
boolean allowed = mFilter.onLoadClass(clazz);
if (!allowed) {
failNotAllowed(name, prefix, attrs);
}
}
//获取构造函数,mConstructorSignature是一个常量,就是获取两参的构造函数
//static final Class<?>[] mConstructorSignature = new Class[] {
Context.class, AttributeSet.class};
constructor = clazz.getConstructor(mConstructorSignature);
//允许访问
constructor.setAccessible(true);
//将构造函数存入缓存
sConstructorMap.put(name, constructor);
} else {
// 判断是否存在过滤器
if (mFilter != null) {
//判断之前是否过滤过这个名字的class
Boolean allowedState = mFilterMap.get(name);
if (allowedState == null) {
// 如果没有则生成这个class
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
//并进行检查是否允许创建
boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
//最后存入这个名字的class已经被检查过了,
// 也就是说如果存在过滤器会在第一次准备创建前检查,如果检查过了则不会在去检查
mFilterMap.put(name, allowed);
if (!allowed) {
//不允许创建抛异常
failNotAllowed(name, prefix, attrs);
}
} else if (allowedState.equals(Boolean.FALSE)) {
//之前检查不允许创建则在这里抛出异常
failNotAllowed(name, prefix, attrs);
}
}
}
Object[] args = mConstructorArgs;
args[1] = attrs;
//创建这个view
final View view = constructor.newInstance(args);
if (view instanceof ViewStub) {
// Use the same context when inflating ViewStub later.
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
//返回这个view
return view;
} catch (NoSuchMethodException e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + (prefix != null ? (prefix + name) : name), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (ClassCastException e) {
// If loaded class is not a View subclass
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Class is not a View " + (prefix != null ? (prefix + name) : name), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (ClassNotFoundException e) {
// If loadClass fails, we should propagate the exception.
throw e;
} catch (Exception e) {
final InflateException ie = new InflateException(
attrs.getPositionDescription() + ": Error inflating class "
+ (clazz == null ? "<unknown>" : clazz.getName()), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}

14. 上面主要逻辑如下

  • 检查是否存在这个名字的构造函数
  • 如果存在则调用verifyClassLoader方法进行检查这个构造函数所持有的CLassLoader是否合法,其中有两个逻辑1.如果是BootClassLoader则直接返回ture,BootClassLoader是最Android中最顶层的ClassLoader,2.拿到Context的CLassLoader一直向上查找,只要有一个相等就返回true,否则不合法,不合法则将缓存中的构造函数赋值为null并清除缓存
  • 接着判断构造函数是否为空,如果为空则创建该名字的Class,这里有几种情况
  1. 如果是Android自带的控件,第二个参数传入的是android.view.
  2. 如果不是Android自带的控件传入的则是null
  3. 看一下这个三元运算,如果是Android自带控件会拼接名字,例如xml中用了一个TextView,这里最终加载的class就是android.view.TextView
  4. 如果不是Android自带的控件则就是xml中定义的标签,例如我们自定义的控件com.xx.CusttomView
  5. 调用asSubclass将加载的Class转换成View的子类Class,这是因为系统并不能确定你在xml中定义的标签就是View的子类,这也就说明了xml中使用的标签都必须是View的子类,否则将抛出ClassNotFoundException的异常
  • 接着判断是否存在过滤器,如果存在过滤器检查是否允许创建这个View,其中实现过滤器的有RemoteViews和AppWidgetHostView等,两者都是通过clazz.isAnnotationPresent(xxx.class)。来判断决定是否允许创建的,如果不允许创建则直接抛出异常,如果允许创建就接着往下走获取两参的构造函数并缓存该名字的构造函数
  • else中的逻辑可以看出,如果存在过滤器,Android为了减少性格的开销,只会在第一次去验证是否允许创建View,然后将结果存入mFilterMap中,下次进来直接会取上次的验证结果
  • 最后就是通过构造函数构造view并返回

15. 上面已经分析完了inflater中通过xml的标签创建view的逻辑,接着最后看一下递归创建子view的方法rInflateChildren

    final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
boolean finishInflate)
throws XmlPullParserException, IOException
{
//直接调用了rInflate方法,这个方法在inflater中判断如果是merge标签的情况下也会走这个方法
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
//直接分析这个方法就好了
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate)
throws XmlPullParserException, IOException
{
final int depth = parser.getDepth();
int type;
//循环遍历标签
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
final String name = parser.getName();
//requestFocus
if (TAG_REQUEST_FOCUS.equals(name)) {
parseRequestFocus(parser, parent);
} else if (TAG_TAG.equals(name)) {
//tag
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) {
//include
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
//merge
throw new InflateException("<merge /> must be the root element");
} else {
//正常标签View
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
//继续调用rInflateChildren方法接着再调回这个方法
rInflateChildren(parser, view, attrs, true);
viewGroup.addView(view, params);
}
}
//是否回调onFinishInflate方法
if (finishInflate) {
parent.onFinishInflate();
}
}

上面的逻辑很清楚,根据不同的标签进行不同的处理,这里有我们熟悉的include、merge,从这里也可以看出来merge只能作为根标签存在,而不能作为一个子标签存在,如果是正常的view标签则会继续调用rInflateChildren方法接着再调回这个方法,知道所有子标签遍历加载完成。到这里LayoutInflater的加载逻辑也就分析清楚了,下一次我们会分析View的底层工作原理和绘制流程。

猜你喜欢

转载自blog.csdn.net/yulong0809/article/details/79277574
今日推荐