setContentView和LayoutInflater源码原理分析

2024-02-28 10:38

本文主要是介绍setContentView和LayoutInflater源码原理分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本文注重的是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

    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的底层工作原理和绘制流程。

这篇关于setContentView和LayoutInflater源码原理分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/755262

相关文章

[职场] 公务员的利弊分析 #知识分享#经验分享#其他

公务员的利弊分析     公务员作为一种稳定的职业选择,一直备受人们的关注。然而,就像任何其他职业一样,公务员职位也有其利与弊。本文将对公务员的利弊进行分析,帮助读者更好地了解这一职业的特点。 利: 1. 稳定的职业:公务员职位通常具有较高的稳定性,一旦进入公务员队伍,往往可以享受到稳定的工作环境和薪资待遇。这对于那些追求稳定的人来说,是一个很大的优势。 2. 薪资福利优厚:公务员的薪资和

springboot家政服务管理平台 LW +PPT+源码+讲解

3系统的可行性研究及需求分析 3.1可行性研究 3.1.1技术可行性分析 经过大学四年的学习,已经掌握了JAVA、Mysql数据库等方面的编程技巧和方法,对于这些技术该有的软硬件配置也是齐全的,能够满足开发的需要。 本家政服务管理平台采用的是Mysql作为数据库,可以绝对地保证用户数据的安全;可以与Mysql数据库进行无缝连接。 所以,家政服务管理平台在技术上是可以实施的。 3.1

高仿精仿愤怒的小鸟android版游戏源码

这是一款很完美的高仿精仿愤怒的小鸟android版游戏源码,大家可以研究一下吧、 为了报复偷走鸟蛋的肥猪们,鸟儿以自己的身体为武器,仿佛炮弹一样去攻击肥猪们的堡垒。游戏是十分卡通的2D画面,看着愤怒的红色小鸟,奋不顾身的往绿色的肥猪的堡垒砸去,那种奇妙的感觉还真是令人感到很欢乐。而游戏的配乐同样充满了欢乐的感觉,轻松的节奏,欢快的风格。 源码下载

高度内卷下,企业如何通过VOC(客户之声)做好竞争分析?

VOC,即客户之声,是一种通过收集和分析客户反馈、需求和期望,来洞察市场趋势和竞争对手动态的方法。在高度内卷的市场环境下,VOC不仅能够帮助企业了解客户的真实需求,还能为企业提供宝贵的竞争情报,助力企业在竞争中占据有利地位。 那么,企业该如何通过VOC(客户之声)做好竞争分析呢?深圳天行健企业管理咨询公司解析如下: 首先,要建立完善的VOC收集机制。这包括通过线上渠道(如社交媒体、官网留言

基于Java医院药品交易系统详细设计和实现(源码+LW+调试文档+讲解等)

💗博主介绍:✌全网粉丝10W+,CSDN作者、博客专家、全栈领域优质创作者,博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌💗 🌟文末获取源码+数据库🌟 感兴趣的可以先收藏起来,还有大家在毕设选题,项目以及论文编写等相关问题都可以给我留言咨询,希望帮助更多的人  Java精品实战案例《600套》 2023-2025年最值得选择的Java毕业设计选题大全:1000个热

数据库原理与安全复习笔记(未完待续)

1 概念 产生与发展:人工管理阶段 → \to → 文件系统阶段 → \to → 数据库系统阶段。 数据库系统特点:数据的管理者(DBMS);数据结构化;数据共享性高,冗余度低,易于扩充;数据独立性高。DBMS 对数据的控制功能:数据的安全性保护;数据的完整性检查;并发控制;数据库恢复。 数据库技术研究领域:数据库管理系统软件的研发;数据库设计;数据库理论。数据模型要素 数据结构:描述数据库

计算机组成原理——RECORD

第一章 概论 1.固件  将部分操作系统固化——即把软件永恒存于只读存储器中。 2.多级层次结构的计算机系统 3.冯*诺依曼计算机的特点 4.现代计算机的组成:CPU、I/O设备、主存储器(MM) 5.细化的计算机组成框图 6.指令操作的三个阶段:取指、分析、执行 第二章 计算机的发展 1.第一台由电子管组成的电子数字积分和计算机(ENIAC) 第三章 系统总线

美容美发店营销版微信小程序源码

打造线上生意新篇章 一、引言:微信小程序,开启美容美发行业新纪元 在数字化时代,微信小程序以其便捷、高效的特点,成为了美容美发行业营销的新宠。本文将带您深入了解美容美发营销微信小程序,探讨其独特优势及如何助力商家实现业务增长。 二、微信小程序:美容美发行业的得力助手 拓宽客源渠道:微信小程序基于微信社交平台,轻松实现线上线下融合,帮助商家快速吸引潜在客户,拓宽客源渠道。 提升用户体验:

风水研究会官网源码系统-可展示自己的领域内容-商品售卖等

一款用于展示风水行业,周易测算行业,玄学行业的系统,并支持售卖自己的商品。 整洁大气,非常漂亮,前端内容均可通过后台修改。 大致功能: 支持前端内容通过后端自定义支持开启关闭会员功能,会员等级设置支持对接官方支付支持添加商品类支持添加虚拟下载类支持自定义其他类型字段支持生成虚拟激活卡支持采集其他站点文章支持对接收益广告支持文章评论支持积分功能支持推广功能更多功能,搭建完成自行体验吧! 原文

GaussDB关键技术原理:高性能(二)

GaussDB关键技术原理:高性能(一)从数据库性能优化系统概述对GaussDB的高性能技术进行了解读,本篇将从查询处理综述方面继续分享GaussDB的高性能技术的精彩内容。 2 查询处理综述 内容概要:本章节介绍查询端到端处理的执行流程,首先让读者对查询在数据库内部如何执行有一个初步的认识,充分理解查询处理各阶段主要瓶颈点以及对应的解决方案,本章以GaussDB为例讲解查询执行的几个主要阶段