setContentView 布局加載流程

Android知識總結(jié)

一呢岗、繼承 Activity 的流程

整體流程圖

1.1请敦、PhoneWindow 的創(chuàng)建

首先ActivityThreadperformLaunchActivity方法中進(jìn)行activity的attach進(jìn)行參數(shù)綁定

    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ActivityInfo aInfo = r.activityInfo; //獲取AcitivityInfo對象
        if (r.packageInfo == null) { //獲取APK文件的描述類LoadeApk
            r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
                    Context.CONTEXT_INCLUDE_CODE);
        }

        ComponentName component = r.intent.getComponent(); //創(chuàng)建要啟動Activity的上下文環(huán)境
        if (component == null) {
            component = r.intent.resolveActivity(
                mInitialApplication.getPackageManager());
            r.intent.setComponent(component);
        }

        if (r.activityInfo.targetActivity != null) {
            component = new ComponentName(r.activityInfo.packageName,
                    r.activityInfo.targetActivity);
        }

        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader(); //用類加載器來創(chuàng)建該Activity的實例
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess();
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to instantiate activity " + component
                    + ": " + e.toString(), e);
            }
        }

        try {
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);//創(chuàng)建Application

            if (localLOGV) Slog.v(TAG, "Performing launch of " + r);
            if (localLOGV) Slog.v(
                    TAG, r + ": app=" + app
                    + ", appName=" + app.getPackageName()
                    + ", pkg=" + r.packageInfo.getPackageName()
                    + ", comp=" + r.intent.getComponent().toShortString()
                    + ", dir=" + r.packageInfo.getAppDir());

            if (activity != null) {
                CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
                Configuration config = new Configuration(mCompatConfiguration);
                if (r.overrideConfig != null) {
                    config.updateFrom(r.overrideConfig);
                }
                if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
                        + r.activityInfo.name + " with config " + config);
                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,
                        r.assistToken); //初始化Activity

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

                activity.mCalled = false;
                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState); //調(diào)用Activity的onCreate
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
                if (!activity.mCalled) {
                    throw new SuperNotCalledException(
                        "Activity " + r.intent.getComponent().toShortString() +
                        " did not call through to super.onCreate()");
                }
                r.activity = activity;
            }
            r.setState(ON_CREATE);
            synchronized (mResourcesManager) {
                mActivities.put(r.token, r);
            }

        } catch (SuperNotCalledException e) {
            throw e;

        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to start activity " + component
                    + ": " + e.toString(), e);
            }
        }

        return activity;
    }

我們再看Activity#attach中具體做了些什么事贸营。

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

        mFragments.attachHost(null /*parent*/);
        //創(chuàng)建抽象類 window 的實現(xiàn)類 PhoneWindow贾漏,對View進(jìn)行管理
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(mWindowControllerCallback);
        mWindow.setCallback(this); //設(shè)置回調(diào),向 Activity 分發(fā)點擊或狀態(tài)改變等事件
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
        if (info.uiOptions != 0) {
            mWindow.setUiOptions(info.uiOptions);
        }
        mUiThread = Thread.currentThread();

        mMainThread = aThread;
        mInstrumentation = instr;
        mToken = token;
        mAssistToken = assistToken;
        mIdent = ident;
        mApplication = application;
        mIntent = intent;
        mReferrer = referrer;
        mComponent = intent.getComponent();
        mActivityInfo = info;
        mTitle = title;
        mParent = parent;
        mEmbeddedID = id;
        mLastNonConfigurationInstances = lastNonConfigurationInstances;
        if (voiceInteractor != null) {
            if (lastNonConfigurationInstances != null) {
                mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
            } else {
                mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
                        Looper.myLooper());
            }
        }
        //給 Window 設(shè)置 WindowManager 對象
        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);
        mWindow.setPreferMinimalPostProcessing(
                (info.flags & ActivityInfo.FLAG_PREFER_MINIMAL_POST_PROCESSING) != 0);

        setAutofillOptions(application.getAutofillOptions());
        setContentCaptureOptions(application.getContentCaptureOptions());
    }

1.2具伍、setContentView 流程

首先進(jìn)入Activity#setContentView方法

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

上面通過getWindow()獲取Window的實現(xiàn)類PhoneWindow

   //在attach方法里面通過 PhoneWindow 創(chuàng)建
    private Window mWindow;

    public Window getWindow() {
        return mWindow;
    }

然后調(diào)用PhoneWindow#setContentView進(jìn)行渲染視圖

    private DecorView mDecor;
    ViewGroup mContentParent;

主要目的是創(chuàng)建 DecorView 拿到 Content

    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            //創(chuàng)建 DecorView 
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            //移除 ViewGroup 中所有的 View
            mContentParent.removeAllViews();
        }

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

1.2.1翅雏、創(chuàng)建 DecorView

    private void installDecor() {
        mForceDecorInstall = false;
        //當(dāng)mDecor為空時創(chuàng)建 DecorView
        if (mDecor == null) {
            //創(chuàng)建 DecorView
            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 為空時,用系統(tǒng)提供的layout初始化
            mContentParent = generateLayout(mDecor);
            ...
            }
        }
    }

當(dāng)mDecor == null時人芽,調(diào)用PhoneWindow#generateDecor進(jìn)行初始化

    protected DecorView generateDecor(int featureId) {
        Context context;
        if (mUseDecorContext) {
            Context applicationContext = getContext().getApplicationContext();
            if (applicationContext == null) {
                context = getContext();
            } else {
                context = new DecorContext(applicationContext, this);
                if (mTheme != -1) {
                    context.setTheme(mTheme);
                }
            }
        } else {
            context = getContext();
        }
        //創(chuàng)建 DecorView
        return new DecorView(context, featureId, this, getAttributes());
    }

接下來看generateLayout里面怎么初始化 mContentParent

 public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
 protected ViewGroup generateLayout(DecorView decor) {
        //設(shè)置主題
        TypedArray a = getWindowStyle();
        ....
        //系統(tǒng)提供的xml文件
        int layoutResource;
        int features = getLocalFeatures();
        // System.out.println("Features: 0x" + Integer.toHexString(features));
        if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleIconsDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_title_icons;
            }
            // XXX Remove this once action bar supports these features.
            removeFeature(FEATURE_ACTION_BAR);
            // System.out.println("Title Icons!");
        } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
                && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
            // Special case for a window with only a progress bar (and title).
            // XXX Need to have a no-title version of embedded windows.
            layoutResource = R.layout.screen_progress;
            // System.out.println("Progress!");
        } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
            // Special case for a window with a custom title.
            // If the window is floating, we need a dialog layout
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogCustomTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_custom_title;
            }
            // XXX Remove this once action bar supports these features.
            removeFeature(FEATURE_ACTION_BAR);
        } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
            // If no other features and not embedded, only need a title.
            // If the window is floating, we need a dialog layout
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
                layoutResource = a.getResourceId(
                        R.styleable.Window_windowActionBarFullscreenDecorLayout,
                        R.layout.screen_action_bar);
            } else {
                layoutResource = R.layout.screen_title;
            }
            // System.out.println("Title!");
        } 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();
       // 添加到 DecorView(FrameLayout)里面
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource););
        //獲取 contentParent (FrameLayout)
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
      .....
        return contentParent;
    }

再看DecorViewonResourcesLoaded方法望几,主要初始化根布局

    ViewGroup mContentRoot;

    void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
        if (mBackdropFrameRenderer != null) {
            loadBackgroundDrawablesIfNeeded();
            mBackdropFrameRenderer.onResourcesLoaded(
                    this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
                    mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
                    getCurrentColor(mNavigationColorViewState));
        }

        mDecorCaptionView = createDecorCaptionView(inflater);
        //獲取布局的 View
        final View root = inflater.inflate(layoutResource, null);
        if (mDecorCaptionView != null) {
            if (mDecorCaptionView.getParent() == null) {
                addView(mDecorCaptionView,
                        new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            }
            mDecorCaptionView.addView(root,
                    new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
        } else {

            // Put it below the color views.
            addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mContentRoot = (ViewGroup) root;
        initializeElevation();
    }

1.2.2、渲染到 mContentParent

執(zhí)行LayoutInflater里的inflate 方法

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

    //resource: 要加載的XML布局資源的ID
    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        //獲取Resources萤厅, Resources是安卓的資源管理類
        final Resources res = getContext().getResources();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                  + Integer.toHexString(resource) + ")");
        }
        //通過反射獲取編譯后 View
        View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
        if (view != null) {
            return view;
        }
        //解析xml
        XmlResourceParser parser = res.getLayout(resource);
        try {
            //添加到 root (mContentParent) 跟布局中
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }
    View tryInflatePrecompiled(@LayoutRes int resource, Resources res, @Nullable ViewGroup root,
        boolean attachToRoot) {
        if (!mUseCompiledView) {
            return null;
        }

        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate (precompiled)");

        // Try to inflate using a precompiled layout.
        String pkg = res.getResourcePackageName(resource);
        String layout = res.getResourceEntryName(resource);

        try {
            Class clazz = Class.forName("" + pkg + ".CompiledView", false, mPrecompiledClassLoader);
            Method inflater = clazz.getMethod(layout, Context.class, int.class);
            View view = (View) inflater.invoke(null, mContext, resource);

            if (view != null && root != null) {
                // We were able to use the precompiled inflater, but now we need to do some work to
                // attach the view to the root correctly.
                XmlResourceParser parser = res.getLayout(resource);
                try {
                    AttributeSet attrs = Xml.asAttributeSet(parser);
                    advanceToRootNode(parser);
                    ViewGroup.LayoutParams params = root.generateLayoutParams(attrs);

                    if (attachToRoot) {
                        root.addView(view, params);
                    } else {
                        view.setLayoutParams(params);
                    }
                } finally {
                    parser.close();
                }
            }

            return view;
        } catch (Throwable e) {
            if (DEBUG) {
                Log.e(TAG, "Failed to use precompiled view", e);
            }
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        return null;
    }

二橄抹、繼承 AppCompatActivity 的流程

進(jìn)入AppCompatActivity#setContentView方法

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

getDelegate()獲取AppCompatDelegate的實現(xiàn)類,具體相當(dāng)于代理的作用

    private AppCompatDelegate mDelegate;

    public AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, this);
        }
        return mDelegate;
    }

最終會調(diào)用AppCompatDelegate的實現(xiàn)類AppCompatDelegateImpl里面的setContentView

    @Override
    public void setContentView(View v) {
        ensureSubDecor();
        ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        contentParent.addView(v);
        mAppCompatWindowCallback.getWrapped().onContentChanged();
    }

    public void setContentView(int resId) {
        //步驟一
        // 創(chuàng)建并初始化DecorView
        ensureSubDecor();
        // 找到subDecor中的content id
        ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
        // 移除content中的所有view
        contentParent.removeAllViews();
        //步驟二
        // 通過LayoutInflater加載用戶定義的布局惕味,內(nèi)部實現(xiàn)是通過獲得類名反射出每一個View楼誓,我們可以通過setFactory2()接管創(chuàng)建View的過程來自定義LayoutInflater
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        // 內(nèi)容改變回調(diào)
        mAppCompatWindowCallback.getWrapped().onContentChanged();
    }

    @Override
    public void setContentView(View v, ViewGroup.LayoutParams lp) {
        ensureSubDecor();
        ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        contentParent.addView(v, lp);
        mAppCompatWindowCallback.getWrapped().onContentChanged();
    }

    @Override
    public void addContentView(View v, ViewGroup.LayoutParams lp) {
        ensureSubDecor();
        ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
        contentParent.addView(v, lp);
        mAppCompatWindowCallback.getWrapped().onContentChanged();
    }

createSubDecor方法, 根據(jù)應(yīng)用的樣式主題(Theme)設(shè)置根布局DecorView的樣式, 并執(zhí)行初始化. 當(dāng)未含有AppCompatTheme_windowActionBar屬性時, 則認(rèn)為主題未設(shè)置, 并拋出異常IllegalStateException.

2.1、步驟一

    private void ensureSubDecor() {
        if (!mSubDecorInstalled) {
            //創(chuàng)建mSubDecor
            mSubDecor = createSubDecor();

            // If a title was set before we installed the decor, propagate it now
            CharSequence title = getTitle();
            if (!TextUtils.isEmpty(title)) {
                if (mDecorContentParent != null) {
                    mDecorContentParent.setWindowTitle(title);
                } else if (peekSupportActionBar() != null) {
                    peekSupportActionBar().setWindowTitle(title);
                } else if (mTitleView != null) {
                    mTitleView.setText(title);
                }
            }
            //窗口大小設(shè)置
            applyFixedSizeWindow();
            onSubDecorInstalled(mSubDecor);
            mSubDecorInstalled = true; //是否初始化的標(biāo)志位
            PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
            if (!mIsDestroyed && (st == null || st.menu == null)) {
                invalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR);
            }
        }
    }
    private ViewGroup createSubDecor(){
        //設(shè)置屬性主題
        TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
        //這個錯誤是不是曾經(jīng)見到過名挥,如果用的Theme不是AppCompatTheme的就會報錯
        if (!a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) {
            a.recycle();
            throw new IllegalStateException(
                    "You need to use a Theme.AppCompat theme (or descendant) with this activity.");
        }

        if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) {
            requestWindowFeature(Window.FEATURE_NO_TITLE);
        } else if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBar, false)) {
            // Don't allow an action bar if there is no title.
            requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR);
        }
        if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBarOverlay, false)) {
            requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
        }
        if (a.getBoolean(R.styleable.AppCompatTheme_windowActionModeOverlay, false)) {
            requestWindowFeature(FEATURE_ACTION_MODE_OVERLAY);
        }
        mIsFloating = a.getBoolean(R.styleable.AppCompatTheme_android_windowIsFloating, false);
        a.recycle();
        //1慌随、從Activity 拿 PhoneWindow
        ensureWindow();
        // 2、獲取DecorView
        mWindow.getDecorView();
        ...
        //  通過LayoutInflater inflate subDecor的視圖躺同,這里有好幾種,我挑了其中一個繼續(xù)分析
        LayoutInflater inflater = LayoutInflater.from(mContext);
        subDecor = (ViewGroup) inflater.inflate(R.layout.abc_dialog_title_material, null);
        //我們得到了DecorView丸逸,而DecorView里面已經(jīng)有了content內(nèi)容蹋艺,現(xiàn)在這里新建了subDecor,所以需要把content的視圖migrate黄刚,之后再重新設(shè)置 content id 
        final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(R.id.action_bar_activity_content);
        ....
        final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
        if (windowContentView != null) {
            // 也許已經(jīng)有Views加載到windowContentView中捎谨,需要把這些視圖重新添加到contentView中,并設(shè)置id
            while (windowContentView.getChildCount() > 0) {
                final View child = windowContentView.getChildAt(0);
                windowContentView.removeViewAt(0);
                contentView.addView(child);
            }
            //將原始的 content id 置為 NO_ID
            windowContentView.setId(View.NO_ID);
            //subDecerView R.id.action_bar_activity_content --> 置為 content
            contentView.setId(android.R.id.content);
        }
        // 用subDecor填充Window的內(nèi)容視圖
        mWindow.setContentView(subDecor);
        contentView.setAttachListener(new ContentFrameLayout.OnAttachListener() {
            @Override
            public void onAttachedFromWindow() {}

            @Override
            public void onDetachedFromWindow() {
                dismissPopups();
            }
        });

        return subDecor;
    }
  • 1
    private void ensureWindow() {
        // We lazily fetch the Window for Activities, to allow DayNight to apply in
        // attachBaseContext
        if (mWindow == null && mHost instanceof Activity) {
            attachToWindow(((Activity) mHost).getWindow());
        }
        if (mWindow == null) {
            throw new IllegalStateException("We have not been given a Window");
        }
    }
  • 2
    執(zhí)行PhoneWindow#getDecorView方法
    public final @NonNull View getDecorView() {
        if (mDecor == null || mForceDecorInstall) {
            //初始化 DecorView
            installDecor();
        }
        return mDecor;
    }

下面具體流程Activity的流程

private void installDecor() {
  ...
}

窗口大小設(shè)置

    private void applyFixedSizeWindow() {
        //加載布局
        ContentFrameLayout cfl = (ContentFrameLayout) mSubDecor.findViewById(android.R.id.content);

        //獲取 DecorView
        final View windowDecor = mWindow.getDecorView();

         //設(shè)置ContentFrameLayout 布局的間距
        cfl.setDecorPadding(windowDecor.getPaddingLeft(),
                windowDecor.getPaddingTop(), windowDecor.getPaddingRight(),
                windowDecor.getPaddingBottom());

        TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
        a.getValue(R.styleable.AppCompatTheme_windowMinWidthMajor, cfl.getMinWidthMajor());
        a.getValue(R.styleable.AppCompatTheme_windowMinWidthMinor, cfl.getMinWidthMinor());

        if (a.hasValue(R.styleable.AppCompatTheme_windowFixedWidthMajor)) {
            a.getValue(R.styleable.AppCompatTheme_windowFixedWidthMajor,
                    cfl.getFixedWidthMajor());
        }
        if (a.hasValue(R.styleable.AppCompatTheme_windowFixedWidthMinor)) {
            a.getValue(R.styleable.AppCompatTheme_windowFixedWidthMinor,
                    cfl.getFixedWidthMinor());
        }
        if (a.hasValue(R.styleable.AppCompatTheme_windowFixedHeightMajor)) {
            a.getValue(R.styleable.AppCompatTheme_windowFixedHeightMajor,
                    cfl.getFixedHeightMajor());
        }
        if (a.hasValue(R.styleable.AppCompatTheme_windowFixedHeightMinor)) {
            a.getValue(R.styleable.AppCompatTheme_windowFixedHeightMinor,
                    cfl.getFixedHeightMinor());
        }
        a.recycle();

        cfl.requestLayout();
    }

通過我們設(shè)置的style選出一個布局文件,這一步好像在installDecor中已經(jīng)做過了涛救,這樣好像重復(fù)了畏邢,為什么有這樣的一個重復(fù)?有這樣一個重復(fù)是為了不影響原來的代碼的同時,把一部分對style處理的邏輯轉(zhuǎn)移到AppCompatDelegateImpl中检吆,例如對windowTitle的隱藏與顯示舒萎,這里可看出來設(shè)計師的設(shè)計,通過下面的學(xué)習(xí)慢慢體會蹭沛,先看一下這個布局文件:

<androidx.appcompat.widget.FitWindowsLinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/action_bar_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:fitsSystemWindows="true">
 
    <androidx.appcompat.widget.ViewStubCompat
        android:id="@+id/action_mode_bar_stub"
        android:inflatedId="@+id/action_mode_bar"
        android:layout="@layout/abc_action_mode_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
 
    <include layout="@layout/abc_screen_content_include" />
 
</androidx.appcompat.widget.FitWindowsLinearLayout>

abc_screen_content_include的布局文件:

<merge xmlns:android="http://schemas.android.com/apk/res/android">
 
    <androidx.appcompat.widget.ContentFrameLayout
            android:id="@id/action_bar_activity_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:foregroundGravity="fill_horizontal|top"
            android:foreground="?android:attr/windowContentOverlay" />
 
</merge>

把這個布局文件inflate成view賦值給subDecor臂寝,subDecor是一個ViewGroup:

2.2、步驟二摊灭, View 創(chuàng)建

執(zhí)行LayoutInflater#inflate方法

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

標(biāo)簽

    private static final String TAG_MERGE = "merge";
    private static final String TAG_INCLUDE = "include";
    private static final String TAG_1995 = "blink";
    private static final String TAG_REQUEST_FOCUS = "requestFocus";
    private static final String TAG_TAG = "tag";

初始化布局

    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            final Context inflaterContext = mContext;
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            mConstructorArgs[0] = inflaterContext;
            View result = root;
              ...
                if (TAG_MERGE.equals(name)) {
                    //root == null || attachToRoot == true 拋出異常
                    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 {
                    // 通過反射創(chuàng)建View  --- 布局的rootView
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    ViewGroup.LayoutParams params = null;

                    if (root != null) {
                        if (DEBUG) {
                            System.out.println("Creating params from root: " +
                                    root);
                        }
                        // 從 xml 中解析我們布局中根布局設(shè)值得大小
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            // 設(shè)置View在父布局下Params
                            temp.setLayoutParams(params);
                        }
                    }

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

                    // 遍歷子節(jié)點
                    rInflateChildren(parser, temp, attrs, true);
                    //如果Root存在并且attachToRoot為true咆贬,即與父View綁定
                    //這里在解析的同時,就會將其添加至父View上
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }
                    //如果父Viewwe為null或者沒有綁定父View都會將當(dāng)前解析的View返回帚呼,大小有自己確定
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }
            }
            return result;
        }
    }
  • 總結(jié):由上可的分為三種情況
LayoutInflate的參數(shù)的作用
// 方式一:將布局添加成功
View view = inflater.inflate(R.layout.inflate_layout, ll, true);

// 方式二:報錯掏缎,一個View只能有一個父親(The specified child already has a parent.)
View view = inflater.inflate(R.layout.inflate_layout, ll, true); // 已經(jīng)addView
ll.addView(view);

// 方式三:布局成功,第三個參數(shù)為false
// 目的:想要 inflate_layout 的根節(jié)點的屬性(寬高)有效煤杀,又不想讓其處于某一個容器中
View view = inflater.inflate(R.layout.inflate_layout, ll, false);
ll.addView(view);

// 方式四:root = null眷蜈,這個時候不管第三個參數(shù)是什么,顯示效果一樣
// inflate_layout 的根節(jié)點的屬性(寬高)設(shè)置無效怜珍,只是包裹子View端蛆,
// 但是子View(Button)有效,因為Button是出于容器下的
View view = inflater.inflate(R.layout.inflate_layout, null, false);
ll.addView(view);

由上面可得當(dāng)父布局 root != null酥泛,的時候我們設(shè)置的布局才有效

調(diào)用createViewFromTag方法創(chuàng)建 View

    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);
            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    if (-1 == name.indexOf('.')) {
                        //(1)今豆、是否是sdk里面的控件,如:LinearLayout
                        view = onCreateView(context, parent, name, attrs);
                    } else {
                        //(2)柔袁、自定義View 
                        //如:name = androidx.constraintlayout.widget.ConstraintLayout
                        view = createView(context, name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }

            return view;
        } 
        ...
    }
  • (1)
    會執(zhí)行PhoneLayoutInflater里面的 onCreateView方法
    private static final String[] sClassPrefixList = {
        "android.widget.",
        "android.webkit.",
        "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);
    }

接下來執(zhí)行LayoutInflater#createView方法

    public final View createView(@NonNull Context viewContext, @NonNull String name,
            @Nullable String prefix, @Nullable AttributeSet attrs)
          // 具體實現(xiàn)見下:
    }
  • (2)
    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) {
                //獲取全類名呆躲,通過反射創(chuàng)建 View 對象
                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);
                    }
                }
                //獲取構(gòu)造方法
                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 {
                //執(zhí)行構(gòu)造方法,創(chuàng)建 View
                final View view = constructor.newInstance(args);
                if (view instanceof ViewStub) {
                    // Use the same context when inflating ViewStub later.
                    final ViewStub viewStub = (ViewStub) view;
                    viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
                }
                //返回構(gòu)造的view
                return view;
            } finally {
                mConstructorArgs[0] = lastContext;
            }
            ...
        } 
    }

接下來看inflate中調(diào)用的rInflateChildren方法

    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) {
                     // include 不能作為根結(jié)點
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {
                // merge 只能作為根結(jié)點
                throw new InflateException("<merge /> must be the root element");
            } else {
                //創(chuàng)建 View
                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();
        }
    }

上面方法所用到的標(biāo)簽

    //優(yōu)化布局捶索,必須作為rootView
    private static final String TAG_MERGE = "merge";
    //不能作為 root_View
    private static final String TAG_INCLUDE = "include";
    private static final String TAG_1995 = "blink";
    private static final String TAG_REQUEST_FOCUS = "requestFocus";
    private static final String TAG_TAG = "tag";
    private static final String ATTR_LAYOUT = "layout";
  • include 布局
    private void parseInclude(XmlPullParser parser, Context context, View parent,
            AttributeSet attrs) throws XmlPullParserException, IOException {
        int type;
        //判斷 Include標(biāo)簽是否在 ViewGroup容器之內(nèi)插掂,因為 include 標(biāo)簽只能存在于 ViewGroup 容器之內(nèi)
        if (!(parent instanceof ViewGroup)) {
            throw new InflateException("<include /> can only be used inside of a ViewGroup");
        }
        //提取出 include 的 thme 屬性,如果設(shè)置了 them 屬性腥例,那么include 包裹的View 設(shè)置的 theme 將會無效
        final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
        final int themeResId = ta.getResourceId(0, 0);
        final boolean hasThemeOverride = themeResId != 0;
        if (hasThemeOverride) {
            context = new ContextThemeWrapper(context, themeResId);
        }
        ta.recycle();

            //如果這個屬性是指向主題中的某個屬性辅甥,我們必須設(shè)法得到主題中l(wèi)ayout 的資源標(biāo)識符
            //先獲取 layout 屬性(資源 id)是否設(shè)置
        int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);
        if (layout == 0) {
            //如果沒直接設(shè)置布局的資源 id,那么就檢索?attr/name這一類的 layout 屬性
            final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
            if (value == null || value.length() <= 0) {
                throw new InflateException("You must specify a layout in the"
                    + " include tag: <include layout=\"@layout/layoutID\" />");
            }

            //從  ?attr/name 這一類的屬性中燎竖,獲取布局屬性  
            layout = context.getResources().getIdentifier(
                value.substring(1), "attr", context.getPackageName());

        }
       //這個布局資源也許存在主題屬性中璃弄,所以需要去主題屬性中解析
        if (mTempValue == null) {
            mTempValue = new TypedValue();
        }
        if (layout != 0 && context.getTheme().resolveAttribute(layout, mTempValue, true)) {
            layout = mTempValue.resourceId;
        }

        if (layout == 0) {
            final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
            throw new InflateException("You must specify a valid layout "
                + "reference. The layout ID " + value + " is not valid.");
        }

        final View precompiled = tryInflatePrecompiled(layout, context.getResources(),
            (ViewGroup) parent, /*attachToRoot=*/true);
        if (precompiled == null) {
            final XmlResourceParser childParser = context.getResources().getLayout(layout);

            try {
                final AttributeSet childAttrs = Xml.asAttributeSet(childParser);

                while ((type = childParser.next()) != XmlPullParser.START_TAG &&
                    type != XmlPullParser.END_DOCUMENT) {
                    // Empty.
                }

                if (type != XmlPullParser.START_TAG) {
                    throw new InflateException(getParserStateDescription(context, childAttrs)
                            + ": No start tag found!");
                }

                final String childName = childParser.getName();

                if (TAG_MERGE.equals(childName)) {
                    // The <merge> tag doesn't support android:theme, so
                    //解析 Meger 標(biāo)簽
                    rInflate(childParser, parent, context, childAttrs, false);
                } else {
                    //根據(jù) name名稱來創(chuàng)建View
                    final View view = createViewFromTag(parent, childName,
                        context, childAttrs, hasThemeOverride);
                    final ViewGroup group = (ViewGroup) parent;
                    //獲取 View 的 id 和其 Visiable 屬性
                    final TypedArray a = context.obtainStyledAttributes(
                        attrs, R.styleable.Include);
                    //include 布局的 id
                    final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
                    final int visibility = a.getInt(R.styleable.Include_visibility, -1);
                    a.recycle();
                    //設(shè)置 include 包裹內(nèi)容的通用 Params
                    ViewGroup.LayoutParams params = null;
                    try {//include 的 params 
                        params = group.generateLayoutParams(attrs);
                    } catch (RuntimeException e) {
                        // Ignore, just fail over to child attrs.
                    }
                    if (params == null) { //子視圖的 params 
                        params = group.generateLayoutParams(childAttrs);
                    }
                    //include設(shè)置了params用include的,否則用子視圖的
                    view.setLayoutParams(params);

                    // 解析子標(biāo)簽
                    rInflateChildren(childParser, view, childAttrs, true);
                    //如果不等于 View.NO_ID 重新設(shè)置 ID
                    if (id != View.NO_ID) {
                        //將include的id設(shè)置給view
                        view.setId(id);
                    }
                    // 加載include內(nèi)容時构回,需要直接設(shè)置其 可見性
                    switch (visibility) {
                        case 0:
                            view.setVisibility(View.VISIBLE);
                            break;
                        case 1:
                            view.setVisibility(View.INVISIBLE);
                            break;
                        case 2:
                            view.setVisibility(View.GONE);
                            break;
                    }
                    //添加至父容器中
                    group.addView(view);
                }
            } finally {
                childParser.close();
            }
        }
        LayoutInflater.consumeChildElements(parser);
    }
解析 Include 標(biāo)簽
整體流程圖
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末夏块,一起剝皮案震驚了整個濱河市疏咐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌脐供,老刑警劉巖浑塞,帶你破解...
    沈念sama閱讀 221,406評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異政己,居然都是意外死亡酌壕,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評論 3 398
  • 文/潘曉璐 我一進(jìn)店門匹颤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來仅孩,“玉大人,你說我怎么就攤上這事印蓖×赡剑” “怎么了?”我有些...
    開封第一講書人閱讀 167,815評論 0 360
  • 文/不壞的土叔 我叫張陵赦肃,是天一觀的道長溅蛉。 經(jīng)常有香客問我,道長他宛,這世上最難降的妖魔是什么船侧? 我笑而不...
    開封第一講書人閱讀 59,537評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮厅各,結(jié)果婚禮上镜撩,老公的妹妹穿的比我還像新娘。我一直安慰自己队塘,他們只是感情好袁梗,可當(dāng)我...
    茶點故事閱讀 68,536評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著憔古,像睡著了一般遮怜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鸿市,一...
    開封第一講書人閱讀 52,184評論 1 308
  • 那天锯梁,我揣著相機(jī)與錄音,去河邊找鬼焰情。 笑死陌凳,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的内舟。 我是一名探鬼主播合敦,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼谒获!你這毒婦竟也來了蛤肌?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,668評論 0 276
  • 序言:老撾萬榮一對情侶失蹤批狱,失蹤者是張志新(化名)和其女友劉穎裸准,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體赔硫,經(jīng)...
    沈念sama閱讀 46,212評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡炒俱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,299評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了爪膊。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片权悟。...
    茶點故事閱讀 40,438評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖推盛,靈堂內(nèi)的尸體忽然破棺而出峦阁,到底是詐尸還是另有隱情,我是刑警寧澤耘成,帶...
    沈念sama閱讀 36,128評論 5 349
  • 正文 年R本政府宣布榔昔,位于F島的核電站,受9級特大地震影響瘪菌,放射性物質(zhì)發(fā)生泄漏撒会。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,807評論 3 333
  • 文/蒙蒙 一师妙、第九天 我趴在偏房一處隱蔽的房頂上張望诵肛。 院中可真熱鬧,春花似錦默穴、人聲如沸怔檩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,279評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽珠洗。三九已至,卻和暖如春若专,著一層夾襖步出監(jiān)牢的瞬間许蓖,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,395評論 1 272
  • 我被黑心中介騙來泰國打工调衰, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留膊爪,地道東北人。 一個月前我還...
    沈念sama閱讀 48,827評論 3 376
  • 正文 我出身青樓嚎莉,卻偏偏與公主長得像米酬,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子趋箩,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,446評論 2 359

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