Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 從setContentView方法分析Android加載布局流程

從setContentView方法分析Android加載布局流程

編輯:關於Android編程

PS一句:當初你所逃避的問題終會在未來的某一天重新出現在你面前,因此,當你第一次遇到它時,請不要逃避。

相信很多初學者對XML布局怎麼加載到Activity上並且顯示在手機屏幕上很好奇吧?今天我們就從經常使用的方法

setContentView來從源碼分析一下XML布局是怎麼加載到當前Activity上的。

Activity#setContentView

我們知道,Activity是在onCreate方法中使用setContentView方法來加載布局的,那麼它內部的源碼是怎麼實現的呢?

Setp 1

處於好奇,我們進入了Activity的源碼,找到setContentView方法如下:

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

setContentView方法實現很簡單,裡面調用兩個方法,我們這裡分析第一個方法:

getWindow().setContentView(layoutResID);

getWindow()得到一個Window對象 mWindow ,Window是一個抽象類,用來描述Activity視圖最頂端的窗口顯示和行為操作。Window是

一個抽象類,那麼裡面的setContentView(layoutResID)是一個抽象方法,並沒有具體的實現,既然Window是一個抽象類,那麼

在Activity裡面就有一個Window抽象類的實現,我們查找代碼發現 mWindow對象賦值方法如下:

mWindow = PolicyManager.makeNewWindow(this);

Step 2

Look the fuck resoure code,繼續看看 PolicyManager類

public final class More PolicyManager {
30    private static final String POLICY_IMPL_CLASS_NAME =
31        "com.android.internal.policy.impl.Policy";
32
33    private static final IPolicy sPolicy;
34
35    static {
36        // Pull in the actual implementation of the policy at run-time
37        try {
38            Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
39            sPolicy = (IPolicy)policyClass.newInstance();
40        } catch (ClassNotFoundException ex) {
41            throw new RuntimeException(
42                    POLICY_IMPL_CLASS_NAME + " could not be loaded", ex);
43        } catch (InstantiationException ex) {
44            throw new RuntimeException(
45                    POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
46        } catch (IllegalAccessException ex) {
47            throw new RuntimeException(
48                    POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
49        }
50    }
51
52    // Cannot instantiate this class
53    private More PolicyManager() {}
54
55    // The static methods to spawn new policy-specific objects
56    public static Window More makeNewWindow(Context context) {
57        return sPolicy.makeNewWindow(context);
58    }

地 57 行 sPolicy對象是有第 38,39行通過類路徑”com.android.internal.policy.impl.Policy“生成的,那麼我們在源碼中找到 Policy類,在此類中找到了如下方法:

public Window makeNewWindow(Context context) {
        return new PhoneWindow(context);
    }

由此可見,我們終於找到Activity類中的 mWindow對象的實現類了,就是PhoneWindow類。

Setp 3

從源碼發現,PhoneWindow類就是抽象類Window的實現類。那麼 setContentView方法的實現也在這個類裡面了,

到PhoneWindow類發現,setContentView方法實現如下:

 @Override
    public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

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

首先判斷mContentParent是否為null,mContentParent是什麼呢?接下來會分析,一開始條件成立,進入installDecor()方法。

Setp 4

到installDecor()方法裡,實現如下:

    private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor();
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        }
        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) {
                mDecorContentParent = decorContentParent;
                mDecorContentParent.setWindowCallback(getCallback());
                if (mDecorContentParent.getTitle() == null) {
                    mDecorContentParent.setWindowTitle(mTitle);
                }

                final int localFeatures = getLocalFeatures();
                for (int i = 0; i < FEATURE_MAX; i++) {
                    if ((localFeatures & (1 << i)) != 0) {
                        mDecorContentParent.initFeature(i);
                    }
                }

                mDecorContentParent.setUiOptions(mUiOptions);

                if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) != 0 ||
                        (mIconRes != 0 && !mDecorContentParent.hasIcon())) {
                    mDecorContentParent.setIcon(mIconRes);
                } else if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) == 0 &&
                        mIconRes == 0 && !mDecorContentParent.hasIcon()) {
                    mDecorContentParent.setIcon(
                            getContext().getPackageManager().getDefaultActivityIcon());
                    mResourcesSetFlags |= FLAG_RESOURCE_SET_ICON_FALLBACK;
                }
                if ((mResourcesSetFlags & FLAG_RESOURCE_SET_LOGO) != 0 ||
                        (mLogoRes != 0 && !mDecorContentParent.hasLogo())) {
                    mDecorContentParent.setLogo(mLogoRes);
                }

                // Invalidate if the panel menu hasn't been created before this.
                // Panel menu invalidation is deferred avoiding application onCreateOptionsMenu
                // being called in the middle of onCreate or similar.
                // A pending invalidation will typically be resolved before the posted message
                // would run normally in order to satisfy instance state restoration.
                PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
                if (!isDestroyed() && (st == null || st.menu == null)) {
                    invalidatePanelMenu(FEATURE_ACTION_BAR);
                }
            } else {
                mTitleView = (TextView)findViewById(R.id.title);
                if (mTitleView != null) {
                    mTitleView.setLayoutDirection(mDecor.getLayoutDirection());
                    if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
                        View titleContainer = findViewById(
                                R.id.title_container);
                        if (titleContainer != null) {
                            titleContainer.setVisibility(View.GONE);
                        } else {
                            mTitleView.setVisibility(View.GONE);
                        }
                        if (mContentParent instanceof FrameLayout) {
                            ((FrameLayout)mContentParent).setForeground(null);
                        }
                    } else {
                        mTitleView.setText(mTitle);
                    }
                }
            }

            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)) {
                if (mTransitionManager == null) {
                    final int transitionRes = getWindowStyle().getResourceId(
                            R.styleable.Window_windowContentTransitionManager,
                            0);
                    if (transitionRes != 0) {
                        final TransitionInflater inflater = TransitionInflater.from(getContext());
                        mTransitionManager = inflater.inflateTransitionManager(transitionRes,
                                mContentParent);
                    } else {
                        mTransitionManager = new TransitionManager();
                    }
                }

                mEnterTransition = getTransition(mEnterTransition, null,
                        R.styleable.Window_windowEnterTransition);
                mReturnTransition = getTransition(mReturnTransition, USE_DEFAULT_TRANSITION,
                        R.styleable.Window_windowReturnTransition);
                mExitTransition = getTransition(mExitTransition, null,
                        R.styleable.Window_windowExitTransition);
                mReenterTransition = getTransition(mReenterTransition, USE_DEFAULT_TRANSITION,
                        R.styleable.Window_windowReenterTransition);
                mSharedElementEnterTransition = getTransition(mSharedElementEnterTransition, null,
                        R.styleable.Window_windowSharedElementEnterTransition);
                mSharedElementReturnTransition = getTransition(mSharedElementReturnTransition,
                        USE_DEFAULT_TRANSITION,
                        R.styleable.Window_windowSharedElementReturnTransition);
                mSharedElementExitTransition = getTransition(mSharedElementExitTransition, null,
                        R.styleable.Window_windowSharedElementExitTransition);
                mSharedElementReenterTransition = getTransition(mSharedElementReenterTransition,
                        USE_DEFAULT_TRANSITION,
                        R.styleable.Window_windowSharedElementReenterTransition);
                if (mAllowEnterTransitionOverlap == null) {
                    mAllowEnterTransitionOverlap = getWindowStyle().getBoolean(
                            R.styleable.Window_windowAllowEnterTransitionOverlap, true);
                }
                if (mAllowReturnTransitionOverlap == null) {
                    mAllowReturnTransitionOverlap = getWindowStyle().getBoolean(
                            R.styleable.Window_windowAllowReturnTransitionOverlap, true);
                }
                if (mBackgroundFadeDurationMillis < 0) {
                    mBackgroundFadeDurationMillis = getWindowStyle().getInteger(
                            R.styleable.Window_windowTransitionBackgroundFadeDuration,
                            DEFAULT_BACKGROUND_FADE_DURATION_MS);
                }
                if (mSharedElementsUseOverlay == null) {
                    mSharedElementsUseOverlay = getWindowStyle().getBoolean(
                            R.styleable.Window_windowSharedElementsUseOverlay, true);
                }
            }
        }
    }

在代碼的第 3 行我們看到 mDecor = generateDecor();方法調用,繼續跳進 generateDecor()方法:

protected DecorView generateDecor() {
        return new DecorView(getContext(), -1);
    }

這裡生成一個DecorView對象,DecorView是PhoneWindow類的內部類,繼承自FrameLayout。到目前為止,setContentView

方法裡生成一個FrameLayout類型的DecorView組件。

Step 5

繼續分析代碼,看第 11 行:

 mContentParent = generateLayout(mDecor);

把 DecorView 對象 mDecor 作為參數傳遞給 generateLayout方法得到 mContentParent。generateLayout()方法中的代碼實現如下:

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

        TypedArray a = getWindowStyle();

        .........

        /**以下這些是Activity 窗口屬性特征的設置*/
        //窗口是否浮動,一般用於Dialog窗口是否浮動:是否顯示在布局的正中間。
        mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
        int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
                & (~getForcedWindowFlags());
        if (mIsFloating) {
            setLayout(WRAP_CONTENT, WRAP_CONTENT);
            setFlags(0, flagsToUpdate);
        } else {
            setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
        }
        //設置窗口是否支持標題欄,隱藏顯示標題欄操作在此處。
        if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
            requestFeature(FEATURE_NO_TITLE);
        } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
            // Don't allow an action bar if there is no title.
            requestFeature(FEATURE_ACTION_BAR);
        }
        //ActionBar導航欄是否不占布局空間疊加顯示在當前窗口之上。
        if (a.getBoolean(R.styleable.Window_windowActionBarOverlay, false)) {
            requestFeature(FEATURE_ACTION_BAR_OVERLAY);
        }

        if (a.getBoolean(R.styleable.Window_windowActionModeOverlay, false)) {
            requestFeature(FEATURE_ACTION_MODE_OVERLAY);
        }

        if (a.getBoolean(R.styleable.Window_windowSwipeToDismiss, false)) {
            requestFeature(FEATURE_SWIPE_TO_DISMISS);
        }
        //當前Activity是否支持全屏
        if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
            setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
        }

       ................

       //設置狀態欄的顏色
        if (!mForcedStatusBarColor) {
            mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, 0xFF000000);
        }
        //設置導航欄的顏色
        if (!mForcedNavigationBarColor) {
            mNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0xFF000000);
        }

        if (mAlwaysReadCloseOnTouchAttr || getContext().getApplicationInfo().targetSdkVersion
                >= android.os.Build.VERSION_CODES.HONEYCOMB) {
            if (a.getBoolean(
                    R.styleable.Window_windowCloseOnTouchOutside,
                    false)) {
                setCloseOnTouchOutsideIfNotSet(true);
            }
        }

        WindowManager.LayoutParams params = getAttributes();
        //設置輸入法的狀態
        if (!hasSoftInputMode()) {
            params.softInputMode = a.getInt(
                    R.styleable.Window_windowSoftInputMode,
                    params.softInputMode);
        }

        if (a.getBoolean(R.styleable.Window_backgroundDimEnabled,
                mIsFloating)) {
            /* All dialogs should have the window dimmed */
            if ((getForcedWindowFlags()&WindowManager.LayoutParams.FLAG_DIM_BEHIND) == 0) {
                params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
            }
            if (!haveDimAmount()) {
                params.dimAmount = a.getFloat(
                        android.R.styleable.Window_backgroundDimAmount, 0.5f);
            }
        }
        //設置當前Activity的出現動畫效果
        if (params.windowAnimations == 0) {
            params.windowAnimations = a.getResourceId(
                    R.styleable.Window_windowAnimationStyle, 0);
        }

        // The rest are only done if this window is not embedded; otherwise,
        // the values are inherited from our container.
        if (getContainer() == null) {
            if (mBackgroundDrawable == null) {
                if (mBackgroundResource == 0) {
                    mBackgroundResource = a.getResourceId(
                            R.styleable.Window_windowBackground, 0);
                }
                if (mFrameResource == 0) {
                    mFrameResource = a.getResourceId(R.styleable.Window_windowFrame, 0);
                }
                mBackgroundFallbackResource = a.getResourceId(
                        R.styleable.Window_windowBackgroundFallback, 0);
                if (false) {
                    System.out.println("Background: "
                            + Integer.toHexString(mBackgroundResource) + " Frame: "
                            + Integer.toHexString(mFrameResource));
                }
            }
            mElevation = a.getDimension(R.styleable.Window_windowElevation, 0);
            mClipToOutline = a.getBoolean(R.styleable.Window_windowClipToOutline, false);
            mTextColor = a.getColor(R.styleable.Window_textColor, Color.TRANSPARENT);
        }

        //以下代碼為當前Activity窗口添加 decor根布局。
        // Inflate the window decor.

        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;
        } 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 {
            // Embedded, so no decoration is needed.
            layoutResource = R.layout.screen_simple;
            // System.out.println("Simple!");
        }

        mDecor.startChanging();

        //通過布局添加器LayoutInflater獲取layoutResource布局,
        View in = mLayoutInflater.inflate(layoutResource, null);
        //將XML資源為layoutResource的布局添加到decor容器裡面,至此PhoneWindow 內部類DecorView就添加了之布局
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        mContentRoot = (ViewGroup) in;

        //此處很重要,通過findViewById找到 contentParent容器,也是該方法的返回值。
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

        if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
            ProgressBar progress = getCircularProgressBar(false);
            if (progress != null) {
                progress.setIndeterminate(true);
            }
        }

        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            registerSwipeCallbacks();
        }

        // Remaining setup -- of background and title -- that only applies
        // to top-level windows.
        //以下代碼設置Activity窗口的背景,標題等
        if (getContainer() == null) {
            final Drawable background;
            if (mBackgroundResource != 0) {
                background = getContext().getDrawable(mBackgroundResource);
            } else {
                background = mBackgroundDrawable;
            }
            mDecor.setWindowBackground(background);

            final Drawable frame;
            if (mFrameResource != 0) {
                frame = getContext().getDrawable(mFrameResource);
            } else {
                frame = null;
            }
            mDecor.setWindowFrame(frame);

            mDecor.setElevation(mElevation);
            mDecor.setClipToOutline(mClipToOutline);

            if (mTitle != null) {
                setTitle(mTitle);
            }

            if (mTitleColor == 0) {
                mTitleColor = mTextColor;
            }
            setTitleColor(mTitleColor);
        }

        mDecor.finishChanging();

        return contentParent;
    }

以上代碼代比較復雜,主要做了以下幾件事情。

從第 8 行到第110行,主要是初始化窗口的特征,是否顯示標題欄,是否全屏,是否支持ActionBar浮動等等。 第112-187行,主要是給容器DecorView添加id為layoutResource布局的根布局。待會分析layoutResource布局。 第178行,通過LayoutInflater 將xml布局轉換成VIEW. 第180行將找到的View添加到DecorView根布局當中。 第184行,從根布局中找到id為R.id.content的 contentParent 容器。也就是當前方法的返回值。
接下來,看看 id為layoutResource的布局到底實現了什麼?我們來看看171行的R.layout.screen_simple;資源

    
    <framelayout android:foreground="?android:attr/windowContentOverlay" android:foregroundgravity="fill_horizontal|top" android:foregroundinsidepadding="false" android:id="@android:id/content" android:layout_height="match_parent" android:layout_width="match_parent">
</framelayout>

原來我們的DecorView根布局裡面添加了類似上面的布局,線性布局LinearLaout裡包含兩個組件,ViewStub是懶加載,默認不顯示,FrameLayout是什麼呢?看看id=content,就是我們184行找到的父容器 contentParent。那麼這個父容器 contentParent有什麼作用呢?

我們回到 Step4 的第 11行,mContentParent = generateLayout(mDecor); 獲得 父容器 mContentParent。我們再次回到 Step3步的第17行, mLayoutInflater.inflate(layoutResID, mContentParent); 這裡通過LayoutInflater將 setContentView(layoutResID)傳進來的布局id加載到 父容器mContentParent中,至此,setContentView就將布局添加到Activity裡面了。

現在我們來梳理一下流程:
Activity setContentView—>Window setContentView—>PhoneWindow setContentView—->PhoneWindow installDecor—–>PhoneWindow generateLayout——>PhoneWindow mLayoutInflater.inflate(layoutResID, mContentParent);

Activity 類中有一個Window抽象類的實現PhoneWindow類,該類中有個內部類DecorView,繼承自FrameLayout,在DecorView容器中添加了根布局,根布局中包含了一個id為 contnet的FrameLayout 內容布局,我們的Activity加載的布局xml最後添加到 id為content的FrameLayout布局當中了。用一個圖來描述,如下:

這裡寫圖片描述vcC4o6zI57n70OjSqsils/21vLq9wLijrMTjv8nS1M2ouf3I58/CtPrC66O6Z2V0U3VwcG9ydEFjdGlvbkJhcigpLmhpZGUoKTvAtNL+sti1vLq9wLihozwvcD4NCjxwPjIucmVxdWVzdFdpbmRvd0ZlYXR1cmUoV2luZG93LkZFQVRVUkVfTk9fVElUTEUpO7e9t6jQ6NKq1Nogc2V0Q29udGVudFZpZXe3vbeo1q7HsMq508OjrNPJyc/D5iBTdGVwNbfWzva/ybXDo6zJ6NbDQWN0aXZpdHkgV2luZG93IMzY1ffKx9Tac2V0Q29udGVudFZpZXe3vbeo1tDJ6NbDtcSjrNLytMujrMjnufvQ6NKquMSx5EFjdGl2aXR5IFdpbmRvd7Swv9rM2NX3o6zQ6NKq1NpzZXRDb250ZW50Vmlld7e9t6jWrsewoaPG5Mq11eLA79PQ0snOyqO/o7+jv86qyrLDtMno1sPIq8bBtcS3vbeoPC9wPg0KPHByZSBjbGFzcz0="brush:java;"> getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);

可以在setContentView之後呢???求解。

版權聲明:本文為博主原創文章,未經博主允許不得轉載。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved