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

相关文章

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

hdu4407(容斥原理)

题意:给一串数字1,2,......n,两个操作:1、修改第k个数字,2、查询区间[l,r]中与n互质的数之和。 解题思路:咱一看,像线段树,但是如果用线段树做,那么每个区间一定要记录所有的素因子,这样会超内存。然后我就做不来了。后来看了题解,原来是用容斥原理来做的。还记得这道题目吗?求区间[1,r]中与p互质的数的个数,如果不会的话就先去做那题吧。现在这题是求区间[l,r]中与n互质的数的和

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

SWAP作物生长模型安装教程、数据制备、敏感性分析、气候变化影响、R模型敏感性分析与贝叶斯优化、Fortran源代码分析、气候数据降尺度与变化影响分析

查看原文>>>全流程SWAP农业模型数据制备、敏感性分析及气候变化影响实践技术应用 SWAP模型是由荷兰瓦赫宁根大学开发的先进农作物模型,它综合考虑了土壤-水分-大气以及植被间的相互作用;是一种描述作物生长过程的一种机理性作物生长模型。它不但运用Richard方程,使其能够精确的模拟土壤中水分的运动,而且耦合了WOFOST作物模型使作物的生长描述更为科学。 本文让更多的科研人员和农业工作者

MOLE 2.5 分析分子通道和孔隙

软件介绍 生物大分子通道和孔隙在生物学中发挥着重要作用,例如在分子识别和酶底物特异性方面。 我们介绍了一种名为 MOLE 2.5 的高级软件工具,该工具旨在分析分子通道和孔隙。 与其他可用软件工具的基准测试表明,MOLE 2.5 相比更快、更强大、功能更丰富。作为一项新功能,MOLE 2.5 可以估算已识别通道的物理化学性质。 软件下载 https://pan.quark.cn/s/57

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

衡石分析平台使用手册-单机安装及启动

单机安装及启动​ 本文讲述如何在单机环境下进行 HENGSHI SENSE 安装的操作过程。 在安装前请确认网络环境,如果是隔离环境,无法连接互联网时,请先按照 离线环境安装依赖的指导进行依赖包的安装,然后按照本文的指导继续操作。如果网络环境可以连接互联网,请直接按照本文的指导进行安装。 准备工作​ 请参考安装环境文档准备安装环境。 配置用户与安装目录。 在操作前请检查您是否有 sud