Android進階(六):Activity啟動時View顯示過程(淺析)

1.前言

  • 最近一直在看 《Android進階解密》 的一本書,這本書編寫邏輯嘱巾、流程都非常好憨琳,而且很容易看懂,非常推薦大家去看看(沒有收廣告費旬昭,單純覺得作者寫的很好)篙螟。
  • 上一篇簡單的介紹了Android進階(五):Service啟動過程(最詳細&最簡單)
  • 今天就介紹:Activity啟動時View顯示過程 (基于Android 8.0 系統(tǒng))稳懒。
  • 文章中實例 linhaojian的Github

2.View顯示過程時序總圖

  • 引用下面的時序總圖,方便更容易理解下文源碼部分的內(nèi)容慢味。


    Activity啟動時View的顯示流程.png

3.源碼分析

3.1 ActivityThread

    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
        // Activity調(diào)用完onResume之后,會真正開始繪制界面
        if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();//PhoneWindow
            View decor = r.window.getDecorView();// 1   
            decor.setVisibility(View.INVISIBLE);
            //Activity中的WindowMangerImp對象驰唬,它實現(xiàn)ViewManager接口
            ViewManager wm = a.getWindowManager();//2  
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;//把phoneWindow里的DecorView賦值給Activity的decor
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;
            if (r.mPreserveWindow) {
                a.mWindowAdded = true;
                r.mPreserveWindow = false;
                ViewRootImpl impl = decor.getViewRootImpl();
                if (impl != null) {
                    impl.notifyChildRebuilt();
                }
            }
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    //將decor添加在WindowManager(WindowMangerImp)中
                    wm.addView(decor, l);//3  
                } else {
                    // The activity will get a callback for this {@link LayoutParams} change
                    // earlier. However, at that time the decor will not be set (this is set
                    // in this method), so no action will be taken. This call ensures the
                    // callback occurs with the decor set.
                    a.onWindowAttributesChanged(l);
                }
            }
    }
  • 注釋1:獲取PhoneWindow中的DecorView,DecorView在Activity調(diào)用setContentView時被創(chuàng)建顶岸;
  • 注釋2:獲取ViewManager的實現(xiàn)類WindowMangaerImp,WindowManagerImp在Activity中attach()被創(chuàng)建叫编;
  • 注釋3:將DecorView與相關(guān)的布局參數(shù)傳遞至WindowManagerImp中辖佣;

3.2 WindowManagerImpl

public final class WindowManagerImpl implements WindowManager {
    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);// 1
    }
}
  • 注釋1:其實就是運用了外觀模式,真正的實現(xiàn)在WindowManagerGloball的addView()搓逾;

3.3 WindowManagerGlobal

public final class WindowManagerGlobal {
    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
            //...
            root = new ViewRootImpl(view.getContext(), display);// 1
            view.setLayoutParams(wparams);
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
            // do this last because it fires off messages to start doing things
            try {
                root.setView(view, wparams, panelParentView);// 2
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
    }
}
  • 注釋1:創(chuàng)建ViewRootImpl實例卷谈;
  • 注釋2:把decorview傳遞至ViewRootImpl

3.4 ViewRootImpl

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
                // 先把UI界面的數(shù)據(jù)準備好(測量--布局--繪制)
                requestLayout();// 1
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    mInputChannel = new InputChannel();
                }
                mForceDecorViewVisibility = (mWindowAttributes.privateFlags
                        & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    //將該Window添加到屏幕霞篡。
                    //mWindowSession實現(xiàn)了IWindowSession接口世蔗,它是Session的客戶端Binderd代理對象.
                    //addToDisplay是一次AIDL的跨進程通信端逼,通知WindowManagerService添加IWindow
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);// 2
                } catch (RemoteException e) {
                    //...
                } finally {
                    if (restore) {
                        attrs.restore();
                    }
                }
    }
}
  • 注釋1:刷新界面,下文會繼續(xù)介紹污淋;
  • 注釋2:**mWindowSession是Session的代理對象顶滩,而Session在WindowManagerService中被初始化,這里通過AIDL的方式與WindowManagerService進行跨進程通訊寸爆。
    **礁鲁;

3.5 Session

class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
    @Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,
            Rect outStableInsets, Rect outOutsets,
            DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
                outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel);// 1
    }
}
  • 注釋1:其實就是調(diào)用WindowManagerService的addWindow()

3.6 WindowManagerService

public class WindowManagerService extends IWindowManager.Stub
        implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {
    public int addWindow(Session session, IWindow client, int seq,
            LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel) {
            //...
            final WindowState win = new WindowState(this, session, client, token, parentWindow,
                    appOp[0], seq, attrs, viewVisibility, session.mUid,
                    session.mCanAddInternalSystemWindow);// 1
             // ...
            // 初始化SurfaceSession對象同時會調(diào)用底層相關(guān)內(nèi)容(SurfaceComposerClient,SurfaceComposerClient
            // 是與SurfaceFlinger通訊的代理對象)
            win.attach();// 2
            mWindowMap.put(client.asBinder(), win);
            // ....
     }
}
  • 注釋1:創(chuàng)建表示窗口狀態(tài)的對象而昨,并把相關(guān)信息傳入救氯;
  • 注釋2:調(diào)用窗口狀態(tài)對象的初始化函數(shù)

3.7 WindowState

class WindowState extends WindowContainer<WindowState> implements WindowManagerPolicy.WindowState {
    void attach() {
        if (localLOGV) Slog.v(TAG, "Attaching " + this + " token=" + mToken);
        mSession.windowAddedLocked(mAttrs.packageName);//1
    }
}
  • 注釋1:調(diào)用Session的windowAddedLocked函數(shù)
  • Session的windowAddedLocked()
    void windowAddedLocked(String packageName) {
        mPackageName = packageName;
        mRelayoutTag = "relayoutWindow: " + mPackageName;
        if (mSurfaceSession == null) {
            if (WindowManagerService.localLOGV) Slog.v(
                TAG_WM, "First window added to " + this + ", creating SurfaceSession");
            mSurfaceSession = new SurfaceSession();// 1
            if (SHOW_TRANSACTIONS) Slog.i(
                    TAG_WM, "  NEW SURFACE SESSION " + mSurfaceSession);
            mService.mSessions.add(this);
            if (mLastReportedAnimatorScale != mService.getCurrentAnimatorScale()) {
                mService.dispatchNewAnimatorScaleLocked(this);
            }
        }
        mNumWindow++;
    }
  • 注釋1:其實就是初始化SurfaceSession歌憨,SurfaceSession類中大部分都是native的函數(shù)着憨,該類的作用主要是初始化底層庫中的SurfaceComposerClient(與SurfaceFlinger通訊,SurfaceFlinger才是真正實現(xiàn)合成界面并顯示至手機屏幕中)务嫡;
  • 到這里甲抖,我們大概知道ActivityThread.handleResumeActivity()所引發(fā)的一些操作:
    • 1.ActivityThread 通過 WindowManagerGlobal 創(chuàng)建ViewRootImpl(一個Winodw可包含多個ViewRootImpl);
    • 2.ViewRootImpl 通過IWindowSession代理對象最終與WindowManagerService通訊心铃;
    • 3.WindowManagerService中會為應(yīng)用程序創(chuàng)建一個WindowState准谚,代表著應(yīng)用程序窗口信息;
    • 4.WindowState中通過Session創(chuàng)建一個SurfaceSession(SurfaceSession作用:就是使應(yīng)用程序與Surfacelginer構(gòu)建通訊環(huán)境)去扣;
      ViewRootImpl關(guān)聯(lián)SurfaceLinger.png
  • 下面就開始真正的把繪制內(nèi)容填充至手機屏幕(SurfaceWindowUI載體柱衔,所以繪制的內(nèi)容最終在應(yīng)用程序端會變成Surface);

3.8 ViewRootImpl的requestLayout()

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            //啟動一個延遲任務(wù)愉棱,任務(wù)內(nèi)容:mTraversalRunnable
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }
    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
            performTraversals();// 1
            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }
  • 注釋1:performTraversals真正的界面繪制過程唆铐;
    private void performTraversals() {
        //...
       relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);// 1
        //...
        performMeasure(); 
        //...
        performLayout(lp, mWidth, mHeight); 
        //...
        performDraw();
    }
  • performTraversals函數(shù)中,包含4個比較重要的方法奔滑,如上述代碼塊中的注釋1-4
  • 注釋1:relayoutWindow函數(shù)會繼續(xù)跟WindowManagerService通訊艾岂,下文會展開他們通訊的作用
    private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
            boolean insetsPending) throws RemoteException {
               //通過Session的代理對象朋其,調(diào)用其中的relayout函數(shù)王浴,而relayout最終也是調(diào)用WindowManangerService的relayoutWindow函數(shù)
                int relayoutResult = mWindowSession.relayout(mWindow, mSeq, params,
                (int) (mView.getMeasuredWidth() * appScale + 0.5f),
                (int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
                insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
                mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,
                mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingDisplayCutout,
                mPendingMergedConfiguration, mSurface);// 1
          //....
        return relayoutResult;
    }
  • 注釋1:通過Session的代理對象,最終與WindowManangerService通訊梅猿,從傳入的參數(shù)觀看到一個mSurface氓辣,而這個就是顯示界面到屏幕的關(guān)鍵,那這個mSurface從哪里創(chuàng)建呢袱蚓?它里面包含什么內(nèi)容筛婉?;
  • Surface創(chuàng)建
public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
    // These can be accessed by any thread, must be protected with a lock.
    // Surface can never be reassigned or cleared (use Surface.clear()).
    public final Surface mSurface = new Surface();
}
  • 從上述代碼可以發(fā)現(xiàn),其實在ViewRootImpl創(chuàng)建的同時初始化了一個空殼的Surface,上述的relayoutWindow函數(shù)把這個空殼Surface傳遞至WindowManangerService具體是干什么爽撒,我們進行跟蹤入蛆。
  • WindowManangerService的relayoutWindow函數(shù)
    public int relayoutWindow(Session session, IWindow client, int seq, LayoutParams attrs,
            int requestedWidth, int requestedHeight, int viewVisibility, int flags,
            long frameNumber, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,
            Rect outVisibleInsets, Rect outStableInsets, Rect outOutsets, Rect outBackdropFrame,
            DisplayCutout.ParcelableWrapper outCutout, MergedConfiguration mergedConfiguration,
            Surface outSurface) {
            //...
            try {
                    result = createSurfaceControl(outSurface, result, win, winAnimator);
                } catch (Exception e) {
                    mInputMonitor.updateInputWindowsLw(true /*force*/);
                    Slog.w(TAG_WM, "Exception thrown when creating surface for client "
                             + client + " (" + win.mAttrs.getTitle() + ")",
                             e);
                    Binder.restoreCallingIdentity(origId);
                    return 0;
                }
             //...
    }
    //.....
    private int createSurfaceControl(Surface outSurface, int result, WindowState win,
            WindowStateAnimator winAnimator) {
        if (!win.mHasSurface) {
            result |= RELAYOUT_RES_SURFACE_CHANGED;
        }
        WindowSurfaceController surfaceController;
        try {
            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "createSurfaceControl");
            surfaceController = winAnimator.createSurfaceLocked(win.mAttrs.type, win.mOwnerUid);// 1
        } finally {
            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
        }
        if (surfaceController != null) {
            surfaceController.getSurface(outSurface); // 2
            if (SHOW_TRANSACTIONS) Slog.i(TAG_WM, "  OUT SURFACE " + outSurface + ": copied");
        } else {
            // For some reason there isn't a surface.  Clear the
            // caller's object so they see the same state.
            Slog.w(TAG_WM, "Failed to create surface control for " + win);
            outSurface.release();
        }
        return result;
    } 
  • 注釋1:winAnimator.createSurfaceLocked實際上是創(chuàng)建了一個SurfaceControl(SurfaceControl負責(zé)操作與維護Surface)
  • 注釋2:就是把SurfaceControl關(guān)聯(lián)至outSurface中硕勿;
  • 到這里哨毁,我們大概知道ViewRootImpl.relayoutWindow所觸發(fā)的操作:
    • 1.ViewRootImplSurface通過Session的代理對象,最終傳遞至WindowManangerService源武;
    • 2.WindowManangerService中通過創(chuàng)建WindowSurfaceController扼褪;
    • 3.WindowSurfaceController中實現(xiàn)Surface關(guān)聯(lián)SurfaceControl
    • 4.SurfaceControl會創(chuàng)建Native層的Surface粱栖,并把指針賦值與ViewRootImpl的Surface话浇;
      Surface關(guān)聯(lián)SurfaceControl.png
  • 下面繼續(xù)跟蹤View是如何與Surface產(chǎn)生關(guān)聯(lián);

3.8 ViewRootImpl的performDraw()

    private void performDraw() {
        try {
            // 調(diào)用draw繪制界面闹究,最終會調(diào)用drawSoftware()
            boolean canUseAsync = draw(fullRedrawNeeded);
            if (usingAsyncReport && !canUseAsync) {
                mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);
                usingAsyncReport = false;
            }
        } finally {
            mIsDrawing = false;
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
   }
    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
         //.....
         final Canvas canvas;
         //.....
         canvas = mSurface.lockCanvas(dirty);// 1
         //.....
         mView.draw(canvas);// 2
         //.....
         surface.unlockCanvasAndPost(canvas); // 3
    }
  • 注釋1:通過Surface獲取Canvas畫布幔崖;
  • 注釋2:將畫布傳入至DecorView的draw方法中,并向畫布繪制或者填充內(nèi)容渣淤;
  • 注釋3:把繪制完的畫布傳遞至Surface赏寇,最后Surface會調(diào)用相關(guān)的native方法,把界面數(shù)據(jù)添加至底層Buffer中价认,待SurfaceFlinger處理繪制嗅定;
    Surface填充內(nèi)容流程.png

4.關(guān)系鏈

View顯示過程關(guān)系鏈.png
  • 綜合源碼分析與上述圖示,可匯總以下信息:
    1.ViewRootImpl通過創(chuàng)建對應(yīng)的Session用踩、SurfaceSession渠退,并與SurfaceFlinger構(gòu)建通訊環(huán)境;
    2.WindowManangerService創(chuàng)建SurfaceController管理窗口屬性與創(chuàng)建Native層的Surface,并將Native的Surface指向ViewRootImplSurface;
    3.ViewRootImpl中會把View的內(nèi)容傳遞至Surface中脐彩,最后會把界面數(shù)據(jù)保存至ShareBuffer碎乃;
    4.SurfaceFlinger接收到ShareBuffer后,通知相關(guān)硬件進行顯示丁屎;

5.總結(jié)

  • 到此荠锭,Activity啟動時View顯示過程介紹完畢旱眯。
  • 如果喜歡我的分享晨川,可以點擊 關(guān)注 或者 ,你們支持是我分享的最大動力 删豺。
  • linhaojian的Github

歡迎關(guān)注linhaojian_CSDN博客或者linhaojian_簡書共虑!

不定期分享關(guān)于安卓開發(fā)的干貨。


寫技術(shù)文章初心

  • 技術(shù)知識積累
  • 技術(shù)知識鞏固
  • 技術(shù)知識分享
  • 技術(shù)知識交流
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末呀页,一起剝皮案震驚了整個濱河市妈拌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖尘分,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件猜惋,死亡現(xiàn)場離奇詭異,居然都是意外死亡培愁,警方通過查閱死者的電腦和手機著摔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來定续,“玉大人谍咆,你說我怎么就攤上這事∷焦桑” “怎么了摹察?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長倡鲸。 經(jīng)常有香客問我供嚎,道長,這世上最難降的妖魔是什么旦签? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任查坪,我火速辦了婚禮,結(jié)果婚禮上宁炫,老公的妹妹穿的比我還像新娘偿曙。我一直安慰自己,他們只是感情好羔巢,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布望忆。 她就那樣靜靜地躺著,像睡著了一般竿秆。 火紅的嫁衣襯著肌膚如雪启摄。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天幽钢,我揣著相機與錄音歉备,去河邊找鬼。 笑死匪燕,一個胖子當著我的面吹牛蕾羊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播帽驯,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼龟再,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了尼变?” 一聲冷哼從身側(cè)響起利凑,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后哀澈,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體牌借,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年割按,在試婚紗的時候發(fā)現(xiàn)自己被綠了走哺。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡哲虾,死狀恐怖丙躏,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情束凑,我是刑警寧澤晒旅,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站汪诉,受9級特大地震影響废恋,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜扒寄,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一鱼鼓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧该编,春花似錦迄本、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至于樟,卻和暖如春公条,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背迂曲。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工靶橱, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人路捧。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓关霸,卻偏偏與公主長得像,于是被迫代替她去往敵國和親鬓长。 傳聞我的和親對象是個殘疾皇子谒拴,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

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