LayoutInflater原始碼解析及常見相關報錯分析

語言: CN / TW / HK

持續創作,加速成長!這是我參與「掘金日新計劃 · 6 月更文挑戰」的第9天,點選檢視活動詳情

在日常Android開發中,最經常使用的RecyclerView控制元件是大家都繞不開的,而編寫其Adapter時更離不開LayoutInflater的呼叫。當然,如果你做這一行有些時日了,相信你對其使用一定是爐火純青了。即使如此,我覺得LayoutInflater仍舊有值得分析的地方,相信你看完之後有更多的認識。Android系統中有許多包括ActivityManagerService在內的系統級服務,我們平時在使用時可通過Context呼叫,這些服務會在安卓系統初始化時以單例的形式註冊,其中LayoutInflater服務就是其中之一。

我們常在RecyclerView.Adapter的onCreateViewHolder()中通過LayoutInflater將xml編寫的佈局檔案轉換成Android中的一個View物件: @NonNull @NotNull @Override public RecyclerView.ViewHolder onCreateViewHolder(@NonNull @NotNull ViewGroup parent, int viewType) { View inflate = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_recycler_item, parent, false); ViewHolder viewHolder = new ViewHolder(inflate); return viewHolder; } 其實不僅僅是onCreateViewHolder(),在Activity中的setContentView()中內部也是通過LayoutInflater將xml佈局轉換成View。 @Override public void setContentView(int layoutResID) { ... if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, getContext()); transitionTo(newScene); } else { mLayoutInflater.inflate(layoutResID, mContentParent); } ... } //SDK裡PhoneWindow.java中 而我們最常使用的關於LayoutInflater的方法莫過於下面幾個: 1、View.inflate(mContext,R.layout.layout_sence_recycler_item,null); 2、LayoutInflater.from(mContext).inflate(R.layout.layout_sence_recycler_item,parent); 3、LayoutInflater.from(mContext).inflate(R.layout.layout_sence_recycler_item,parent,false); 4、LayoutInflater inflater =(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View view = inflater.inflate(R.layout.main, null); .from(Context)方法原理暫時先不做詳細講解,其內部呼叫的是ContextImpl類中的getSystemService(),因此其實要說的是getSystemService(),這方法程式碼流程過多且與本文主旨無關不做贅述。

我們現在進去看看inflate()相關原始碼(在相關方法上按住Ctrl+滑鼠左鍵點選),會發現其中總共四種過載方法:

image.png 第一種方法: /** * Inflate a new view hierarchy from the specified xml resource. Throws * {@link InflateException} if there is an error. * * @param resource ID for an XML layout resource to load (e.g., * <code>R.layout.main_page</code>) * @param root Optional view to be the parent of the generated hierarchy. * @return The root View of the inflated hierarchy. If root was supplied, * this is the root View; otherwise it is the root of the inflated * XML file. */ public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) { return inflate(resource, root, root != null); } 第二種方法: /** * Inflate a new view hierarchy from the specified xml node. Throws * {@link InflateException} if there is an error. * * <p> * <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance * reasons, view inflation relies heavily on pre-processing of XML files * that is done at build time. Therefore, it is not currently possible to * use LayoutInflater with an XmlPullParser over a plain XML file at runtime. * * @param parser XML dom node containing the description of the view * hierarchy. * @param root Optional view to be the parent of the generated hierarchy. * @return The root View of the inflated hierarchy. If root was supplied, * this is the root View; otherwise it is the root of the inflated * XML file. */ public View inflate(XmlPullParser parser, @Nullable ViewGroup root) { return inflate(parser, root, root != null); } 第三種方法: ``` /* * Inflate a new view hierarchy from the specified xml resource. Throws * {@link InflateException} if there is an error. * * @param resource ID for an XML layout resource to load (e.g., * R.layout.main_page) * @param root Optional view to be the parent of the generated hierarchy (if * attachToRoot is true), or else simply an object that * provides a set of LayoutParams values for root of the returned * hierarchy (if attachToRoot is false.) * @param attachToRoot Whether the inflated hierarchy should be attached to * the root parameter? If false, root is only used to create the * correct subclass of LayoutParams for the root view in the XML. * @return The root View of the inflated hierarchy. If root was supplied and * attachToRoot is true, this is root; otherwise it is the root of * the inflated XML file. / 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) + ")"); }

View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
if (view != null) {
    return view;
}
XmlResourceParser parser = res.getLayout(resource);
try {
    return inflate(parser, root, attachToRoot);
} finally {
    parser.close();
}

} 第四種方法較長: /* * Inflate a new view hierarchy from the specified XML node. Throws * {@link InflateException} if there is an error. *

* Important   For performance * reasons, view inflation relies heavily on pre-processing of XML files * that is done at build time. Therefore, it is not currently possible to * use LayoutInflater with an XmlPullParser over a plain XML file at runtime. * * @param parser XML dom node containing the description of the view * hierarchy. * @param root Optional view to be the parent of the generated hierarchy (if * attachToRoot is true), or else simply an object that * provides a set of LayoutParams values for root of the returned * hierarchy (if attachToRoot is false.) * @param attachToRoot Whether the inflated hierarchy should be attached to * the root parameter? If false, root is only used to create the * correct subclass of LayoutParams for the root view in the XML. * @return The root View of the inflated hierarchy. If root was supplied and * attachToRoot is true, this is root; otherwise it is the root of * the inflated XML file. / 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];  //獲取Context物件
    mConstructorArgs[0] = inflaterContext;    
    View result = root;    //儲存父檢視

    try {
        advanceToRootNode(parser);    
        final String name = parser.getName();

        if (DEBUG) {
            System.out.println("**************************");
            System.out.println("Creating root view: "
                    + name);
            System.out.println("**************************");
        }

        if (TAG_MERGE.equals(name)) {   //判斷是否是merge標籤
            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 {
            // Temp is the root view that was found in the xml
            final View temp = createViewFromTag(root, name, inflaterContext, attrs);  //解析單個元素,例項化xml佈局的根view物件

            ViewGroup.LayoutParams params = null;

            if (root != null) {
                if (DEBUG) {
                    System.out.println("Creating params from root: " +
                            root);
                }
                // Create layout params that match root, if supplied
                params = root.generateLayoutParams(attrs);        // 建立匹配root物件的佈局引數
                if (!attachToRoot) {
                    // Set the layout params for temp if we are not
                    // attaching. (If we are, we use addView, below)
                    temp.setLayoutParams(params);        // attachToRoot:傳進來的引數,如果該view不需要新增到父佈局上,則直接將根據父佈局生成的params引數來設定
                }
            }

            if (DEBUG) {
                System.out.println("-----> start inflating children");
            }

            // Inflate all children under temp against its context.
            rInflateChildren(parser, temp, attrs, true);

            if (DEBUG) {
                System.out.println("-----> done inflating children");
            }

            // We are supposed to attach all the views we found (int temp)
            // to root. Do that now.
            if (root != null && attachToRoot) {
                root.addView(temp, params);  //如果傳入的父佈局不為null,且attachToRoot為true,則給temp設定佈局引數,將例項化的view物件加入到父佈局root中
            }

            // Decide whether to return the root that was passed in or the
            // top view found in xml.
            if (root == null || !attachToRoot) {  //如果傳入的父佈局為null,且attachToRoot為false,則返回temp
                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(
                getParserStateDescription(inflaterContext, attrs)
                + ": " + 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;
}

} ``` 繼續跟隨前三種過載方法後,你會發現最後都繞到了第四種過載方法裡。

此方法可見總共三個引數:

引數一、xml解析器;

引數二、解析佈局的父檢視;

引數三、是否將要解析的檢視新增進父檢視中(是否要將當前載入的xml佈局新增到第二個引數傳入的父佈局上面)。

關於XmlPullParser就不詳解了,裡面解析xml的過程較長,也與本文主旨無關。這裡看一個重要方法advanceToRootNode():

``` /* * Advances the given parser to the first START_TAG. Throws InflateException if no start tag is * found. / private void advanceToRootNode(XmlPullParser parser) throws InflateException, IOException, XmlPullParserException { // Look for the root node. int type; //while迴圈解析查詢xml標籤,START_TAG是開始標籤,END_DOCUMENT是結束標籤 while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { // Empty }

if (type != XmlPullParser.START_TAG) {
    throw new InflateException(parser.getPositionDescription()
        + ": No start tag found!");
}

} ``` 根據以上程式碼,我們可以概括其inflate()流程如下:

1、解析xml佈局檔案中根標籤(第一個元素);

2、如果根標籤是merge標籤,則呼叫rInflate()來將merge標籤下所有子View新增到根標籤中;

3、如果不是merge標籤,則呼叫createViewFromTag();

4、呼叫rInflateChildren()解析temp下所有子View,並將其新增到temp下;

5、根據設定的引數判斷返回對應檢視。

可以說整段程式碼就是一個將xml解析成View結構的過程,其過程中細節實現靠rInflate、createViewFromTag和rInflateChildren方法,而rInflateChildren內部又是呼叫rInflate,因此我們看createViewFromTag()和rInflateChildren()即可。

createViewFromTag

其原始碼如下: ``` @UnsupportedAppUsage View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) { if (name.equals("view")) { name = attrs.getAttributeValue(null, "class"); }

// Apply a theme wrapper, if allowed and one is specified.
if (!ignoreThemeAttr) {            //主題相關
    final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
    final int themeResId = ta.getResourceId(0, 0);
    if (themeResId != 0) {
        context = new ContextThemeWrapper(context, themeResId);
    }
    ta.recycle();
}

try {
    View view = tryCreateView(parent, name, context, attrs);  //通過tryCreateView返回view物件

    if (view == null) {
        final Object lastContext = mConstructorArgs[0];
        mConstructorArgs[0] = context;
        try {
            if (-1 == name.indexOf('.')) {       //內建view控制元件的解析
                view = onCreateView(context, parent, name, attrs);
            } else {
                view = createView(context, name, null, attrs);        //自定義View的解析
            }
        } finally {
            mConstructorArgs[0] = lastContext;
        }
    }

    return view;
} catch (InflateException e) {
    throw e;

} catch (ClassNotFoundException e) {
    final InflateException ie = new InflateException(
            getParserStateDescription(context, attrs)
            + ": Error inflating class " + name, e);
    ie.setStackTrace(EMPTY_STACK_TRACE);
    throw ie;

} catch (Exception e) {
    final InflateException ie = new InflateException(
            getParserStateDescription(context, attrs)
            + ": Error inflating class " + name, e);
    ie.setStackTrace(EMPTY_STACK_TRACE);
    throw ie;
}

} 繼續看tryCreateView()的內部實現: @UnsupportedAppUsage(trackingBug = 122360734) @Nullable public final View tryCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) { if (name.equals(TAG_1995)) { // Let's party like it's 1995! return new BlinkLayout(context, attrs); }

View view;
if (mFactory2 != null) {
    view = mFactory2.onCreateView(parent, name, context, attrs);    //使用者可以設定Factory來解析View,此物件預設為Null,可以忽略
} else if (mFactory != null) {
    view = mFactory.onCreateView(name, context, attrs);        //使用者可以設定Factory來解析View,此物件預設為Null,可以忽略
} else {
    view = null;
}

if (view == null && mPrivateFactory != null) {
    view = mPrivateFactory.onCreateView(parent, name, context, attrs);     //使用者可以設定Factory來解析View,此物件預設為Null,可以忽略
}

return view;

} 所以具體流程下來,你會發現,最終呼叫的還是: if (view == null) { final Object lastContext = mConstructorArgs[0]; mConstructorArgs[0] = context; try { if (-1 == name.indexOf('.')) { view = onCreateView(context, parent, name, attrs); } else { view = createView(context, name, null, attrs); } } finally { mConstructorArgs[0] = lastContext; } } 核心判斷還是在這裡,createViewFromTag的引數會將該元素的parent及名字傳過來,我們修改下核心邏輯部分,即可為下面: if (view == null) { final Object lastContext = mConstructorArgs[0]; mConstructorArgs[0] = context; try { if (-1 == name.indexOf('.')) { view = createView(name, "android.view.", attrs); } else { view = createView(name, null, attrs); } } finally { mConstructorArgs[0] = lastContext; } } ``` 相信這裡你就理解了,如果這個名字在查詢“.”返回-1即沒有包含“.”時, 則認為是一個內建View,呼叫onCreate(),反之,呼叫createView()。

createView()

protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException { return createView(name, "android.view.", attrs); } 其實onCreateView()最終還是呼叫的createView(),所以我們這裡看createView()就行了。 ``` @Nullable public final View createView(@NonNull Context viewContext, @NonNull String name, @Nullable String prefix, @Nullable AttributeSet attrs) throws ClassNotFoundException, InflateException { Objects.requireNonNull(viewContext); Objects.requireNonNull(name); Constructor<? extends View> constructor = sConstructorMap.get(name); // 全域性快取 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) {            //判斷快取是否為空,為空則自行反射載入
        // Class not found in the cache, see if it's real, and try to add it
        clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                mContext.getClassLoader()).asSubclass(View.class);

        if (mFilter != null && clazz != null) {
            boolean allowed = mFilter.onLoadClass(clazz);
            if (!allowed) {
                failNotAllowed(name, prefix, viewContext, attrs);
            }
        }
        constructor = clazz.getConstructor(mConstructorSignature);
        constructor.setAccessible(true);
        sConstructorMap.put(name, constructor);            //新增到快取
    } else {
        // If we have a filter, apply it to cached constructor
        if (mFilter != null) {
            // Have we seen this name before?
            Boolean allowedState = mFilterMap.get(name);
            if (allowedState == null) {
                // New class -- remember whether it is allowed
                clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                        mContext.getClassLoader()).asSubclass(View.class);

                boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                mFilterMap.put(name, allowed);
                if (!allowed) {
                    failNotAllowed(name, prefix, viewContext, attrs);
                }
            } else if (allowedState.equals(Boolean.FALSE)) {
                failNotAllowed(name, prefix, viewContext, attrs);
            }
        }
    }

    Object lastContext = mConstructorArgs[0];
    mConstructorArgs[0] = viewContext;
    Object[] args = mConstructorArgs;
    args[1] = attrs;

    try {
        final View view = constructor.newInstance(args);                //建立新View例項,args是自定義主題相關的變數
        if (view instanceof ViewStub) {                    // 如果是ViewStub,則用同一個Context載入這個ViewStub的LayoutInflater
            // Use the same context when inflating ViewStub later.
            final ViewStub viewStub = (ViewStub) view;
            viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
        }
        return view;
    } finally {
        mConstructorArgs[0] = lastContext;
    }
} catch (NoSuchMethodException e) {
    final InflateException ie = new InflateException(
            getParserStateDescription(viewContext, attrs)
            + ": 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(
            getParserStateDescription(viewContext, attrs)
            + ": 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(
            getParserStateDescription(viewContext, attrs) + ": Error inflating class "
                    + (clazz == null ? "<unknown>" : clazz.getName()), e);
    ie.setStackTrace(EMPTY_STACK_TRACE);
    throw ie;
} finally {
    Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}

} 方法很長,這裡只看其核心部分: if (constructor == null) { // Class not found in the cache, see if it's real, and try to add it clazz = Class.forName(prefix != null ? (prefix + name) : name, false, mContext.getClassLoader()).asSubclass(View.class); //如果prefix不為空則構造完整類的路徑,並通過反射形式載入

if (mFilter != null && clazz != null) {
    boolean allowed = mFilter.onLoadClass(clazz);
    if (!allowed) {
        failNotAllowed(name, prefix, viewContext, attrs);
    }
}
constructor = clazz.getConstructor(mConstructorSignature);    //獲取建構函式
constructor.setAccessible(true);    //將建構函式存入快取中
sConstructorMap.put(name, constructor);

} else { ... } Object lastContext = mConstructorArgs[0]; mConstructorArgs[0] = viewContext; Object[] args = mConstructorArgs; args[1] = attrs;

try {
    final View view = constructor.newInstance(args);        //通過反射構造View
    if (view instanceof ViewStub) {
        // Use the same context when inflating ViewStub later.
        final ViewStub viewStub = (ViewStub) view;
        viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
    }
    return view;
} finally {
    mConstructorArgs[0] = lastContext;
}

``` 程式碼裡的思路還是很清晰的,通過反射找到對應類的位元組碼檔案,然後找到其對應構造方法,例項化,拿到View。

可能這段程式碼會讓你疑惑:

constructor = clazz.getConstructor(mConstructorSignature); 我們點進去檢視內部呼叫: private Constructor<T> getConstructor0(Class<?>[] parameterTypes, int which) throws NoSuchMethodException { if (parameterTypes == null) { parameterTypes = EmptyArray.CLASS; } for (Class<?> c : parameterTypes) { if (c == null) { throw new NoSuchMethodException("parameter type is null"); } } Constructor<T> result = getDeclaredConstructorInternal(parameterTypes); if (result == null || which == Member.PUBLIC && !Modifier.isPublic(result.getAccessFlags())) { throw new NoSuchMethodException(getName() + ".<init> " + Arrays.toString(parameterTypes)); } return result; } 但其實這就是反射獲取的有兩個引數的構造方法,這也就是為什麼在做自定義控制元件的時候要過載兩個函式的建構函式的原因。可以說這就是解析單個View的全部過程。但LayoutInflater要解析的是整個視窗中的檢視樹,這個靠rInflate()實現: ``` 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 ||              //while迴圈解析各view
        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)) {            
        if (parser.getDepth() == 0) {       //如果根佈局是include標籤,拋異常
            throw new InflateException("<include /> cannot be the root element");
        }
        parseInclude(parser, context, parent, attrs);
    } else if (TAG_MERGE.equals(name)) {            //如果merge標籤,拋異常,因為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(parser, view, attrs, true);            //遞迴呼叫
        viewGroup.addView(view, params);                 //將解析的View新增到父控制元件(ViewGroup)中
    }
}

if (pendingRequestFocus) {
    parent.restoreDefaultFocus();
}

if (finishInflate) {
    parent.onFinishInflate();
}

} ``` 可見,新增全部View的過程就是在while迴圈中呼叫一個createViewFromTag生成根view,然後 rInflateChildren → rInflate → rInflateChildren → rInflate → ... ... 就這樣遞迴呼叫一直到遍歷完全,然後通過addView新增到父控制元件。

相信看到這裡就都明白了,整個inflate過程可以分為兩大步驟:

  1. 通過解析器來將xml檔案中的內容解析出來。
  2. 使用反射將解析出來的元素建立成View物件。

root為null的情況

而根據LayoutInflater.inflate()中的內部方法和返回物件又可以總結出:

| 呼叫的inflate方法 | 對應最終呼叫inflater()方法引數 | 返回情況 | ------------------------ | ----------------------------- | --------------------- | | inflate(id, null) | inflater(parser, null, false) | 生成對應View物件並返回 | | inflate(id, root) | inflater(parser, root, true) | 生成View物件新增到root上並返回root | | inflate(id, null, false) | inflate(parser, root, false) | 生成View物件並返回 | | inflate(id, null, true) | inflate(parser, null, true) | 生成View物件並返回 | | inflate(id, root, false) | inflate(parser, root, false) | 生成View物件並返回 | | inflate(id, root, true) | inflater(parser, root, true) | 生成View物件新增到root上並返回root|

只要傳遞的root不為空,則會根據root來建立生成View的LayoutParams。

如果LayoutInflater呼叫inflate(id, null),不傳入root即父佈局,則填充的View的layout_width和layout_height的值無論修改成多少,都不會有效果。確切點來講,所有以layout_開頭的屬性都會失去作用,原因很簡單,一個View的測量結果並不只是由它自己的layout_width和layout_height(即LayoutParams)所決定的,而是由父容器給它的約束(MeasureSpec)和它自身的LayoutParams共同決定的。有興趣的朋友可以嘗試驗證下。

常見報錯

LayoutInflate.inflate()方法報錯大體有兩個原因:

1、在傳入父佈局的情況下重複addView,對應非法狀態異常; java.lang.IllegalStateException The specified child already has a parent. You must call removeView() on the child's parent first. 最常見的非法狀態異常,出現場景原因五花八門,舉一個最常見的例子,我們在呼叫Fragment時,為什麼後面的引數一定要是false? @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_layout, container, false); } 其實在Fragement原始碼中,onCreateView()裡返回的View其實已經新增到container中了。說白了就是Fragment有自己的AddView操作,如果第三個引數傳入true,那麼就會直接將inflate出來的佈局新增到父佈局當中。然後再次addView的時候就會發現它已經有一個父佈局了,從而丟擲IllegalStateException。(對應邏輯原始碼在上述解析中可看到)

2、傳入相關引數有問題導致報空指標。 java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.String.equals(java.lang.Object)' on a null object reference at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:715) ... 這種沒什麼好說的,要麼是layoutInflate物件為空、或layoutInflate所依賴的Context物件為空,要麼是inflate()引數中的佈局檔案有錯,導致XmlPullParser物件無法正確的解析佈局檔案原因。

總結

inflate就是先把最外層的root解析完,然後用rInflate去遞迴把子view解析完,子view用createViewFromTag方法去解析單個View,用createView反射出view,全都遞迴完再返回。當然,解析肯定是耗時操作,很明顯耗時操作有兩處:

解析xml佈局檔案反射獲取例項

而谷歌官方對此做的優化是:

預編譯快取

關於LayoutInflater原始碼解析先到這裡了,如果有其它問題或者疑惑可以留言。