setContentView與LayoutInflater詳解

一個(gè)應(yīng)用最基本的就是一個(gè)個(gè)的Activity金踪,在Activity入口方法onCreate中的第一步就是setContentView也就是加載布局文件柠傍。今天我們就來(lái)學(xué)習(xí)一下Android中加載布局文件的流程咏瑟。

通過(guò)本文你也許能明白下面幾個(gè)問(wèn)題:

  • 1.setContentView(id)真正的機(jī)制與原理
  • 2.marge為什么不增加視圖層數(shù)以及為什么不能用于根布局但必須用于xml文件的根標(biāo)簽
  • 3.自定義view在寫到xml文件中時(shí)為什么會(huì)調(diào)用兩參數(shù)的構(gòu)造方法
  • 4.inflate(resource, root, attachToRoot)三個(gè)參數(shù)之間的真正關(guān)系及一些常見(jiàn)問(wèn)題
  • 5.ViewStub的幾個(gè)問(wèn)題:①.為什么可以提高性能 ②.是如何延遲加載布局的 ③.為什么inflate()只能調(diào)用一次而setVisibility(View.VISIBLE)可以多次調(diào)用

首先要清楚一點(diǎn),Activity的類型有很多四瘫,有些Activity的setContentView方法和最原始的android.app.Activity中的還是有一定區(qū)別的,這里我們以最原始的為依據(jù)開始分析歼秽,本文源碼基于Android 8.0,源碼位置

android\frameworks\base\core\java\android\app\Activity.java

setContentView有幾個(gè)重載方法情组,但是功能都一樣燥筷,只不過(guò)有的需要傳入資源名有的直接傳一個(gè)view,既然是分析加載xml文件的流程院崇,我們當(dāng)然看最常用的那個(gè)傳入資源名的方法:

    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

發(fā)現(xiàn)實(shí)際上是調(diào)用的getWindow()的setContentView肆氓,先看getWindow():

    public Window getWindow() {
        return mWindow;
    }

    mWindow = new PhoneWindow(this, window, activityConfigCallback);

mWindow是一個(gè)PhoneWindow對(duì)象,PhoneWindow源碼位置:

frameworks\base\core\java\com\android\internal\policy\PhoneWindow.java

我們先不管那個(gè)構(gòu)造函數(shù),直接看setContentView方法

    @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

這里對(duì)有過(guò)場(chǎng)動(dòng)畫的情況下做了特殊處理底瓣,我們先看沒(méi)有過(guò)程動(dòng)畫的情況谢揪。到這里我們發(fā)現(xiàn)最后調(diào)用了inflate方法,和我們?cè)谧鰈istview等一些場(chǎng)景中加載一個(gè)布局文件的方法是一樣的捐凭。接下來(lái)就著重看看這里拨扶。

先看mLayoutInflater的實(shí)例化過(guò)程:

android\frameworks\base\core\java\android\view\LayoutInflater.java

    mLayoutInflater = 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;
    }

原來(lái)我們一直在用的LayoutInflater是一個(gè)系統(tǒng)服務(wù)。來(lái)看一下這個(gè)服務(wù)茁肠,但LayoutInflater是一個(gè)抽象類患民,肯定不是我們要找的,只能從Context 的getSystemService入手垦梆。但是匹颤,Context 只是一個(gè)抽象類,getSystemService必定是它的一個(gè)實(shí)現(xiàn)類中的托猩,就是ContextImpl(至于這里面的關(guān)系印蓖,詳見(jiàn)此處),ContextImpl源碼位置:

android\frameworks\base\core\java\android\app\ContextImpl.java
    @Override
    public Object getSystemService(String name) {
        return SystemServiceRegistry.getSystemService(this, name);
    }

再看 SystemServiceRegistry.getSystemService京腥,SystemServiceRegistry也在app包下

    public static Object getSystemService(ContextImpl ctx, String name) {
        ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        return fetcher != null ? fetcher.getService(ctx) : null;
    }

    private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
            new HashMap<String, ServiceFetcher<?>>();

發(fā)現(xiàn)SYSTEM_SERVICE_FETCHERS 只是一個(gè)HashMap赦肃,getSystemService所做的操作僅僅是按名字去內(nèi)容,那么我們就看看這個(gè)HashMap的put方法公浪,看有沒(méi)有什么收獲摆尝。經(jīng)過(guò)搜索,這個(gè)類中有個(gè)registerService方法用來(lái)put:

    private static <T> void registerService(String serviceName, Class<T> serviceClass,
            ServiceFetcher<T> serviceFetcher) {
        SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
        SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
    }

這里要清楚因悲,有可能是外部某個(gè)類調(diào)用這個(gè)方法用來(lái)注冊(cè)服務(wù)堕汞,這時(shí)候我們只能全文搜索注冊(cè)LayoutInflater時(shí)用的key--Context.LAYOUT_INFLATER_SERVICE。幸運(yùn)的時(shí)晃琳,這個(gè)服務(wù)就是在SystemServiceRegistry的static代碼段中注冊(cè)的:

static {
        registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
                new CachedServiceFetcher<LayoutInflater>() {
            @Override
            public LayoutInflater createService(ContextImpl ctx) {
                return new PhoneLayoutInflater(ctx.getOuterContext());
            }});
}

跟蹤了一圈終于找到了讯检,LayoutInflater的具體實(shí)現(xiàn)類就是PhoneLayoutInflater。PhoneLayoutInflater代碼位置:

android\frameworks\base\core\java\com\android\internal\policy\PhoneLayoutInflater.java

這個(gè)類中卫旱,我們當(dāng)然要先看一下inflate的實(shí)現(xiàn),但是并沒(méi)有找到人灼。原來(lái)PhoneLayoutInflater并沒(méi)有重寫inflate方法,調(diào)用的還是抽象類LayoutInflater內(nèi)原本就實(shí)現(xiàn)的顾翼。投放。。既然如此适贸,還是看一下吧:

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);
    }

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();

        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

上面這個(gè)過(guò)程主要工作就是構(gòu)造了一個(gè)XmlResourceParser 解析器灸芳,然后調(diào)用了inflate的另一個(gè)重載方法:

    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 result = root;

            try {
                int type;
                while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                }

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

                final String name = parser.getName();

                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 {
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    ViewGroup.LayoutParams params = null;

                    if (root != null) {
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            temp.setLayoutParams(params);
                        }
                    }

                    rInflateChildren(parser, temp, attrs, true);

                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }

                    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 {
                mConstructorArgs[0] = lastContext;
                mConstructorArgs[1] = null;

                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }

            return result;
        }
    }

這個(gè)方法還是比較長(zhǎng)的涝桅,我們一點(diǎn)一點(diǎn)看。從try代碼段開始烙样。第一個(gè)while循環(huán)是尋找開始標(biāo)簽或結(jié)束標(biāo)簽冯遂。如果直接先找到結(jié)束標(biāo)簽,則拋異常谒获。否則區(qū)除標(biāo)簽名蛤肌。

之后如果是marge標(biāo)簽,則root不能為空且attachToRoot不能為false批狱。這里也就驗(yàn)證了marge使用時(shí)不能作為根布局裸准。對(duì)于marge的處理是調(diào)用了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 ||
                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) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {
                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);
            }
        }

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

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

上面方法主要就是遍歷所有子節(jié)點(diǎn),首先是對(duì)"requestFocus"赔硫,"tag"炒俱,"include","merge"幾個(gè)特殊標(biāo)簽的處理卦停。其中如果有merge標(biāo)簽則拋異常向胡,若為include標(biāo)簽,執(zhí)行parseInclude方法惊完,由于include主要是引入另一個(gè)xml文件僵芹,所以parseInclude功能和inflate類似,這里先不介紹小槐。在if判斷的最后一塊則是重點(diǎn)拇派,這里是創(chuàng)建view的真正地方,調(diào)用了createViewFromTag方法凿跳,然后配置LayoutParams 件豌,最后添加到viewGroup中,這也就是marge為什么不添加嵌套等級(jí)的原因控嗜。上面兩個(gè)方法我們放到后面講茧彤。

到這里marge標(biāo)簽處理完了,我們?cè)诨氐絠nflate方法疆栏,看不是marge標(biāo)簽的情況曾掂。既然不是marge標(biāo)簽則就是一個(gè)view了,所以直接調(diào)用了:

final View temp = createViewFromTag(root, name, inflaterContext, attrs);

這里我們就來(lái)看一下這個(gè)核心的方法:

    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");
        }

        // 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();
        }

        if (name.equals(TAG_1995)) {
            // Let's party like it's 1995!
            return new BlinkLayout(context, attrs);
        }

        try {
            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);
            }

            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(parent, name, attrs);
                    } else {
                        view = createView(name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }

            return view;
        } 
        .....
    }

首先調(diào)用了另一個(gè)重載方法壁顶,并將參數(shù)ignoreThemeAttr改為false珠洗,意思是會(huì)收到主題的影響。接下來(lái)首先處理了一下如果標(biāo)簽是blink的情況若专,這種東西好像很少用许蓖,有興趣的可以去查找一下。我們看正常情況:

首先出現(xiàn)了mFactory2、mFactory 和mPrivateFactory三個(gè)變量膊爪。我們先探究一下這三個(gè)參數(shù)的來(lái)歷自阱,首先看一下他們的類:

public interface Factory {
      public View onCreateView(String name, Context context, AttributeSet attrs);
}

public interface Factory2 extends Factory {
      public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
}

全都是接口,再看他們初始化的地方蚁飒,首先是在構(gòu)造中:

    protected LayoutInflater(LayoutInflater original, Context newContext) {
        mContext = newContext;
        mFactory = original.mFactory;
        mFactory2 = original.mFactory2;
        mPrivateFactory = original.mPrivateFactory;
        setFilter(original.mFilter);
    }

不過(guò)回顧LayoutInflater的創(chuàng)建過(guò)程:

LayoutInflater.from(context) -> 
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) ->
new PhoneLayoutInflater(ctx.getOuterContext()); -> 
LayoutInflater(context)

而這三個(gè)參數(shù)是在LayoutInflater的另外一個(gè)兩參數(shù)構(gòu)造中初始化的动壤,所以從Activity中過(guò)來(lái)時(shí)萝喘,這三個(gè)參數(shù)都為空淮逻。

但是他們還有set方法:

void setFactory(Factory factory) 
void setFactory2(Factory2 factory)
void setPrivateFactory(Factory2 factory)

通過(guò)搜索在Activity中調(diào)用了setPrivateFactory:

final void attach(....) {
        ...
        mWindow.getLayoutInflater().setPrivateFactory(this);
        ...
}

傳入的是this,是因?yàn)锳ctivity實(shí)現(xiàn)了Factory2 接口:

    @Nullable
    public View onCreateView(String name, Context context, AttributeSet attrs) {
        return null;
    }
    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        if (!"fragment".equals(name)) {
            return onCreateView(name, context, attrs);
        }

        return mFragments.onCreateView(parent, name, context, attrs);
    }

從代碼中看阁簸,主要是對(duì)標(biāo)簽為fragment時(shí)做了處理爬早,由于我們主要是看layout的加載,當(dāng)標(biāo)簽不為fragment時(shí)返回null启妹,所以我們直接從createViewFromTag中的view==null開始看筛严。

接下來(lái)又出現(xiàn)一個(gè)if判斷,主要是判斷標(biāo)簽中是否有“.”饶米,有則代表是自定義view桨啃,沒(méi)有則是系統(tǒng)內(nèi)置的。分別調(diào)用不同的方法檬输。對(duì)于自定義view:

    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 {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);

            if (constructor == null) {
                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);
                    }
                }
                constructor = clazz.getConstructor(mConstructorSignature);
                constructor.setAccessible(true);
                sConstructorMap.put(name, constructor);
            } else {
                if (mFilter != null) {
                    Boolean allowedState = mFilterMap.get(name);
                    if (allowedState == null) {
                        clazz = mContext.getClassLoader().loadClass(
                                prefix != null ? (prefix + name) : name).asSubclass(View.class);

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

            Object lastContext = mConstructorArgs[0];
            if (mConstructorArgs[0] == null) {
                mConstructorArgs[0] = mContext;
            }
            Object[] args = mConstructorArgs;
            args[1] = attrs;

            final View view = constructor.newInstance(args);
            if (view instanceof ViewStub) {
                final ViewStub viewStub = (ViewStub) view;
                viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
            }
            mConstructorArgs[0] = lastContext;
            return view;

        } 
        ....
    }

關(guān)鍵代碼是這一句·:

constructor = clazz.getConstructor(mConstructorSignature);

static final Class<?>[] mConstructorSignature = new Class[] {Context.class, AttributeSet.class};

看見(jiàn)是利用了反射照瘾,并且調(diào)用了view眾多構(gòu)造中的兩參數(shù)那個(gè),這也就驗(yàn)證了為什么在xml文件中丧慈,自定義view會(huì)調(diào)用兩參數(shù)的構(gòu)造析命。獲得了構(gòu)造后他還會(huì)存起來(lái)供下次使用,節(jié)省性能逃默。之后便調(diào)用了newInstance方法鹃愤,并傳入了context和attrs。接下來(lái)又處理了view時(shí)是ViewStub的情況完域。如果是ViewStub則給他設(shè)置一個(gè)LayoutInflater软吐,這里的cloneInContext實(shí)現(xiàn)在PhoneLayoutInflater中:

    public LayoutInflater cloneInContext(Context newContext) {
        return new PhoneLayoutInflater(this, newContext);
    }

可見(jiàn)就是給他一個(gè)LayoutInflater以便于在ViewStub調(diào)用inflate()時(shí)使用,ViewStub的inflate()方法實(shí)際上就是調(diào)用了LayoutInflater的inflate方法來(lái)加載view吟税,這樣就實(shí)現(xiàn)了界面加載效率的提高(具體分析見(jiàn)文章最后)凹耙。

看完自定義view的加載,我們?cè)倏磧?nèi)置view的加載乌妙,調(diào)用的時(shí)onCreateView方法:

    protected View onCreateView(View parent, String name, AttributeSet attrs)
            throws ClassNotFoundException {
        return onCreateView(name, attrs);
    }

這里的兩參數(shù)onCreateView方法在PhoneLayoutInflater中有重寫:

    @Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
        for (String prefix : sClassPrefixList) {
            try {
                View view = createView(name, prefix, attrs);
                if (view != null) {
                    return view;
                }
            } catch (ClassNotFoundException e) {
            }
        }

        return super.onCreateView(name, attrs);
    }

sClassPrefixList值為下:

    private static final String[] sClassPrefixList = {
        "android.widget.",
        "android.webkit.",
        "android.app."
    };

最后也調(diào)用了createView方法使兔,和自定義view的一樣,只不過(guò)自定義view中createView第二個(gè)參數(shù)為null藤韵,內(nèi)置view這里有值虐沥,主要是講TextView,ImageView這些簡(jiǎn)稱補(bǔ)全為全類名,方便反射欲险。

到這里一個(gè)view基本上是創(chuàng)建出來(lái)了镐依,我們?cè)倩厮莸絠nflate方法中(代碼跨度比較大天试,源碼分析就是這樣槐壳,但我們心中要有清晰的線路)务唐,在創(chuàng)建完view后,做了一個(gè)root != null的判斷带兜,這里是讀取了根布局的Params枫笛,只有attachToRoot為false時(shí)刚照,才會(huì)應(yīng)用父節(jié)點(diǎn)的布局參數(shù),這也就是我們?cè)谑褂肔istView或者RecycleView創(chuàng)建View時(shí)无畔,為什么有時(shí)候根標(biāo)簽的屬性會(huì)補(bǔ)齊作用的原因啊楚,到這里下面這個(gè)我們常用方法的三個(gè)參數(shù)的意義應(yīng)該都清楚了:

View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)

在配置完參數(shù)后,又調(diào)用了rInflateChildren去解析標(biāo)簽內(nèi)的子view:

    final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
            boolean finishInflate) throws XmlPullParserException, IOException {
        rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
    }

這個(gè)里面又調(diào)用了rInflate方法恭理,基本上就是深度優(yōu)先的遍歷解析xml文件闸昨,把遇到的所有標(biāo)簽換為對(duì)應(yīng)對(duì)象饵较,然后viewGroup.addView方法一層一層聯(lián)系起來(lái)。

在inflate方法最后循诉,通過(guò)兩個(gè)判斷,確定是返回根布局還是單個(gè)view狈蚤。我們從Activity中過(guò)來(lái)時(shí)划纽,root不為空勇劣,attachToRoot為true潭枣,根據(jù)流程時(shí)將解析出來(lái)的view添加到根布局并返回根布局幻捏。再介紹另外一個(gè)場(chǎng)景,就是在Listview中g(shù)etView時(shí)谐岁,我們參考一個(gè)權(quán)威的寫法:

view = inflater.inflate(resource, parent, false);

上面來(lái)自于ArrayAdapter榛臼。他的attachToRoot為false伊佃,所以返回的是我們要構(gòu)建的xml文件內(nèi)容讽坏,也就是一個(gè)一個(gè)item中要顯示的內(nèi)容例证。

最后的最后织咧,我們?cè)诨氐絇honeWindow的setContentView方法,這里只是簡(jiǎn)單的調(diào)用mLayoutInflater.inflate(layoutResID, mContentParent);抵屿,根據(jù)上一段的流程分析捅位,目的就是將xml文件解析出來(lái)的視圖樹添加到mContentParent(關(guān)于mContentParent的具體可以分析PhoneWindow的內(nèi)容,這里就不詳細(xì)說(shuō)明了尿扯,只用知道他是DecorView的一部分衷笋,是Activity的視圖區(qū)域即可)矩屁。

到這里我們的分析基本上就完成了,我們最后梳理一下泊脐,前一部分跟蹤了setContentView的調(diào)用烁峭,發(fā)現(xiàn)最后調(diào)用了mLayoutInflater.inflate(layoutResID, mContentParent);方法。inflate方法只是一個(gè)入口耘柱,具體一層一層解析xml文件視圖樹的任務(wù)則交給了rInflate方法调煎,根據(jù)標(biāo)簽創(chuàng)建view對(duì)象的任務(wù)交給了createViewFromTag方法,遍歷的方法是深度優(yōu)先遍歷的悲关。

最后我們?cè)谔幚硪粋€(gè)遺留問(wèn)題娄柳,就是關(guān)于ViewStub的赤拒,都說(shuō)使用這個(gè)會(huì)節(jié)省內(nèi)存提高性能,到底因?yàn)槭裁茨卣饩矗覀兘柽@個(gè)分析LayoutInflater.inflate的機(jī)會(huì)來(lái)看一下蕉朵。

第一個(gè)問(wèn)題:我們說(shuō)ViewStub中的內(nèi)容是不會(huì)立即創(chuàng)建出對(duì)象的,而其他view不管是不是GONE狀態(tài)都會(huì)創(chuàng)建冷蚂。這點(diǎn)很好證明蝙茶,在createView方法中蛉拙,我們并沒(méi)有看到任何地方會(huì)根據(jù)VIew的可見(jiàn)性來(lái)控制對(duì)象的創(chuàng)建,而單獨(dú)對(duì)ViewStub做了特殊處理吮廉,僅僅實(shí)例化了ViewStub對(duì)象畸肆,而對(duì)其內(nèi)部的layout沒(méi)有處理轴脐,所以可以說(shuō)明ViewStub確實(shí)有延遲加載的功能抡砂,可以在界面創(chuàng)建階段不過(guò)多的加載view節(jié)省內(nèi)存注益。

第二個(gè)問(wèn)題:我們都說(shuō)ViewStub被設(shè)置為可見(jiàn)的時(shí)候溯捆,或是調(diào)用了ViewStub.inflate()的時(shí)候提揍,布局才會(huì)被加載,到底是不是呢谎仲?我們看ViewStub的源碼:

android\frameworks\base\core\java\android\view\ViewStub.java
    public void setVisibility(int visibility) {
        if (mInflatedViewRef != null) {
            View view = mInflatedViewRef.get();
            if (view != null) {
                view.setVisibility(visibility);
            } else {
                throw new IllegalStateException("setVisibility called on un-referenced view");
            }
        } else {
            super.setVisibility(visibility);
            if (visibility == VISIBLE || visibility == INVISIBLE) {
                inflate();
            }
        }
    }

    public View inflate() {
        final ViewParent viewParent = getParent();

        if (viewParent != null && viewParent instanceof ViewGroup) {
            if (mLayoutResource != 0) {
                final ViewGroup parent = (ViewGroup) viewParent;
                final View view = inflateViewNoAdd(parent);
                replaceSelfWithView(view, parent);

                mInflatedViewRef = new WeakReference<>(view);
                if (mInflateListener != null) {
                    mInflateListener.onInflate(this, view);
                }

                return view;
            } else {
                throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
            }
        } else {
            throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
        }
    }

    private View inflateViewNoAdd(ViewGroup parent) {
        final LayoutInflater factory;
        if (mInflater != null) {
            factory = mInflater;
        } else {
            factory = LayoutInflater.from(mContext);
        }
        final View view = factory.inflate(mLayoutResource, parent, false);

        if (mInflatedId != NO_ID) {
            view.setId(mInflatedId);
        }
        return view;
    }

可見(jiàn)調(diào)用setVisibility設(shè)置為VISIBLE 或INVISIBLE是就會(huì)調(diào)用inflate()郑诺。而inflate()中的inflateViewNoAdd方法就是調(diào)用了LayoutInflater的inflate方法去解析布局文件(這里的LayoutInflater實(shí)例就是在LayoutInflater的createView中最后若一個(gè)view是ViewStub時(shí)調(diào)用setLayoutInflater傳入的)间景,最后在ViewStub的inflate()中調(diào)用replaceSelfWithView將布局中ViewStub自己替換為inflate出來(lái)的視圖樹艺智,replaceSelfWithView也很簡(jiǎn)單就是移除和添加:

    private void replaceSelfWithView(View view, ViewGroup parent) {
        final int index = parent.indexOfChild(this);
        parent.removeViewInLayout(this);

        final ViewGroup.LayoutParams layoutParams = getLayoutParams();
        if (layoutParams != null) {
            parent.addView(view, index, layoutParams);
        } else {
            parent.addView(view, index);
        }
    }

第三個(gè)問(wèn)題十拣,為什么inflate()只能用一次而setVisibility(View.VISIBLE )可以多次調(diào)用志鹃?這個(gè)在上面方法中就能發(fā)現(xiàn)曹铃,它直接把ViewStub移除,新的布局被替換進(jìn)去秘血,這也就是為什么Inflate只能調(diào)用一次的原因评甜,因?yàn)樵趇nflate()中viewParent 為空忍坷,將拋異常熔脂。另外在inflate()中實(shí)例化了mInflatedViewRef 對(duì)象柑肴,下次調(diào)用setVisibility時(shí)晰骑,由于mInflatedViewRef 不為空,就調(diào)用不到inflate()了隶症,所以可以安全的多次調(diào)用setVisibility岗宣。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末耗式,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子彪见,更是在濱河造成了極大的恐慌娱挨,老刑警劉巖跷坝,帶你破解...
    沈念sama閱讀 211,376評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件柴钻,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡靠粪,警方通過(guò)查閱死者的電腦和手機(jī)毫蚓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門绍些,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人啸澡,你說(shuō)我怎么就攤上這事嗅虏。” “怎么了楞艾?”我有些...
    開封第一講書人閱讀 156,966評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵硫眯,是天一觀的道長(zhǎng)择同。 經(jīng)常有香客問(wèn)我,道長(zhǎng)裹纳,這世上最難降的妖魔是什么紧武? 我笑而不...
    開封第一講書人閱讀 56,432評(píng)論 1 283
  • 正文 為了忘掉前任阻星,我火速辦了婚禮迫横,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己疏哗,他們只是感情好返奉,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評(píng)論 6 385
  • 文/花漫 我一把揭開白布芽偏。 她就那樣靜靜地躺著,像睡著了一般膀哲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上仿村,一...
    開封第一講書人閱讀 49,792評(píng)論 1 290
  • 那天蔼囊,我揣著相機(jī)與錄音衣迷,去河邊找鬼壶谒。 笑死,一個(gè)胖子當(dāng)著我的面吹牛泼差,可吹牛的內(nèi)容都是我干的呵俏。 我是一名探鬼主播普碎,決...
    沈念sama閱讀 38,933評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼缀皱!你這毒婦竟也來(lái)了啤斗?” 一聲冷哼從身側(cè)響起赁咙,我...
    開封第一講書人閱讀 37,701評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤彼水,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后链瓦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盯桦,經(jīng)...
    沈念sama閱讀 44,143評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評(píng)論 2 327
  • 正文 我和宋清朗相戀三年溪掀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了揪胃。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片氛琢。...
    茶點(diǎn)故事閱讀 38,626評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡阳似,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出俏讹,到底是詐尸還是另有隱情泽疆,我是刑警寧澤玲献,帶...
    沈念sama閱讀 34,292評(píng)論 4 329
  • 正文 年R本政府宣布捌年,位于F島的核電站,受9級(jí)特大地震影響眠砾,放射性物質(zhì)發(fā)生泄漏托酸。R本人自食惡果不足惜获高,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評(píng)論 3 313
  • 文/蒙蒙 一念秧、第九天 我趴在偏房一處隱蔽的房頂上張望布疼。 院中可真熱鬧币狠,春花似錦砾层、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)秕重。三九已至,卻和暖如春二拐,著一層夾襖步出監(jiān)牢的瞬間凳兵,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留聚蝶,地道東北人碘勉。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓验靡,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親高职。 傳聞我的和親對(duì)象是個(gè)殘疾皇子辞州,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容