深入分析LayoutInflater

在日常的开发过程如果需要加载一个布局,这时候通常会用到加载布局的系统服务类——LayoutInflater。比如在RecyclerView的onCreateViewHolder方法中,或者ListView的getView方法中,亦或是我们经常使用的的Activity的setContentView()加载布局方法,其实它底层用的还是LayoutInflater服务。由此可见只要涉及到布局加载就离不开LayoutInflater,那么弄清LayoutInflater的内在逻辑,对于我们学习如何加载布局就显得至关重要。

学习源码一定不要像无头苍蝇似的乱撞,否则就深入源码的苦海无法自拔。带着问题找答案永远要比盲目更容易柳暗花明,所以在开始学习LayoutInflater之前思考几件事情。

  1. 如何获取获取LayoutInflater服务;
  2. 和获取与之对应的就是LayoutInflater服务是什么时候注册的;
  3. LayoutInflater服务是如何加载布局的;

带着这三个问题开始这篇文章,下面以RecyclerView中的onCreateViewHolder方法为例:

   @NonNull
   @Override
   public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
       //加载布局的时候需要注意要把parent传进去
       View v = inflater.inflate(R.layout.layout_item, parent, false);
       //返回MyViewHolder的对象
       return new MyViewHolder(v);
   }

通常情况通过LayoutInflater.from(context)来获取LayoutInflater服务,在调用inflate( int resource,ViewGroup root, boolean attachToRoot)方法。

第一个参数:添加的布局子布局;

第二个参数:想要添加到的父布局 ——root为null时第一个参数中最外层的布局大小无效,有值的时候最外层的布局大小有效,后面会解释为什么。

第三个参数:是否直接添加到第二个参数布局上面——true代表layout文件填充的View会被直接添加进root,而传入false则代表创建的View会以其他方式被添加进root

先看LayoutInflater.from(context)的实现:

public static LayoutInflater from(Context context) {
        LayoutInflater LayoutInflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (LayoutInflater == null) {
            throw new AssertionError("LayoutInflater not found.");
        }
        return LayoutInflater;
}

可以看出form函数内部调用的是Context的getSystemService方法,而大家都知道Context又是个抽象类,getSystemService也是够抽象方法。所以现在要找的是:这里使用的Context对象的实现到底是谁?其实在Application、Activity、Service中都会存在一个Context对象,显然这里用不到Application和Service中的Context。View通常都显示在Activity中,所以使用的是Activity中的Context。

Activity的入口是ActivityThread的main方法,main方法中会创建一个ActivityThead对象,在UI线程中启动消息循环,创建新的Activity、Context对象。然后把这个Context传递给Activity。

 public static void main(String[] args) {
       //代码...
        Process.setArgV0("<pre-initialized>");
        Looper.prepareMainLooper();
        //代码...
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();
}
    private void attach(boolean system, long startSeq) {
        sCurrentActivityThread = this;
        mSystemThread = system;
        if (!system) {
            ViewRootImpl.addFirstDrawHandler(new Runnable() {
                @Override
                public void run() {
                    ensureJitEnabled();
                }
            });
            android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
                                                    UserHandle.myUserId());
            RuntimeInit.setApplicationObject(mAppThread.asBinder());
            final IActivityManager mgr = ActivityManager.getService();
            try {
                mgr.attachApplication(mAppThread, startSeq);
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
            //代码... 
        } else {
            //代码... 
        }
    }

在main方法中ActivityThread会调用attach函数,参数为false表明非系统应用。会通过binder机制与ActivityManagerServer通信(关于Binder通信不是本篇主要内容,可以查看老罗的系列博客),并最终回调ActivityThread的handleLaunchActivity方法:

 @Override
 public Activity handleLaunchActivity(ActivityClientRecord r,
            PendingTransactionActions pendingActions, Intent customIntent) {
        // If we are getting ready to gc after going to the background, well
        // we are back active so skip it.
        //代码...
        final Activity a = performLaunchActivity(r, customIntent);
        //代码...
        return a;
 }
  /**  Core implementation of activity launch. */
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ActivityInfo aInfo = r.activityInfo;
        if (r.packageInfo == null) {
            r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
                    Context.CONTEXT_INCLUDE_CODE);
        }
        //代码...
        //获取context对象
        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess();
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to instantiate activity " + component
                    + ": " + e.toString(), e);
            }
        }

        try {
            //代码...
            if (activity != null) {
             //代码...
                //将appContext等对象attach到activity中
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback);

                if (customIntent != null) {
                    activity.mIntent = customIntent;
                }
                r.lastNonConfigurationInstances = null;
                checkAndBlockForNetworkAccess();
                activity.mStartedActivity = false;
                int theme = r.activityInfo.getThemeResource();
                if (theme != 0) {
                    activity.setTheme(theme);
                }

                activity.mCalled = false;
                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    //调用activity的onCreate方法
                    mInstrumentation.callActivityOnCreate(activity, r.state);
               //代码...

          
        return activity;
    }

从上面可知在Activity中的Context实现对象是ContexImpl,继续进入到ContexImpl类中,找到需要的getSystemService方法的实现:

class ContextImpl extends Context {
    //代码...
    @Override
    public Object getSystemService(String name) {
        return SystemServiceRegistry.getSystemService(this, name);
    }
    //代码...
}
final class SystemServiceRegistry {
    //Service容器
    private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
            new HashMap<String, ServiceFetcher<?>>();
     /**
     * Gets a system service from a given context.
     *根据key 获取对应的服务
     */
    public static Object getSystemService(ContextImpl ctx, String name) {
        ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        return fetcher != null ? fetcher.getService(ctx) : null;
    }

   /**
     * Base interface for classes that fetch services.
     * These objects must only be created during static initialization.
     */
    static abstract interface ServiceFetcher<T> {
        T getService(ContextImpl ctx);
    }
}

可以看到调用的是SystemServiceRegistry的getSystemService方法,并通过对应的服务名从HashMap中获取对应的ServiceFetcher,然后在通过ServiceFetcher的getService方法来获取具体的服务对象。到这里终于解答第一个问题,原来我们的服务是通过key-value的方式从一个HashMap中获取。但是还有一个很关键的问题,这些服务是怎么缓存到SYSTEM_SERVICE_FETCHERS这个Map中的——也就是这些服务什么时候注册的。

想要知道如何注册,同样也能在SystemServiceRegistry这个类寻找到答案。通过查看在该类其中有一大段静态代码块,在这段静态代码块中注册各种各样的系统服务。这里只贴出LayoutInflater服务有关的代码:

final class SystemServiceRegistry {
    static {
       //代码...
       registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
                new CachedServiceFetcher<LayoutInflater>() {
            @Override
            public LayoutInflater createService(ContextImpl ctx) {
                  //PhoneLayoutInflater才是我们真正需要的布局加载服务
                return new PhoneLayoutInflater(ctx.getOuterContext());
            }});

       //代码...

    }

    static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {
        private final int mCacheIndex;

        CachedServiceFetcher() {
            // Note this class must be instantiated only by the static initializer of the
            // outer class (SystemServiceRegistry), which already does the synchronization,
            // so bare access to sServiceCacheSize is okay here.
            mCacheIndex = sServiceCacheSize++;
        }

        @Override
        @SuppressWarnings("unchecked")
        public final T getService(ContextImpl ctx) {
            final Object[] cache = ctx.mServiceCache;
            final int[] gates = ctx.mServiceInitializationStateArray;

            for (;;) {
                boolean doInitialize = false;
                synchronized (cache) {
                    // Return it if we already have a cached instance.
                   //如果在缓存有直接返回需要的服务
                    T service = (T) cache[mCacheIndex];
                    if (service != null || gates[mCacheIndex] == ContextImpl.STATE_NOT_FOUND) {
                        return service;
                    }

                    // If we get here, there's no cached instance.

                    // Grr... if gate is STATE_READY, then this means we initialized the service
                    // once but someone cleared it.
                    // We start over from STATE_UNINITIALIZED.
                    if (gates[mCacheIndex] == ContextImpl.STATE_READY) {
                        gates[mCacheIndex] = ContextImpl.STATE_UNINITIALIZED;
                    }

                    // It's possible for multiple threads to get here at the same time, so
                    // use the "gate" to make sure only the first thread will call createService().

                    // At this point, the gate must be either UNINITIALIZED or INITIALIZING.
                    if (gates[mCacheIndex] == ContextImpl.STATE_UNINITIALIZED) {
                        doInitialize = true;
                        gates[mCacheIndex] = ContextImpl.STATE_INITIALIZING;
                    }
                }

                if (doInitialize) {
                    // Only the first thread gets here.

                    T service = null;
                    @ServiceInitializationState int newState = ContextImpl.STATE_NOT_FOUND;
                    try {
                        // This thread is the first one to get here. Instantiate the service
                        // *without* the cache lock held.
                       //初始化服务
                        service = createService(ctx);
                        newState = ContextImpl.STATE_READY;

                    } catch (ServiceNotFoundException e) {
                        onServiceNotFound(e);

                    } finally {
                        synchronized (cache) {
                            //把创建的服务放入缓存中
                            cache[mCacheIndex] = service;
                            gates[mCacheIndex] = newState;
                            cache.notifyAll();
                        }
                    }
                    return service;
                }
                // The other threads will wait for the first thread to call notifyAll(),
                // and go back to the top and retry.
                synchronized (cache) {
                    while (gates[mCacheIndex] < ContextImpl.STATE_READY) {
                        try {
                            cache.wait();
                        } catch (InterruptedException e) {
                            Log.w(TAG, "getService() interrupted");
                            Thread.currentThread().interrupt();
                            return null;
                        }
                    }
                }
            }
        }
        //需要重写的方法
        public abstract T createService(ContextImpl ctx) throws ServiceNotFoundException;
    }
}

我们知道静态代码块只会在类第一次加载的时候执行(只执行一次),也就是说在我们第一次调用SystemServiceRegistry.getSystemService方法时,会预先加载这一段代码块,也就完成服务的注册。在代码块中能清楚的发现PhoneLayoutInflater才是真正需要的服务,也就说虚拟机第一次加载SystemServiceRegistry类时会注册各种ServiceFetcher,其中就包含PhoneLayoutInflater,这些服务通过健值对的形式存储在一个HashMap中,用户使用时根据key来获取对应的ServiceFetcher,然后通过ServiceFetcher的getService方法获取具体的对象。当第一次获取时,会调用ServiceFetcher的createService方法创建对象,然后把这个对象缓存在一个列表中,等下次在获取时直接从缓存中获取避免重复创建对象。

分析到这我们已经弄清楚前两个问题——如何获取获取LayoutInflater服务和什么时候注册LayoutInflater服务。

接下来就剩LayoutInflater如何加载布局这个最关键的问题,接下来查看LayoutInflater的inflate方法:

 public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
      //root不为空则会把resource布局解析到view,并添加到root中  
     return inflate(resource, root, root != null);
 }

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
     final Resources res = getContext().getResources();
     if (DEBUG) {
         Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                    + Integer.toHexString(resource) + ")");
      }
    //获得XML解析器
     final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            final Context inflaterContext = mContext;
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            mConstructorArgs[0] = inflaterContext;
            View result = root;

            try {
                // Look for the root node.
                int type;
                while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                    // Empty
                }
                final String name = parser.getName();

                //解析merage标签
                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");
                    }

                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    //这里通过xml的tag来解析layout的根视图,name就是要解析的视图名称(如:ViewGroup、LinearLayout等)
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    ViewGroup.LayoutParams params = null;
                     //1:如果父布局为空生成布局参数
                    if (root != null) {
                        // Create layout params that match root, if supplied
                        params = root.generateLayoutParams(attrs);
                        //attachToRoot为false则给temp设置布局参数
                        if (!attachToRoot) {
                            temp.setLayoutParams(params);
                        }
                    }

                    //解析temp视图下所有子视图
                    rInflateChildren(parser, temp, attrs, true);
                    //如果root不为空且attachToRoot为true,将temp添加到父视图
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }

                    //如果root为空或者attachToRoot为false直接返回temp结果
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }

            }
            //代码...

            return result;
        }
    }

上述inflate方法中,主要有下面几步:

  1. 根据resource获的xml解析器
  2. 解析xml中的根标签
  3. 如果根标签是merge,那么调用rInflate进行解析,rInflate会将merge标签下所有的子view直接添加到标签中。
  4. 如果是普通标签,则调用createViewFromTag对该标签进行解析;
  5. 调用rInflateChildren解析temp根元素下的所有子 view,并将这些子View添加到temp下;
  6. 返回解析的根视图。

分析到这可以解答文章最开始的一个问题——在使用LayoutInflater的inflate(int resource, ViewGroup root,boolean attachToRoot)方法时,如果第二参数root为空,则第一个参数中最外层的布局大小无效。

 if (root != null) {
      // Create layout params that match root, if supplied
     params = root.generateLayoutParams(attrs);
    //attachToRoot为false则给temp设置布局参数
    if (!attachToRoot) {
      temp.setLayoutParams(params);
     }
}

在这段代码中,如果root不为空才会调用 root.generateLayoutParams(attrs)获取布局参数,如果root为空显然就不会有布局参数自然外层布局大小也无效

回到主题继续分析View的解析,接下来先从简单的单个标签布局开始分析,即createViewFromTag方法:

 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) {
        if (name.equals("view")) {
            name = attrs.getAttributeValue(null, "class");
        }    
        try {
            //用户通过设置LayoutInflater的Factory2 、mPrivateFactory、mFactory自行解析
            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);
            }
            //通过内置onCreateView解析
            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    //系统内置的View
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(parent, name, attrs);
                    } else {
                        //自定义View
                        view = createView(name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }
            return view;
        } 
        //代码...
    }

在createViewFromTag方法中有一段代码需要解释一下,如果标签名name中不包含'.'时,也就是-1 == name.indexOf('.'),这表明是系统内置的View,解析方式onCreateView(parent, name, attrs)。例如在xml文件中声明一个内置的TextView方式如下:

<TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

如果标签名中包含'.'时,表明为自定义view,解析方式为 createView(name, null, attrs),例如在xml文件中声明一个自定义view方式如下:

 <com.sun.wds.content.MyTextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

这李我们已经知道这段逻辑判读的含义,那两者之间有有什么不同呢?
我们先从onCreateView(parent, name, attrs)方法开始,也就是解析系统内置View,注意最开始调用的是三个参数的方法:

protected View onCreateView(View parent, String name, AttributeSet attrs)
            throws ClassNotFoundException {
        return onCreateView(name, attrs);
    }
protected View onCreateView(String name, AttributeSet attrs)
            throws ClassNotFoundException {
        return createView(name, "android.view.", attrs);
    }
public class PhoneLayoutInflater extends LayoutInflater {
    //内置View类型的前缀
    private static final String[] sClassPrefixList = {
        "android.widget.",
        "android.webkit.",
        "android.app."
    };
    @Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
        //在View名字的前面添加前缀构造View的完整路径
        for (String prefix : sClassPrefixList) {
            try {
                View view = createView(name, prefix, attrs);
                if (view != null) {
                    return view;
                }
            } catch (ClassNotFoundException e) {
                // In this case we want to let the base class take a crack
                // at it.
            }
        }

        return super.onCreateView(name, attrs);
    }
}

从上文可知PhoneLayoutInflater是LayoutInflater真正的实现类,其覆写了onCreateView(String name, AttributeSet attrs)方法。当解析内置View的情况下,调用是PhoneLayoutInflater的onCreateView方法,该方法会给View加上前缀构造View的完整路径,然后传递给createView(name, prefix, attrs)方法。也就是说不管是内置View还是自定义View最终都会调用createView(name, prefix, attrs)方法解析,只不过是自定义View调用createView方法是prefix为空,内置View不为空。

  1. 当只写View的名称没有路径,LayoutLayout解析时就认为是系统内置View,在onCreateView方法中自动添加路径,最后传递给createView方法进行解析。

  2. 如果是自定义View,那么在xml文中定义必须是完整路径,此时调用createView(name, prefix, attrs)解析,只不过prefix前缀为空。

解释了这么,最重要还是要看createView方法是如何解析的,代码如下:

   public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
         //缓存中获取构造函数
        Constructor<? extends View> constructor = sConstructorMap.get(name);
        if (constructor != null && !verifyClassLoader(constructor)) {
            constructor = null;
            sConstructorMap.remove(name);
        }
        Class<? extends View> clazz = null;
        try {
            if (constructor == null) {
                /**如果缓存不存在,从Class对象获取构造函数,并添加至缓存**/
                //prefix != null ? (prefix + name) : name
                //如果prefix不为空则添加完整路径,加载此类
                clazz = mContext.getClassLoader().loadClass(
                        prefix != null ? (prefix + name) : name).asSubclass(View.class);

                if (mFilter != null && clazz != null) {
                    boolean allowed = mFilter.onLoadClass(clazz);
                    if (!allowed) {
                        failNotAllowed(name, prefix, attrs);
                    }
                }
                //从Class对象获取构造函数
                constructor = clazz.getConstructor(mConstructorSignature);
                constructor.setAccessible(true);
                //添加缓存
                sConstructorMap.put(name, constructor);
            } else {
            //代码...
            }

            Object lastContext = mConstructorArgs[0];
            if (mConstructorArgs[0] == null) {
                // Fill in the context if not already within inflation.
                mConstructorArgs[0] = mContext;
            }
            Object[] args = mConstructorArgs;
            args[1] = attrs;
            //通过反射构造View  args [context,attrs]
            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]));
            }
            mConstructorArgs[0] = lastContext;
            return view;

        } catch (NoSuchMethodException e) {
        //代码...
        }
    }

逻辑很简单,先从缓存中获取View的构造函数,如果没有就拼接一个View的完整路径,并将该类加载到虚拟机中,然后获取该类的构造函数并缓存起来,再通过构造函数来创建改View的对象,最后返回该View。

到这里我们只是分析了单个标签通过createViewFromTag的解析方式,通常情况下一个布局下会有很多子View,子View下又有子View,组成一个树状结构。这个时候就要回到inflate 方法中步骤5,调用rInflateChildren解析temp视图下所有子视图。

final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
            boolean finishInflate) throws XmlPullParserException, IOException {
        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;
        boolean pendingRequestFocus = false;
        //挨个遍历解析
        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();

            if (TAG_REQUEST_FOCUS.equals(name)) {
                pendingRequestFocus = true;
                consumeChildElements(parser);
            } else if (TAG_TAG.equals(name)) {
                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 {
                //根据单个标签解析 之前已经分析这一过程
                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);
                //把解析到View添加父容器中
                viewGroup.addView(view, params);
            }
        }
    }

rInflate方法通过深度优先遍历视图,每解析完一个View就会递归调用rInflateChildren解析,直到这个深度下的最后一个view,然后再把解析得到的每一个View添加到这些它们的父容器中,这样整个树状视图就解析完成了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容