Android setContentView加載布局流程分析

我們?nèi)粘T陂_發(fā)中在Activity設(shè)置布局的時(shí)候都會(huì)用到setContentView方法,那setContentView到底是如何實(shí)現(xiàn)添加布局的呢,帶著這個(gè)疑問本篇文章來詳細(xì)查看源碼分析setContentView加載布局的流程,首先我們先看Activity的setContentView方法

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

在這里 getWindow()獲取的是mWindow這個(gè)變量就是Window,mWindow實(shí)例化就是PhoneWindow

  mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);

我們查看PhoneWindow中的setContentView方法

    @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
          第一次會(huì)走進(jìn)這里
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

 if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            最終獲取的mContentParent加載顯示布局
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();

    .....省略

我們看下mContentParent到底是怎么實(shí)例化的,進(jìn)入installDecor方法

        mForceDecorInstall = false;
        if (mDecor == null) {
            創(chuàng)建一個(gè)DecorView
            mDecor = generateDecor(-1);
            ...
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            創(chuàng)建mContentParent
            mContentParent = generateLayout(mDecor);

          ...設(shè)置一些屬性
        }

所以從這里可以看出Activity的布局加載是先創(chuàng)建一個(gè)PhoneWindow的窗口布局,然后在PhoneWindow內(nèi)又創(chuàng)建一個(gè)窗口布局DecorView就是個(gè)FrameLayout,嵌套了兩層,我們再看generateLayout方法

       ...獲取一些屬性

        int layoutResource;
        int features = getLocalFeatures();
        // System.out.println("Features: 0x" + Integer.toHexString(features));
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            layoutResource = R.layout.screen_swipe_dismiss;
            setCloseOnSwipeEnabled(true);
        } else 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 {
            獲取系統(tǒng)布局
            layoutResource = R.layout.screen_simple;
            // System.out.println("Simple!");
        }

        mDecor.startChanging();
        獲取到layoutResource,填充DecorView
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

        找一個(gè)叫做android.id.content的FrameLayout系統(tǒng)布局
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

    ....
        找到返回
        return contentParent;
    

這個(gè)方法主要是加載系統(tǒng)一些資源做了一系列的判斷,先獲取到layoutResource去填充DecorView,然后獲取系統(tǒng)布局中的FrameLayout的id,這個(gè)id就是從screen_simple布局中獲取的

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

        mDecorCaptionView = createDecorCaptionView(inflater);
         獲取到root
        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 {

            系統(tǒng)布局添加到DecorView中
            addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mContentRoot = (ViewGroup) root;
        initializeElevation();
    }
 public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
<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>

從源碼可以看出DecorView又添加了一個(gè)系統(tǒng)的布局,所以現(xiàn)在已經(jīng)嵌套第三層了,但是在系統(tǒng)布局中還有個(gè)FrameLayout,這樣就嵌套了四層,也就是說最終加載我們自己的布局也就是加到系統(tǒng)布局中的FrameLayout,如果有頭部布局就會(huì)加載系統(tǒng)的頭部布局,沒有則不加。下面畫的一張圖加以說明:

布局加載流程.png

接下來我們再來看下AppCompateActivity的setContentView源碼

  @Override
    public void setContentView(@LayoutRes int layoutResID) {
        initViewTreeOwners();
        getDelegate().setContentView(layoutResID);
    }
    public abstract void setContentView(@LayoutRes int resId);
    @NonNull
    public AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, this);
        }
        return mDelegate;
    }

跟進(jìn)去看下發(fā)現(xiàn)是一個(gè)抽象方法,抽象方法一定會(huì)有實(shí)現(xiàn)類,我們找到它的實(shí)現(xiàn)類,點(diǎn)擊進(jìn)入AppCompatDelegate.create方法,找到對應(yīng)的實(shí)現(xiàn)類

 @NonNull
    public static AppCompatDelegate create(@NonNull Activity activity,
            @Nullable AppCompatCallback callback) {
        return new AppCompatDelegateImpl(activity, callback);
    }

我們進(jìn)入AppCompatDelegateImpl中找到setContentView方法看看具體怎么實(shí)現(xiàn)的

   @Override
    public void setContentView(View v) {
        ensureSubDecor();
        ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        寫好的布局添加到contentParent中
        contentParent.addView(v);
        mAppCompatWindowCallback.getWrapped().onContentChanged();
    }

發(fā)現(xiàn)和Activity的寫法類似也是獲取android.R.id.content,我們進(jìn)入ensureSubDecor方法看看到底怎么處理的

    private void ensureSubDecor() {
        if (!mSubDecorInstalled) {
          獲取到mSubDecor 是一個(gè)ViewGroup
            mSubDecor = createSubDecor();

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

            applyFixedSizeWindow();
            onSubDecorInstalled(mSubDecor);
            mSubDecorInstalled = true;
           onCreateOptionsMenu
            PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
            if (!mDestroyed && (st == null || st.menu == null)) {
                invalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR);
            }
        }
    }

看看mSubDecor 是怎樣創(chuàng)建的,進(jìn)入createSubDecor方法

    private ViewGroup createSubDecor() {
    
      ....設(shè)置一些屬性

        這個(gè)方法是獲取PhoneWindow
        ensureWindow();
        這是獲取PhoneWindow內(nèi)創(chuàng)建的DecorView
        mWindow.getDecorView();

        final LayoutInflater inflater = LayoutInflater.from(mContext);
        ViewGroup subDecor = null;


        if (!mWindowNoTitle) {
            if (mIsFloating) {
                // If we're floating, inflate the dialog title decor
                subDecor = (ViewGroup) inflater.inflate(
                        R.layout.abc_dialog_title_material, null);

                // Floating windows can never have an action bar, reset the flags
                mHasActionBar = mOverlayActionBar = false;
            } else if (mHasActionBar) {
                /**
                 * This needs some explanation. As we can not use the android:theme attribute
                 * pre-L, we emulate it by manually creating a LayoutInflater using a
                 * ContextThemeWrapper pointing to actionBarTheme.
                 */
                TypedValue outValue = new TypedValue();
                mContext.getTheme().resolveAttribute(R.attr.actionBarTheme, outValue, true);

                Context themedContext;
                if (outValue.resourceId != 0) {
                    themedContext = new ContextThemeWrapper(mContext, outValue.resourceId);
                } else {
                    themedContext = mContext;
                }

                // Now inflate the view using the themed context and set it as the content view
                subDecor = (ViewGroup) LayoutInflater.from(themedContext)
                        .inflate(R.layout.abc_screen_toolbar, null);

                mDecorContentParent = (DecorContentParent) subDecor
                        .findViewById(R.id.decor_content_parent);
                mDecorContentParent.setWindowCallback(getWindowCallback());

                /**
                 * Propagate features to DecorContentParent
                 */
                if (mOverlayActionBar) {
                    mDecorContentParent.initFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
                }
                if (mFeatureProgress) {
                    mDecorContentParent.initFeature(Window.FEATURE_PROGRESS);
                }
                if (mFeatureIndeterminateProgress) {
                    mDecorContentParent.initFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
                }
            }
        } else {
            if (mOverlayActionMode) {
                   默認(rèn)會(huì)走到這里
                subDecor = (ViewGroup) inflater.inflate(
                        R.layout.abc_screen_simple_overlay_action_mode, null);
            } else {
                subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
            }
        }

        if (subDecor == null) {
            throw new IllegalArgumentException(
                    "AppCompat does not support the current theme features: { "
                            + "windowActionBar: " + mHasActionBar
                            + ", windowActionBarOverlay: "+ mOverlayActionBar
                            + ", android:windowIsFloating: " + mIsFloating
                            + ", windowActionModeOverlay: " + mOverlayActionMode
                            + ", windowNoTitle: " + mWindowNoTitle
                            + " }");
        }

     .... 一些兼容的判斷

        if (mDecorContentParent == null) {
            mTitleView = (TextView) subDecor.findViewById(R.id.title);
        }

        // Make the decor optionally fit system windows, like the window's decor
        ViewUtils.makeOptionalFitsSystemWindows(subDecor);
        獲取系統(tǒng)布局中的ContentFrameLayout
        final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
                R.id.action_bar_activity_content);
      獲取PhoneWindow中的系統(tǒng)布局FrameLayout
        final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
        if (windowContentView != null) {
         如果有子布局
            while (windowContentView.getChildCount() > 0) {
                 獲取PhoneWindow中的子布局
                final View child = windowContentView.getChildAt(0);
                刪除PhoneWindow中的根布局
                windowContentView.removeViewAt(0);
               子布局重新添加到contentView中
                contentView.addView(child);
            }

            // Change our content FrameLayout to use the android.R.id.content id.
            清除PhoneWindow的id
            windowContentView.setId(View.NO_ID);
            重新設(shè)置id
            contentView.setId(android.R.id.content);

            // The decorContent may have a foreground drawable set (windowContentOverlay).
            // Remove this as we handle it ourselves
            if (windowContentView instanceof FrameLayout) {
                ((FrameLayout) windowContentView).setForeground(null);
            }
        }

        重新添加到DecorView
        mWindow.setContentView(subDecor);

        contentView.setAttachListener(new ContentFrameLayout.OnAttachListener() {
            @Override
            public void onAttachedFromWindow() {}

            @Override
            public void onDetachedFromWindow() {
                dismissPopups();
            }
        });
        返回重新設(shè)置的一個(gè)id為android.R.id.content的ViewGroup
        return subDecor;
    }

從這個(gè)方法可以看出跟Activity中的原理邏輯是一樣的,只不過處理方式不太一樣,我們先來看下PhoneWindow是怎樣獲取的,進(jìn)入ensureWindow方法

    private void ensureWindow() {         
        if (mWindow == null && mHost instanceof Activity) {
       這個(gè)就很明了了其實(shí)就是獲取到Activity中的實(shí)例化的PhoneWindow對象
            attachToWindow(((Activity) mHost).getWindow());
        }
        if (mWindow == null) {
            throw new IllegalStateException("We have not been given a Window");
        }
    }

接下來我們看看subDecor是怎樣處理的,我們先看布局文件到底是咋搞的,查看abc_screen_simple_overlay_action_mode布局

<androidx.appcompat.widget.FitWindowsFrameLayout
        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:fitsSystemWindows="true">

    <include layout="@layout/abc_screen_content_include" />

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

</androidx.appcompat.widget.FitWindowsFrameLayout>
<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>

這個(gè)地方有點(diǎn)饒,我們詳細(xì)看下源碼發(fā)現(xiàn)id為action_bar_activity_content是一個(gè)ContentFrameLayout,所以根據(jù)源碼可以看出來在下面獲取的第一步contentView是ContentFrameLayout,然后第二步mWindow獲取的android.R.id.content是PhoneWindow中的系統(tǒng)布局id,第三步先判斷有沒有子布局,如果有的話則刪除自己的跟布局和id,替換成ContentFrameLayout根布局設(shè)置id為android.R.id.content,第四步修改好的subDecor布局添加到DecorView中,最后setContentView中我們自己設(shè)置的布局添加到android.R.id.content中

源碼分析完了,現(xiàn)在我們再看Activity和AppCompateActivity的區(qū)別在哪里
通過AppCompateActivity的源碼分析,我們在AppCompatDelegateImpl中發(fā)現(xiàn)實(shí)現(xiàn)了LayoutInflater.Factory2接口

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

既然實(shí)現(xiàn)接口一定會(huì)有事件,果然發(fā)現(xiàn)在AppCompatDelegateImpl的事件處理,LayoutInflater源碼分析本篇文章就不寫了,可自行查看,這里直接講一些,后續(xù)文章中會(huì)有補(bǔ)充。接者往下看

 @Override
    public void installViewFactory() {
        LayoutInflater layoutInflater = LayoutInflater.from(mContext);
        if (layoutInflater.getFactory() == null) {
          設(shè)置一個(gè)Factory事件
            LayoutInflaterCompat.setFactory2(layoutInflater, this);
        } else {
            if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
                Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
                        + " so we can not install AppCompat's");
            }
        }
    }

有個(gè)Factory對象就一定會(huì)走onCreateView方法,我們查看LayoutInflater中的具體實(shí)現(xiàn)方法

 private static class FactoryMerger implements Factory2 {
        private final Factory mF1, mF2;
        private final Factory2 mF12, mF22;

        FactoryMerger(Factory f1, Factory2 f12, Factory f2, Factory2 f22) {
            mF1 = f1;
            mF2 = f2;
            mF12 = f12;
            mF22 = f22;
        }

        @Nullable
        public View onCreateView(@NonNull String name, @NonNull Context context,
                @NonNull AttributeSet attrs) {
            View v = mF1.onCreateView(name, context, attrs);
            if (v != null) return v;
            return mF2.onCreateView(name, context, attrs);
        }

        @Nullable
        public View onCreateView(@Nullable View parent, @NonNull String name,
                @NonNull Context context, @NonNull AttributeSet attrs) {
            View v = mF12 != null ? mF12.onCreateView(parent, name, context, attrs)
                    : mF1.onCreateView(name, context, attrs);
            if (v != null) return v;
            return mF22 != null ? mF22.onCreateView(parent, name, context, attrs)
                    : mF2.onCreateView(name, context, attrs);
        }
    }

所以我們現(xiàn)在來查看AppCompatDelegateImpl中實(shí)現(xiàn)的onCreateView方法到底干了啥

  @Override
    public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        return createView(parent, name, context, attrs);
    }
    @Override
    public View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs) {
        關(guān)注此方法
        return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
                IS_PRE_LOLLIPOP,  );
       
    }

進(jìn)入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;

     ...省略

        View view = null;

       如果是TextView,則轉(zhuǎn)換成AppCompatTextView,以此類推
        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;
            case "ToggleButton":
                view = createToggleButton(context, attrs);
                verifyNotNull(view, name);
                break;
            default:
                view = createView(context, name, attrs);
        }
   
  ......省略

  if (view == null && originalContext != context) {
            這個(gè)方法是自定義控件,構(gòu)造方法通過反射進(jìn)行創(chuàng)建的
            view = createViewFromTag(context, name, attrs);
        }


        return view;
    }
    @NonNull
    protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {
        return new AppCompatTextView(context, attrs);
    }

從這里我們就看清楚了,其實(shí)它就是把我們在布局中設(shè)置的控件轉(zhuǎn)換成帶有 AppCompat兼容控件

最后總結(jié)一下:AppCompateActivity創(chuàng)建View的時(shí)候會(huì)被攔截,不會(huì)走系統(tǒng)的LayoutInflater的創(chuàng)建,就會(huì)替換一些系統(tǒng)兼容的View

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容