Android 重學(xué)系列 View的繪制流程 (一)View的初始化

前言

View的繪制流程這一篇文章其實(shí)十分不好寫(xiě),因?yàn)樵诰W(wǎng)上已經(jīng)有千篇一律的文章哪廓,導(dǎo)致我一直不太想寫(xiě)這一篇文章。不過(guò)既然是Android重學(xué)系列,還是一步一腳印來(lái)分析分析里面的細(xì)節(jié)证逻。如果對(duì)這個(gè)流程很熟悉的人來(lái)說(shuō),本文就沒(méi)必要閱讀了抗斤。如果不是很熟悉的朋友可以閱讀本文囚企,看看系統(tǒng)上設(shè)計(jì)的優(yōu)點(diǎn)以及可以?xún)?yōu)化的地方丈咐。

正文

在整個(gè)View的繪制流程中,從大的方向看來(lái)龙宏,大致上分為兩部分:

  • 在Activity的onCreate生命周期棵逊,實(shí)例化所有的View。
  • Activity的onResume生命周期银酗,測(cè)量辆影,布局,繪制所有的View黍特。

暫時(shí)不去看Activity如何聯(lián)通SurfaceFlinger蛙讥,之后會(huì)有專(zhuān)門(mén)的專(zhuān)題再來(lái)聊聊。
那么Activity是怎么管理View的繪制的呢灭衷?接下來(lái)我們會(huì)以上面兩點(diǎn)為線索來(lái)分析一下源碼键菱。

不過(guò)內(nèi)容很多,本文集中重點(diǎn)聊聊view的實(shí)例化是怎么回事今布。

總覽

  • Activity的初始化與分層
  • LayoutInflater原理经备,以及思考
  • AsyncLayoutInflater的原理以及缺陷

Activity onCreate的綁定

其實(shí)Activity的生命周期僅僅只是管理著Activity這個(gè)對(duì)象的活躍狀態(tài),并沒(méi)有真的去管理View部默,那么Activity是怎么通過(guò)管理View的繪制的呢侵蒙?我們來(lái)看看在ActivityThread調(diào)用performLaunchActivity:
文件:/frameworks/base/core/java/android/app/ActivityThread.java

                Window window = null;
                if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
                    window = r.mPendingRemoveWindow;
                    r.mPendingRemoveWindow = null;
                    r.mPendingRemoveWindowManager = null;
                }
                appContext.setOuterContext(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);

能看到在這個(gè)步驟中,對(duì)著實(shí)例化的Activity做了一次綁定操作傅蹂。具體做什么呢纷闺?

    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) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
...
        mUiThread = Thread.currentThread();

        mMainThread = aThread;
        mInstrumentation = instr;
        mToken = token;
        mIdent = ident;
        mApplication = application;
        mIntent = intent;
        mReferrer = referrer;
        mComponent = intent.getComponent();
        mActivityInfo = info;
....
        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);

...
    }

能看到在attach中實(shí)際上最為重要的工作就是實(shí)例化一個(gè)PhoneWindow對(duì)象,并且把當(dāng)前Phone相關(guān)的監(jiān)聽(tīng)份蝴,如點(diǎn)擊事件的回調(diào)犁功,窗體消失的回調(diào)等等。

并且把ActivityThread婚夫,ActivityInfo浸卦,Application等重要的信息綁定到當(dāng)前的Activity。

從上一個(gè)專(zhuān)欄WMS案糙,就能知道限嫌,實(shí)際上承載視圖真正的對(duì)象實(shí)際上是Window窗口。那么這個(gè)Window對(duì)象又是什么做第一次的視圖加載呢时捌?

其實(shí)是調(diào)用了我們及其熟悉的api:

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

在這個(gè)api中設(shè)置了PhoneWindow的內(nèi)容視圖區(qū)域怒医。這也是每一個(gè)Android開(kāi)發(fā)的接觸到的第一個(gè)api。因?yàn)槠渲陵P(guān)重要承載了Android接下來(lái)要顯示什么內(nèi)容奢讨。

接下來(lái)我們很容易想到setContentView究竟做了什么事情稚叹,來(lái)初始化所有的View對(duì)象。

PhoneWindow.setContentView

文件:/frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java

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

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

我們這里只關(guān)注核心邏輯。能看到當(dāng)mContentParent為空的時(shí)候扒袖,會(huì)調(diào)用installDecor生成一個(gè)父容器蛤奥,最終會(huì)通過(guò)我們另一個(gè)熟悉的函數(shù)LayoutInflater.inflate把所有的View都實(shí)例化出來(lái)。

那么同理僚稿,我們把整個(gè)步驟分為2部分:

  • 1.installDecor生成DecorView安裝在FrameLayout作為所有View的頂層View
  • 2.LayoutInflater.inflate 實(shí)例化傳進(jìn)來(lái)的內(nèi)容。

installDecor生成DecorView作為所有View的頂層View

    private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
//核心事件1
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
//核心事件二
            mContentParent = generateLayout(mDecor);

            // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
            mDecor.makeOptionalFitsSystemWindows();

            final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
                    R.id.decor_content_parent);

            if (decorContentParent != null) {
...
            } else {
...
            }

            if (mDecor.getBackground() == null && mBackgroundFallbackResource != 0) {
                mDecor.setBackgroundFallback(mBackgroundFallbackResource);
            }

            // Only inflate or create a new TransitionManager if the caller hasn't
            // already set a custom one.
            if (hasFeature(FEATURE_ACTIVITY_TRANSITIONS)) {
...
            }
        }
    }

我們抽出核心邏輯看看這個(gè)installDecor做的事情實(shí)際上很簡(jiǎn)單:

  • 1.generateDecor生成DecorView
  • 2.generateLayout 獲取DecorView中的內(nèi)容區(qū)域
  • 3.尋找DecorView中的DecorContentParent處理PanelMenu等系統(tǒng)內(nèi)置的掛在view蟀伸。
  • 4.處理專(zhuān)場(chǎng)動(dòng)畫(huà)蚀同。

我們只需要把關(guān)注點(diǎn)放在頭兩項(xiàng)。這兩個(gè)才是本文的重點(diǎn)啊掏。第四項(xiàng)的處理實(shí)際上是處理

generateDecor生成DecorView

    protected DecorView generateDecor(int featureId) {
        // System process doesn't have application context and in that case we need to directly use
        // the context we have. Otherwise we want the application context, so we don't cling to the
        // activity.
        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();
        }
        return new DecorView(context, featureId, this, getAttributes());
    }

能看到里面十分簡(jiǎn)單蠢络,聲明DecorContext,注入到DecorView迟蜜。如果處理著名的鍵盤(pán)內(nèi)存泄漏的時(shí)候刹孔,把打印打開(kāi),當(dāng)切換到另一個(gè)Activity的時(shí)候娜睛,就會(huì)看到這個(gè)這個(gè)Context髓霞。

在Android系統(tǒng)看來(lái)DecorView必須擁有自己的Context的原因是,DecorView是系統(tǒng)自己的服務(wù)畦戒,因此需要做Context的隔離方库。不過(guò)雖然是系統(tǒng)服務(wù),但是還是添加到我們的View當(dāng)中障斋。

generateLayout 獲取DecorView中的內(nèi)容區(qū)域

 protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.

        TypedArray a = getWindowStyle();
//獲取當(dāng)前窗體所有的標(biāo)志位
   ...
//根據(jù)標(biāo)志位做初步處理纵潦,如背景
  ....

 ...

        // 根據(jù)標(biāo)志位設(shè)置資源id
        int layoutResource;
        int features = getLocalFeatures();
        // System.out.println("Features: 0x" + Integer.toHexString(features));
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
      ...
        } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
 ...
        } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
                && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
   ...
        } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
...
        } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
...
        } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
            layoutResource = R.layout.screen_simple_overlay_action_mode;
        } else {
            // Embedded, so no decoration is needed.
            layoutResource = R.layout.screen_simple;
            // System.out.println("Simple!");
        }

        mDecor.startChanging();
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

 ...

        return contentParent;
    }

這里做的事情如下:

  • 1.獲取設(shè)置在xml中的窗體style,設(shè)置相應(yīng)的標(biāo)志位如mFloat等
  • 2.獲取DecorView窗體的屬性垃环,進(jìn)一步處理一些背景邀层,根據(jù)當(dāng)前的Android版本,style重新設(shè)置窗體的屬性遂庄,以供后面使用寥院。
  • 3.根據(jù)上面設(shè)置的標(biāo)志位設(shè)置合適的窗體資源
  • 4.獲取ID_ANDROID_CONTENT中的內(nèi)容區(qū)域。

假如涛目,我們當(dāng)前使用的是最普通的狀態(tài)只磷,將會(huì)加載R.layout.screen_simple;資源文件到DecorView中,當(dāng)實(shí)例化好當(dāng)前的xml資源之后泌绣,將會(huì)從DecorView找到我們的內(nèi)容區(qū)域部分钮追。

我們看看screen_simple是什么東西:

<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>

能看到這是一個(gè)LinearLayout包裹著的一個(gè)FrameLayout。把當(dāng)前的View設(shè)置一個(gè)windowContentOverlay屬性阿迈。這個(gè)屬性可以在Activity生成之后元媚,渲染速度太慢可以設(shè)置成白色透明或者圖片。一個(gè)ViewStub用來(lái)優(yōu)化顯示actionbar

和startingWindow有本質(zhì)上的區(qū)別,startingWindow的出現(xiàn)是為了處理還沒(méi)有進(jìn)入Activity刊棕,繪制在屏幕的窗體炭晒,同時(shí)還能獲取之前保留下來(lái)的屏幕像素渲染在上面。是一個(gè)優(yōu)化顯示體驗(yàn)的設(shè)計(jì)甥角。

最后找到這個(gè)id為content的內(nèi)容區(qū)域返回回去网严。

這樣我們就知道網(wǎng)上那個(gè)Android的顯示區(qū)域劃分圖是怎么來(lái)的。


Android顯示View的構(gòu)成.jpeg

如果我們把對(duì)象考慮進(jìn)來(lái)大致上是如此:


對(duì)象包含圖.jpeg

LayoutInflater.inflate實(shí)例化所有內(nèi)容視圖

核心代碼如下:

mLayoutInflater.inflate(layoutResID, mContentParent);

實(shí)際上對(duì)于Android開(kāi)發(fā)來(lái)說(shuō)嗤无,這個(gè)api也熟悉的不能再熟悉了震束。我們看看LayoutInflater常用的用法。

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

可以去結(jié)合我上一個(gè)專(zhuān)欄的WMS的getSystemService分析当犯。實(shí)際上在這里面LayoutInflater是一個(gè)全局單例垢村。為什么一定要設(shè)計(jì)為單例這是有原因的『课溃看到后面就知道為什么了嘉栓。

LayoutInflater.inflate原理

接下來(lái)我們把注意力轉(zhuǎn)移到inflater如何實(shí)例化view上面:

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

    public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {
        return inflate(parser, 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) + ")");
        }

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

我們常用的LayoutInflater其實(shí)有三種方式,最后都會(huì)調(diào)用三個(gè)參數(shù)的inflate方法拓诸。
分為2個(gè)步驟

  • 1.首先先通過(guò)Resource獲取XmlResourceParser的解析器
  • 2.inflate按照解析器實(shí)例化View侵佃。

換句話說(shuō),為了弄清楚View怎么實(shí)例化這個(gè)流程奠支,我們必須看Android是怎么獲取資源的趣钱。

Resource解析xml

    public XmlResourceParser getLayout(@LayoutRes int id) throws NotFoundException {
        return loadXmlResourceParser(id, "layout");
    }

    XmlResourceParser loadXmlResourceParser(@AnyRes int id, @NonNull String type)
            throws NotFoundException {
        final TypedValue value = obtainTempTypedValue();
        try {
            final ResourcesImpl impl = mResourcesImpl;
            impl.getValue(id, value, true);
            if (value.type == TypedValue.TYPE_STRING) {
                return impl.loadXmlResourceParser(value.string.toString(), id,
                        value.assetCookie, type);
            }
            throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
                    + " type #0x" + Integer.toHexString(value.type) + " is not valid");
        } finally {
            releaseTempTypedValue(value);
        }
    }

能看到此時(shí)是通過(guò)loadXmlResourceParser來(lái)告訴底層解析的是Layout資源。能看到此時(shí)會(huì)把工作交給ResourcesImpl和AssetManager去完成胚宦。這個(gè)類(lèi)如果看過(guò)我的文章的朋友就很熟悉了首有,這兩個(gè)類(lèi)就是Java層加載資源的核心類(lèi),當(dāng)我們做插件化的時(shí)候枢劝,是不可避免的接觸這個(gè)類(lèi)井联。

經(jīng)過(guò)的方法,大致上我們把資源讀取的步驟分為3部分:

  • 1.獲取TypedValue
  • 2.讀取資源文件您旁,生成保存著xml解析內(nèi)容的對(duì)象
  • 3.釋放掉TypedValue

這個(gè)步驟和我們平時(shí)開(kāi)發(fā)自定義View設(shè)置自定義屬性的時(shí)候何其相似烙常,都是通過(guò)obtainStyledAttributes打開(kāi)TypeArray,讀取其中的數(shù)據(jù)鹤盒,最后關(guān)閉TypeArray蚕脏。

這背后隱藏這什么玄機(jī)呢?我們之后會(huì)有文章專(zhuān)門(mén)探索侦锯,我們不要打斷當(dāng)前的思緒驼鞭。

inflate解析Xml解析器中的數(shù)據(jù)

    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 {
                // Look for the root node.
                int type;
                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!");
                }
                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 {
                    // Temp is the root view that was found in the xml
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    ViewGroup.LayoutParams params = null;

                    if (root != null) {
                        // Create layout params that match root, if supplied
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            temp.setLayoutParams(params);
                        }
                    }
                    rInflateChildren(parser, temp, attrs, true);

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

                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }

            } catch (XmlPullParserException e) {
...
            } catch (Exception e) {
...
            } finally {
                // Don't retain static reference on context.
                mConstructorArgs[0] = lastContext;
                mConstructorArgs[1] = null;

                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }

            return result;
        }
    }

從這我們就能看到是否添加父布局以及是否綁定父布局之間的差別了。

首先不斷的從xml的頭部開(kāi)始查找(第一個(gè)“<”)尺碰,直到找到第一個(gè)view的進(jìn)行解析挣棕。接下來(lái)就分為兩個(gè)路線:

  • 1.此時(shí)xml布局使用了merge優(yōu)化译隘,調(diào)用rInflate。請(qǐng)注意直接使用LayoutInflate實(shí)例化merge標(biāo)簽洛心,請(qǐng)?jiān)O(shè)置根布局不然會(huì)報(bào)錯(cuò)固耘。
  • 2.xml是普通的布局,調(diào)用rInflateChildren

先來(lái)看看第二種不普通情況词身,在沒(méi)有merge布局的情況:

  • 1.先通過(guò)createViewFromTag實(shí)例化對(duì)應(yīng)的view厅目。
  • 2.接著根據(jù)查看當(dāng)前有沒(méi)有root需要綁定的父布局,如果有法严,則獲取根布局的generLayout:
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(getContext(), attrs);
    }

根據(jù)xml當(dāng)前標(biāo)簽的屬性生成適配根布局的LayoutParams损敷。

attachToRoot如果為true,則會(huì)直接添加到根布局中渐夸。

  • 3.rInflateChildren繼續(xù)解析當(dāng)前布局下的根布局,進(jìn)入遞歸渔欢。

這也解釋了三個(gè)inflate方法之間的區(qū)別墓塌,帶著根部布局參數(shù)的inflate能夠?qū)?dāng)前根部的標(biāo)簽的參數(shù)生成一個(gè)適配根部LayoutParams,也就保留了根部布局的屬性奥额。而最后一個(gè)bool僅僅是代表用不用系統(tǒng)自動(dòng)幫你添加到根布局中

在這里面有一個(gè)核心的函數(shù)createViewFromTag苫幢,這是就是如何創(chuàng)建View的核心。

createViewFromTag創(chuàng)建View

    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;
        } catch (InflateException e) {
...
        } catch (ClassNotFoundException e) {
...
        } catch (Exception e) {
...
        }
    }

實(shí)際上這里面很簡(jiǎn)單和很巧妙垫挨,也能發(fā)現(xiàn)Android 中的新特性韩肝。

  • 1.如果標(biāo)簽直接是view,則直接獲取xml標(biāo)簽中系統(tǒng)中class的屬性九榔,這樣就能找到view對(duì)應(yīng)的類(lèi)名哀峻。

  • 2.如果name是blink,則創(chuàng)建一個(gè)深度鏈接的布局

  • 3.通過(guò)三層的Factory攔截view的創(chuàng)建哲泊,分別是Factory剩蟀,F(xiàn)actory2,privateFactory切威。

  • 4.如果經(jīng)過(guò)上層由用戶(hù)或者系統(tǒng)定義的特殊view的生成攔截沒(méi)有生成育特,則會(huì)判斷當(dāng)前的標(biāo)簽名又沒(méi)有"."。有“.”說(shuō)明是自定義view先朦,沒(méi)有說(shuō)明是系統(tǒng)控件缰冤。

  • 1.系統(tǒng)控件調(diào)用onCreateView創(chuàng)建View。

  • 2.自定義View調(diào)用createView創(chuàng)建View喳魏。

在繼續(xù)下一步之前棉浸,讓我們把目光放回系統(tǒng)生成LayoutInflater的方法中,看看生成LayoutInflater有什么貓膩刺彩?
文件:/frameworks/base/core/java/android/app/SystemServiceRegistry.java

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

能看到系統(tǒng)初期實(shí)例化的是一個(gè)PhoneLayoutInflater涮拗,并非是一個(gè)普通的LayoutInflater乾戏,而這個(gè)類(lèi)重載了一個(gè)很重要的方法:
文件:http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/com/android/internal/policy/PhoneLayoutInflater.java

    private static final String[] sClassPrefixList = {
//常用控件
        "android.widget.",
//一般指WebView
        "android.webkit.",
//一般指Fragment
        "android.app."
    };

    @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) {
                // In this case we want to let the base class take a crack
                // at it.
            }
        }

        return super.onCreateView(name, attrs);
    }

能看到這樣就手動(dòng)為View添加了前綴,還是調(diào)用了createView創(chuàng)建View三热。舉個(gè)例子鼓择,如果是一個(gè)Linearlayout,就會(huì)為這個(gè)標(biāo)簽添加android.widget.前綴就漾,稱(chēng)為android.widget.Linearlayout呐能。這樣就能找到相對(duì)完整的類(lèi)名膛虫。

createView創(chuàng)建View的核心動(dòng)作

    public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
//所有view對(duì)應(yīng)構(gòu)造函數(shù)
        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 = 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 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 = 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) {
                // Fill in the context if not already within inflation.
                mConstructorArgs[0] = mContext;
            }
            Object[] args = mConstructorArgs;
            args[1] = 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) {
...
        } catch (ClassCastException e) {
...
        } catch (ClassNotFoundException e) {
...
        } catch (Exception e) {
...
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

實(shí)際上我們能夠看到在實(shí)例化View有一個(gè)關(guān)鍵的數(shù)據(jù)結(jié)構(gòu):sConstructorMap.

這個(gè)數(shù)據(jù)結(jié)構(gòu)保存著app應(yīng)用內(nèi)所有實(shí)例化過(guò)的View對(duì)應(yīng)的構(gòu)造函數(shù)乙埃。主要經(jīng)過(guò)存儲(chǔ)一次,就以name為key存儲(chǔ)對(duì)應(yīng)的構(gòu)造函數(shù)快骗,就不用一直反射獲取了首妖。

實(shí)際上這段代碼就是做這個(gè)事情:

  • 1.當(dāng)從name找構(gòu)造函數(shù)偎漫,找的到,就直接通過(guò)構(gòu)造函數(shù)實(shí)例化
  • 2.沒(méi)有找到構(gòu)造函數(shù)有缆,則通過(guò)prefix+name的方式查找類(lèi)的構(gòu)造函數(shù)象踊,接著再實(shí)例化。

這也解釋了為什么LayoutInflater一定要做成全局單例的方式棚壁,原因很簡(jiǎn)單就是為了加速view實(shí)例化的過(guò)程杯矩,共用反射的構(gòu)造函數(shù)的緩存。

View布局優(yōu)化細(xì)節(jié)

  • 當(dāng)然也能看到ViewStub實(shí)際上在這里面沒(méi)有做太多事情袖外,把當(dāng)前的Layoutflater復(fù)制一份進(jìn)去史隆,延后實(shí)例化里面的View。
  • merge優(yōu)化原理曼验,也能明白了泌射,實(shí)際上在正常的分支會(huì)直接實(shí)例化一個(gè)View之后再添加,接著遞歸子view繼續(xù)添加當(dāng)前的View鬓照。而merge則是跳出第一次實(shí)例化View步驟魄幕,直接進(jìn)入遞歸。

merge能做到什么事情呢颖杏?我當(dāng)然知道是壓縮層級(jí)纯陨?一般是壓縮include標(biāo)簽的層級(jí)。

這里就解釋了怎么壓縮層級(jí)留储。換句話說(shuō)翼抠,我們使用merge的時(shí)候完全不用添加父布局,直接用merge標(biāo)簽包裹子view即可获讳,當(dāng)我們不能預(yù)覽阴颖,在merge上添加parentTag即可。

舉個(gè)例子:

<FrameLayout>
   <include layout="@layout/layout2"/>
</FrameLayout>

layout2.xml:

<merge>
   <TextView />
</merge>

合并之后就是如下:

<FrameLayout>
   <TextView />
</FrameLayout>

如果把include作為根布局呢丐膝?很明顯會(huì)找不到android.view.include這個(gè)文件量愧。

rInflate遞歸解析View

當(dāng)我們生成View之后钾菊,就需要繼續(xù)遞歸子View,讓我們看看其核心邏輯是什么偎肃。

能看到無(wú)論是是走merge分支還是正常解析分支也好煞烫,本質(zhì)上都會(huì)調(diào)用到rInflate這個(gè)方法。

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

在一段代碼就是不斷循環(huán)當(dāng)前的當(dāng)前xml的節(jié)點(diǎn)解析內(nèi)部的標(biāo)簽累颂,直到到了標(biāo)簽的末尾("/>")
分為如下幾個(gè)分支:

  • 1.如果發(fā)現(xiàn)需要requestFoucs滞详,則設(shè)置當(dāng)前view標(biāo)志為聚焦
  • 2.如果發(fā)現(xiàn)標(biāo)簽是include,則調(diào)用parseInclude紊馏,解開(kāi)include內(nèi)部的layout進(jìn)行解析料饥。
  • 3.標(biāo)簽是tag,則保存tag中的內(nèi)容
  • 4.發(fā)現(xiàn)標(biāo)簽是merge則報(bào)錯(cuò)
  • 5.其他情況朱监,如正常一般岸啡,會(huì)正常生成View,并且添加當(dāng)前的父布局中赫编。

最后會(huì)回調(diào)onFinishInflate監(jiān)聽(tīng)巡蘸。

LayoutInflater Factory的妙用

或許有人會(huì)覺(jué)得LayoutInflater中設(shè)置Factory干什么的?實(shí)際上這個(gè)Factory到處有在用沛慢,只是我們沒(méi)有注意到過(guò)而已赡若。

舉一個(gè)例子达布,肯定有人注意過(guò)吧团甲。當(dāng)我們使用AppCompatActivity的時(shí)候,如果打印或者打斷點(diǎn)黍聂,會(huì)發(fā)現(xiàn)內(nèi)部的ImageView這些view躺苦,會(huì)被替換掉,變成AppCompat開(kāi)頭的view产还,如AppCompatImageView匹厘。

我們來(lái)看看AppCompatActivity的onCreate方法:

  @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        final AppCompatDelegate delegate = getDelegate();
        delegate.installViewFactory();
        delegate.onCreate(savedInstanceState);
}

實(shí)際上AppCompatDelegate本質(zhì)上是AppCompatDelegateImpl,在這個(gè)類(lèi)中調(diào)用了installViewFactory的方法脐区。

    public void installViewFactory() {
        LayoutInflater layoutInflater = LayoutInflater.from(mContext);
        if (layoutInflater.getFactory() == null) {
            LayoutInflaterCompat.setFactory2(layoutInflater, this);
        } else {
...
        }
    }

既然我們直到Factory需要重寫(xiě)onCreateView的方法愈诚,我們直接看看這個(gè)類(lèi)中的方法:

    public View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs) {
        if (mAppCompatViewInflater == null) {
            TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
            String viewInflaterClassName =
                    a.getString(R.styleable.AppCompatTheme_viewInflaterClass);
            if ((viewInflaterClassName == null)
                    || AppCompatViewInflater.class.getName().equals(viewInflaterClassName)) {
...
                mAppCompatViewInflater = new AppCompatViewInflater();
            } else {
                try {
                    Class viewInflaterClass = Class.forName(viewInflaterClassName);
                    mAppCompatViewInflater =
                            (AppCompatViewInflater) viewInflaterClass.getDeclaredConstructor()
                                    .newInstance();
                } catch (Throwable t) {
..
                    mAppCompatViewInflater = new AppCompatViewInflater();
                }
            }
        }

...
        return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
                IS_PRE_LOLLIPOP, /* Only read android:theme pre-L (L+ handles this anyway) */
                true, /* Read read app:theme as a fallback at all times for legacy reasons */
                VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */
        );
    }

能看到此時(shí),會(huì)實(shí)例化AppCompatViewInflater牛隅,把實(shí)例化View交給它來(lái)處理炕柔。

AppCompatViewInflater.createView

    final View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs, boolean inheritContext,
            boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
        final Context originalContext = context;

        // We can emulate Lollipop's android:theme attribute propagating down the view hierarchy
        // by using the parent's context
        if (inheritContext && parent != null) {
            context = parent.getContext();
        }
        if (readAndroidTheme || readAppTheme) {
            // We then apply the theme on the context, if specified
            context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
        }
        if (wrapContext) {
            context = TintContextWrapper.wrap(context);
        }

        View view = null;

        // We need to 'inject' our tint aware Views in place of the standard framework versions
        switch (name) {
            case "TextView":
                view = createTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "ImageView":
                view = createImageView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "Button":
                view = createButton(context, attrs);
                verifyNotNull(view, name);
                break;
            case "EditText":
                view = createEditText(context, attrs);
                verifyNotNull(view, name);
                break;
            case "Spinner":
                view = createSpinner(context, attrs);
                verifyNotNull(view, name);
                break;
            case "ImageButton":
                view = createImageButton(context, attrs);
                verifyNotNull(view, name);
                break;
            case "CheckBox":
                view = createCheckBox(context, attrs);
                verifyNotNull(view, name);
                break;
            case "RadioButton":
                view = createRadioButton(context, attrs);
                verifyNotNull(view, name);
                break;
            case "CheckedTextView":
                view = createCheckedTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "AutoCompleteTextView":
                view = createAutoCompleteTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "MultiAutoCompleteTextView":
                view = createMultiAutoCompleteTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "RatingBar":
                view = createRatingBar(context, attrs);
                verifyNotNull(view, name);
                break;
            case "SeekBar":
                view = createSeekBar(context, attrs);
                verifyNotNull(view, name);
                break;
            default:
                // The fallback that allows extending class to take over view inflation
                // for other tags. Note that we don't check that the result is not-null.
                // That allows the custom inflater path to fall back on the default one
                // later in this method.
                view = createView(context, name, attrs);
        }

        if (view == null && originalContext != context) {
            // If the original context does not equal our themed context, then we need to manually
            // inflate it using the name so that android:theme takes effect.
            view = createViewFromTag(context, name, attrs);
        }

        if (view != null) {
            // If we have created a view, check its android:onClick
            checkOnClickListener(view, attrs);
        }

        return view;
    }

能看到吧,此時(shí)把根據(jù)name去生成不同的View媒佣,這樣就替換原來(lái)的View加入到布局中匕累。

從官方的App包我們能夠得到什么啟發(fā)?我們可以通過(guò)在這里做一個(gè)Factory默伍,做一次View的生成攔截欢嘿。實(shí)際上這種思路衰琐,已經(jīng)被用于換膚框架中,其中一種炼蹦,且個(gè)人認(rèn)為最好的流派羡宙。就是這樣設(shè)計(jì)的

當(dāng)然除了App包有這種操作,實(shí)際上在Activity里面也有一樣的設(shè)計(jì)框弛,不過(guò)是專(zhuān)門(mén)針對(duì)Fragment的辛辨。

Fragment標(biāo)簽的實(shí)例化

還記得開(kāi)篇的attach方法嗎?其中一行設(shè)置了當(dāng)前LayoutInflater的PrivateFactory

mWindow.getLayoutInflater().setPrivateFactory(this);

這個(gè)Factory也是有一個(gè)onCreateView的接口瑟枫,我們直接看看做了什么:

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

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

能看到在PrivateFactory攔截的正是fragment標(biāo)簽斗搞。接著就通過(guò)mFragments.onCreateView創(chuàng)建Fragment。關(guān)于Fragment慷妙,我之后專(zhuān)門(mén)用一篇文章聊聊僻焚。

正是因?yàn)镕ragment不是一個(gè)View,因此才需要做這種特殊處理膝擂。

AsyncLayoutInflater的性能優(yōu)化

在Android的渲染中虑啤,其實(shí)大部分的事情都在ui線程中完成。我們稍微思考其中的工作架馋,我們暫時(shí)只考慮Java可以輕易看見(jiàn)的地方狞山。做了反射,做了測(cè)量布局叉寂,渲染萍启,都在一個(gè)線程中。除此之外還有很多業(yè)務(wù)邏輯屏鳍,這樣會(huì)導(dǎo)致ui線程十分重量級(jí)勘纯。

為了解決這個(gè)問(wèn)題,官方也好钓瞭,各大廠商也好驳遵,都做十分巨大的努力去優(yōu)化這個(gè)ui渲染速度。

接下來(lái)AsyncLayoutInflater就是官方提供優(yōu)化工具山涡,實(shí)際上這是一個(gè)封裝好的異步LayoutInflater堤结,這樣就能降低ui線程的壓力。想法是好的鸭丛,實(shí)際上不過(guò)這個(gè)api設(shè)計(jì)上有點(diǎn)缺陷竞穷,導(dǎo)致有點(diǎn)雞肋。

我們看看怎么使用

        new AsyncLayoutInflater(Activity.this)
                .inflate(R.layout.async_layout, null, new AsyncLayoutInflater.OnInflateFinishedListener() {
                    @Override
                    public void onInflateFinished(View view, int resid, ViewGroup parent) {
                        setContentView(view);
                    }
                });

能看到當(dāng)異步實(shí)例化好View之后系吩,再去setContentView来庭。

我們直接看看構(gòu)造函數(shù),以及實(shí)例化的方法:

 public AsyncLayoutInflater(@NonNull Context context) {
        mInflater = new BasicInflater(context);
        mHandler = new Handler(mHandlerCallback);
        mInflateThread = InflateThread.getInstance();
    }

    @UiThread
    public void inflate(@LayoutRes int resid, @Nullable ViewGroup parent,
            @NonNull OnInflateFinishedListener callback) {
        if (callback == null) {
            throw new NullPointerException("callback argument may not be null!");
        }
        InflateRequest request = mInflateThread.obtainRequest();
        request.inflater = this;
        request.resid = resid;
        request.parent = parent;
        request.callback = callback;
        mInflateThread.enqueue(request);
    }

能看到實(shí)際上每一次調(diào)用穿挨,都會(huì)把一個(gè)所有需要實(shí)例化的request封裝起來(lái)月弛,丟進(jìn)mInflateThread實(shí)例化隊(duì)列中肴盏。

private static class InflateThread extends Thread {
        private static final InflateThread sInstance;
        static {
            sInstance = new InflateThread();
            sInstance.start();
        }

        public static InflateThread getInstance() {
            return sInstance;
        }

        private ArrayBlockingQueue<InflateRequest> mQueue = new ArrayBlockingQueue<>(10);
        private SynchronizedPool<InflateRequest> mRequestPool = new SynchronizedPool<>(10);

        // Extracted to its own method to ensure locals have a constrained liveness
        // scope by the GC. This is needed to avoid keeping previous request references
        // alive for an indeterminate amount of time, see b/33158143 for details
        public void runInner() {
            InflateRequest request;
            try {
                request = mQueue.take();
            } catch (InterruptedException ex) {
                // Odd, just continue
                Log.w(TAG, ex);
                return;
            }

            try {
                request.view = request.inflater.mInflater.inflate(
                        request.resid, request.parent, false);
            } catch (RuntimeException ex) {
                // Probably a Looper failure, retry on the UI thread
                Log.w(TAG, "Failed to inflate resource in the background! Retrying on the UI"
                        + " thread", ex);
            }
            Message.obtain(request.inflater.mHandler, 0, request)
                    .sendToTarget();
        }

        @Override
        public void run() {
            while (true) {
                runInner();
            }
        }

        public InflateRequest obtainRequest() {
            InflateRequest obj = mRequestPool.acquire();
            if (obj == null) {
                obj = new InflateRequest();
            }
            return obj;
        }

        public void releaseRequest(InflateRequest obj) {
            obj.callback = null;
            obj.inflater = null;
            obj.parent = null;
            obj.resid = 0;
            obj.view = null;
            mRequestPool.release(obj);
        }

        public void enqueue(InflateRequest request) {
            try {
                mQueue.put(request);
            } catch (InterruptedException e) {
                throw new RuntimeException(
                        "Failed to enqueue async inflate request", e);
            }
        }
    }

能看到這個(gè)線程的run方法中是一個(gè)死循環(huán),在這個(gè)死循環(huán)里面會(huì)不斷讀取mQueue的的請(qǐng)求帽衙,進(jìn)行一次次的實(shí)例化菜皂。一旦實(shí)例化完成之后,將會(huì)通過(guò)Handler通知回調(diào)完成厉萝。

通過(guò)AsyncLayoutInflater和正常的LayoutInflater比較就能清楚恍飘,雙方的差異是什么?

我們稍微瀏覽一下用于實(shí)例化操作真正的LayoutInflater

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

        BasicInflater(Context context) {
            super(context);
        }

        @Override
        public LayoutInflater cloneInContext(Context newContext) {
            return new BasicInflater(newContext);
        }

        @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) {
                    // In this case we want to let the base class take a crack
                    // at it.
                }
            }

            return super.onCreateView(name, attrs);
        }
    }
  • 首先AsyncLayoutInflater不能設(shè)置Factory谴垫,那么也就沒(méi)辦法創(chuàng)建AppCompat系列的View,也沒(méi)有辦法創(chuàng)建Fragment章母。
  • 其次AsyncLayoutInflater包含的阻塞隊(duì)列居然只有10個(gè),如果遇到RecyclerView這種多個(gè)子布局翩剪,超過(guò)了10個(gè)需要實(shí)例化的View乳怎,反而需要主線程的阻塞∏巴洌可以使用線程池子處理
  • 如果是setContentView的話蚪缀,本身就是處于ui線程的第一步,也就沒(méi)有必要異步恕出。
  • 甚至如果線程工作滿了询枚,可以把部分任務(wù)丟給主線程處理。
  • 而且在run中寫(xiě)一個(gè)死循環(huán)進(jìn)行讀取實(shí)例化任務(wù)浙巫,不合理金蜀。完全有更好的異步等到做法,如生產(chǎn)者消費(fèi)者模式狈醉。

處理這幾個(gè)問(wèn)題確實(shí)不難廉油,閱讀過(guò)源碼當(dāng)然知道怎么處理惠险,這里就不繼續(xù)贅述了苗傅。

總結(jié)

本文總結(jié)了Activity的分層,本質(zhì)上android從視圖上來(lái)看班巩,是一個(gè)DecorView包裹所有的View渣慕,我們繪制的內(nèi)容區(qū)域一般在R.id.content中。

LayoutInflater本質(zhì)上是通過(guò)一個(gè)構(gòu)造函數(shù)的緩存map來(lái)加速反射View的速度抱慌,同時(shí)merge壓縮層級(jí)原理就是越過(guò)本次View的生成逊桦,將內(nèi)部的view生成出來(lái)直接添加到父布局,因此如果include需要的父布局和外層一直抑进,就沒(méi)有必要在內(nèi)部也添加一個(gè)一模一樣的布局强经。

AsyncLayoutInflater本質(zhì)上就是把反射的工作丟給一個(gè)專(zhuān)門(mén)的線程去處理。但是其雞肋的設(shè)計(jì)導(dǎo)致使用場(chǎng)景不廣泛寺渗。

下一篇將和大家聊聊資源是怎么加載到我們的App的匿情,把本篇遺留的問(wèn)題解決了兰迫。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市炬称,隨后出現(xiàn)的幾起案子汁果,更是在濱河造成了極大的恐慌,老刑警劉巖玲躯,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件据德,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡跷车,警方通過(guò)查閱死者的電腦和手機(jī)棘利,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)朽缴,“玉大人赡译,你說(shuō)我怎么就攤上這事〔幻” “怎么了蝌焚?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)誓斥。 經(jīng)常有香客問(wèn)我只洒,道長(zhǎng),這世上最難降的妖魔是什么劳坑? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任毕谴,我火速辦了婚禮,結(jié)果婚禮上距芬,老公的妹妹穿的比我還像新娘涝开。我一直安慰自己,他們只是感情好框仔,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布舀武。 她就那樣靜靜地躺著,像睡著了一般离斩。 火紅的嫁衣襯著肌膚如雪银舱。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,370評(píng)論 1 302
  • 那天跛梗,我揣著相機(jī)與錄音寻馏,去河邊找鬼。 笑死核偿,一個(gè)胖子當(dāng)著我的面吹牛诚欠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼轰绵,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼家乘!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起藏澳,我...
    開(kāi)封第一講書(shū)人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤仁锯,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后翔悠,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體业崖,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年蓄愁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了双炕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡撮抓,死狀恐怖妇斤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情丹拯,我是刑警寧澤站超,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站乖酬,受9級(jí)特大地震影響死相,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜咬像,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一算撮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧县昂,春花似錦肮柜、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至狸驳,卻和暖如春预明,著一層夾襖步出監(jiān)牢的瞬間缩赛,已是汗流浹背耙箍。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留酥馍,地道東北人辩昆。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像旨袒,于是被迫代替她去往敵國(guó)和親汁针。 傳聞我的和親對(duì)象是個(gè)殘疾皇子术辐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

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