Android WMS窗口管理流程分析


1.1 Window窗口管理框架圖

WMS窗口管理

窗口的添加從Activity::makeVisible開始秕硝,由WindowManagerImpl委托給WindowManagerGlobal處理机杜,WindowManagerGlobal內(nèi)部的mViews闰非、mRoots碉咆、mParams骏庸、mDyingViews分別管理窗口的試圖、主線程景东、布局參數(shù)以及死亡過程中的視圖吞琐;ViewRootImpl持有Session的代理端與WMS通信捆探;


2.1 ActivityThread::handleLaunchActivity

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
    ...
    // Initialize before creating the activity
    WindowManagerGlobal.initialize(); // WindowManagerGlobal 初始化
    Activity a = performLaunchActivity(r, customIntent); // 2.2 ActivityThread::performLaunchActivity
    handleResumeActivity(r.token, false, r.isForward, !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason); // 2.6 ActivityThread::handleResumeActivity
}

2.2 ActivityThread::performLaunchActivity

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    Activity activity = null;
    ...
    java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
    activity = mInstrumentation.newActivity(
            cl, component.getClassName(), r.intent); //newInstance
    ...
    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); // 2.3 Activity::attach
    ...
    mInstrumentation.callActivityOnCreate(activity, r.state); //OnCreate
    ...
    activity.performStart();//Onstart
    ...
    mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state); // OnRestoreInstanceState
}

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

    mWindow = new PhoneWindow(this, window); // 新建PhoneWindow
    mWindow.setWindowControllerCallback(this);
    mWindow.setCallback(this); // Window.CallBack的實現(xiàn)類為Activity
    mWindow.setOnWindowDismissedCallback(this);
    ...
    mToken = token; // 添加一個Activity窗口所需要的令牌
    ...
    mWindow.setWindowManager(
            (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), /* 2.4.1 獲取WindowManagerImpl */
            mToken, mComponent.flattenToString(),
            (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); // 2.5.1 PhoneWindow::setWindowManager
    if (mParent != null) {
        mWindow.setContainer(mParent.getWindow());
    }
    mWindowManager = mWindow.getWindowManager(); // mWindowManager is WindowManagerImpl
    mCurrentConfig = config;
}

2.4.1 ContextImpl::getSystemService

public Object getSystemService(String name) {
    return SystemServiceRegistry.getSystemService(this, name); // 2.4.2 SystemServiceRegistry::registerService
}

2.4.2 SystemServiceRegistry::registerService

registerService(Context.WINDOW_SERVICE, WindowManager.class,
        new CachedServiceFetcher<WindowManager>() {
    @Override
    public WindowManager createService(ContextImpl ctx) {
        return new WindowManagerImpl(ctx);
}});

2.5.1 PhoneWindow::setWindowManager

public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
        boolean hardwareAccelerated) { // wm is WindowManagerImpl
    mAppToken = appToken;
    mAppName = appName;
    mHardwareAccelerated = hardwareAccelerated
            || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
    if (wm == null) {
        wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
    }
    mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}

2.5.2 WindowManagerImpl::createLocalWindowManager

public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
    return new WindowManagerImpl(mContext, parentWindow);
}

2.6 ActivityThread::handleResumeActivity

final void handleResumeActivity(IBinder token,
        boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
    ...
    r = performResumeActivity(token, clearHide, reason);
    // r.activity.performResume() -> Activity::OnResume
    ...
    if (r != null) {
        final Activity a = r.activity;
        ...
        if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow(); // PhoneWindow
            View decor = r.window.getDecorView(); // 2.6.1 PhoneWindow::getDecorView
            decor.setVisibility(View.INVISIBLE); // Activity的窗口在初創(chuàng)時不可見,因為尚不確定是否真的要顯示窗口給用戶
            ViewManager wm = a.getWindowManager(); // WindowManagerImpl
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; // 類型:屬于Acitivity的窗口
            l.softInputMode |= forwardBit;
            if (r.mPreserveWindow) {
                a.mWindowAdded = true;
                r.mPreserveWindow = false;
                // Normally the ViewRoot sets up callbacks with the Activity
                // in addView->ViewRootImpl#setView. If we are instead reusing
                // the decor view we have to notify the view root that the
                // callbacks may have changed.
                ViewRootImpl impl = decor.getViewRootImpl();
                if (impl != null) {
                    impl.notifyChildRebuilt();
                }
            }
            if (a.mVisibleFromClient && !a.mWindowAdded) {
                a.mWindowAdded = true;
                wm.addView(decor, l); // WM is windowManagerImpl站粟,添加到WMS
            }
        }
        // The window is now visible if it has been added, we are not
        // simply finishing, and we are not starting another activity.
        if (!r.activity.mFinished && willBeVisible
                && r.activity.mDecor != null && !r.hideForNow) {
            ...
            WindowManager.LayoutParams l = r.window.getAttributes();
            ...
            if (r.activity.mVisibleFromClient) {
                ViewManager wm = a.getWindowManager();
                View decor = r.window.getDecorView();
                wm.updateViewLayout(decor, l); // 通知wms更新布局
            }
            ...
                r.activity.makeVisible(); // 2.7 設置可見
            ...
        }
        ...
        if (!r.onlyLocalRequest) {
            r.nextIdle = mNewActivities;
            mNewActivities = r;
            Looper.myQueue().addIdleHandler(new Idler()); // 
        }
        ActivityManagerNative.getDefault().activityResumed(token); // 通知AMS Activity處于Resumed狀態(tài)

    } else {
        // If an exception was thrown when trying to resume, then
        // just end this activity.
        // resume過程發(fā)生異常黍图,則 finishActivity
            ActivityManagerNative.getDefault()
                .finishActivity(token, Activity.RESULT_CANCELED, null,
                        Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
    }
}

2.6.1 PhoneWindow::getDecorView

public final View getDecorView() {
    if (mDecor == null || mForceDecorInstall) {
        installDecor(); //  2.6.2 
    }
    return mDecor;
}

2.6.2 PhoneWindow::installDecor

private void installDecor() {
    mForceDecorInstall = false;
    if (mDecor == null) {
        mDecor = generateDecor(-1); // 2.6.3 
        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        mDecor.setIsRootNamespace(true);
        if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
            mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
        }
    } else {
        mDecor.setWindow(this); // PhoneWindow::mWindow is PhoneWindow
    }
}

2.6.3 PhoneWindow::generateDecor

protected DecorView generateDecor(int featureId) {
    // System process doesn't have application context and in that case we need to directly use
    // the context we have. Otherwise we want the application context, so we don't cling to the
    // activity.
    Context context;
    ...
    context = getContext();
    ...
    return new DecorView(context, featureId, this, getAttributes());
}

2.6.4 ActivityDecorView相關結(jié)構(gòu)圖

ActivityDecorView相關結(jié)構(gòu)圖

ActivityDecorView為根視圖

2.7 Activity::makeVisible 設置窗口可見

void makeVisible() {
    if (!mWindowAdded) {
        ViewManager wm = getWindowManager();
        wm.addView(mDecor, getWindow().getAttributes()); // 2.9 委托WindowManagerGlobal添加到wms
        mWindowAdded = true;
    }
    mDecor.setVisibility(View.VISIBLE); // 設置DecorView可見
}

2.8 WindowManagerImpl相關框架圖

WindowManagerImpl相關架構(gòu)圖

接口ViewManager定義了view的基本操作增(addView)、刪(removeView)奴烙、更新(updateViewLayout);
接口WindowManger繼承自ViewManager助被;
WindowManagerImpl實現(xiàn)WindowManager中的方法,并委托給WindowMangerGlobal處理缸沃;
WindowManagerGlobal中的mViews恰起、mRoots修械、mParams趾牧、mDyingViews分別管理窗口的試圖、主線程肯污、布局參數(shù)以及死亡的試圖翘单;

2.9 WindowManagerGlobal::addView

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        // 參數(shù)檢查
        ...
        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            ...
            int index = findViewLocked(view, false);
            if (index >= 0) {
                if (mDyingViews.contains(view)) {
                    // Don't wait for MSG_DIE to make it's way through root's queue.
                    mRoots.get(index).doDie(); // 3.1 如果已經(jīng)包含此view,則先remove
                } 
            }
            ...
            root = new ViewRootImpl(view.getContext(), display); // 2.12 ViewRootImpl構(gòu)造過程獲取WindowSession
            view.setLayoutParams(wparams);
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams); // 保存當前window
        }
        try {
            root.setView(view, wparams, panelParentView); // 2.12 更新布局
        } catch (RuntimeException e) {
            synchronized (mLock) {
                final int index = findViewLocked(view, false);
                if (index >= 0) {
                    removeViewLocked(index, true); // remove試圖
                }
            }
        }
    }

2.10 WindowManagerGlobal::addView 相關調(diào)用棧

WindowManagerImpl.addView()
    new ViewRoot
    // 保存到 mViews  mRoots  mParams
    ViewRoot.setView(xx)
        requestLayout // 檢查主線程
            scheduleTraversals
                sendEmptyMessage
                handleMessage
                    performTraversals  // 2.11 測量 布局 繪制
                        ViewRoot.relayoutWindow
                            IWindowSession.relayout // 參考relayout的調(diào)用棧
                        draw
                            View.draw
                        scheduleTralScheduled // try again
        mWindowSession.addToDisplay // 2.15 與requestLayout同級
        // sWindowSession.add
            WMS.addWindow // 2.16
                new WindowState // 
                WindowState.attach // 
                    Session.windowAddedLocked
                        new SurfaceSession // 開辟一塊內(nèi)存吨枉,由SurfaceFlinger進行混合處理

2.11 測量 布局 繪制流程

performTraversals基本流程

2.12 WindowMangerGlobal::getWindowSession

public static IWindowSession getWindowSession() {
    synchronized (WindowManagerGlobal.class) {
        if (sWindowSession == null) {
            try {
                InputMethodManager imm = InputMethodManager.getInstance();
                IWindowManager windowManager = getWindowManagerService(); // wms
                sWindowSession = windowManager.openSession( // 2.13 wms::opensession
                        new IWindowSessionCallback.Stub() {
                            @Override
                            public void onAnimatorScaleChanged(float scale) {
                                ValueAnimator.setDurationScale(scale);
                            }
                        },
                        imm.getClient(), imm.getInputContext());
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
        return sWindowSession;
    }
}

2.13 WindowMangerService::openSession

public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
        IInputContext inputContext) {
    if (client == null) throw new IllegalArgumentException("null client");
    if (inputContext == null) throw new IllegalArgumentException("null inputContext");
    Session session = new Session(this, callback, client, inputContext);
    return session;
}

2.14 WMS::Session與ViewRootImpl關系

Session與ViewRootImpl關系

ViewRootImpl獲取Session的代理類,通過Binder::IPC通信哄芜;
Session持有WMS服務貌亭;
InputState為事件傳遞相關的類;
W代表當前操作的窗口, 是ViewRootImpl的內(nèi)部類认臊,且持有ViewRootImpl的弱引用

2.15 Session::addToDisplay

public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
        int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
        Rect outOutsets, InputChannel outInputChannel) {
    return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
            outContentInsets, outStableInsets, outOutsets, outInputChannel);
}

2.16 WMS::addWindow

/**
 * All currently active sessions with clients.
 */
final ArraySet<Session> mSessions = new ArraySet<>();

/**
 * Mapping from an IWindow IBinder to the server's Window object.
 * This is also used as the lock for all of our state.
 * NOTE: Never call into methods that lock ActivityManagerService while holding this object.
 */
final HashMap<IBinder, WindowState> mWindowMap = new HashMap<>();

/**
 * Mapping from a token IBinder to a WindowToken object.
 */
final HashMap<IBinder, WindowToken> mTokenMap = new HashMap<>();

public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            InputChannel outInputChannel) {
        int res = mPolicy.checkAddPermission(attrs, appOp); // PhoneWindowManager進行權限檢查
        // 接下來依然是做一些檢查
        ...
        WindowState win = new WindowState(this, session, client, token,
                attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent);
        ...
        mPolicy.adjustWindowParamsLw(win.mAttrs);// 調(diào)整window參數(shù)
        ...
        res = mPolicy.prepareAddWindowLw(win, attrs);
        ...
        win.openInputChannel(outInputChannel); // 窗口端IMS管道


        win.attach(); // 2.17
        mWindowMap.put(client.asBinder(), win); // 保存到mWindowMap

        boolean imMayMove = true;

        if (type == TYPE_INPUT_METHOD) {
            addInputMethodWindowToListLocked(win);
        } else if (type == TYPE_INPUT_METHOD_DIALOG) {
            addWindowToListInOrderLocked(win, true);
        } else {
            addWindowToListInOrderLocked(win, true);
        }
        ...
        final WindowStateAnimator winAnimator = win.mWinAnimator;
        winAnimator.mEnterAnimationPending = true;
        winAnimator.mEnteringAnimation = true;
        ...
        boolean focusChanged = false;
        if (win.canReceiveKeys()) {
            focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS,
                    false /*updateInputWindows*/); // 更新焦點窗口
        }
        ...
        if (focusChanged) {
            mInputMonitor.setInputFocusLw(mCurrentFocus, false /*updateInputWindows*/);
        }
        mInputMonitor.updateInputWindowsLw(false /*force*/);
    }
}

mTokenMap: 保存所有的顯示令牌, 用于窗口管理;
mWindowMap:保存所有窗口的狀態(tài)信息(windowstate), 用于窗口管理;
mSession:保存當前所有想向WMS尋求窗口管理服務的客戶端;

2.17 WindowState::attach

void attach() {
    mSession.windowAddedLocked(); // 2.18
}

2.18 WindowState::windowAddedLocked

void windowAddedLocked() {
    if (mSurfaceSession == null) {
        mSurfaceSession = new SurfaceSession(); // 在內(nèi)存中創(chuàng)建一塊空間圃庭,用于SurfaceFlinger
        mService.mSessions.add(this); //記錄當前Session
        if (mLastReportedAnimatorScale != mService.getCurrentAnimatorScale()) {
            mService.dispatchNewAnimatorScaleLocked(this);
        }
    }
    mNumWindow++;
}

3.1 ViewRootImpl::doDie

void doDie() {
    checkThread(); // 檢查主線程
    synchronized (this) {
        mRemoved = true; // 清楚標記
        if (mAdded) {
            dispatchDetachedFromWindow(); // 3.2 清理窗口的入口
        }

        if (mAdded && !mFirst) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "destroyHardwareRenderer");
            destroyHardwareRenderer();
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);

            if (mView != null) {
                int viewVisibility = mView.getVisibility();
                boolean viewVisibilityChanged = mViewVisibility != viewVisibility;
                if (mWindowAttributesChanged || viewVisibilityChanged) {
                    // If layout params have been changed, first give them
                    // to the window manager to make sure it has the correct
                    // animation info.
                    try {
                        if ((relayoutWindow(mWindowAttributes, viewVisibility, false)
                                & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
                            mWindowSession.finishDrawing(mWindow);
                        }
                    } catch (RemoteException e) {
                        Log.e(mTag, "RemoteException when finish draw window " + mWindow
                                + " in " + this, e);
                    }
                }

                mSurface.release();
            }
        }
        mAdded = false;
    }
    WindowManagerGlobal.getInstance().doRemoveView(this); //3.3 移除WindowManagerGlobal中的數(shù)據(jù)
}

3.2 ViewRootImpl::dispatchDetachedFromWindow

void dispatchDetachedFromWindow() {
    if (mView != null && mView.mAttachInfo != null) {
        mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
        mView.dispatchDetachedFromWindow(); // 
    }
    ...
    mView.assignParent(null);
    mView = null;
    mAttachInfo.mRootView = null;

    mSurface.release(); // surface釋放

    if (mInputQueueCallback != null && mInputQueue != null) {
        mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
        mInputQueue.dispose(); // 事件隊列清理
        mInputQueueCallback = null;
        mInputQueue = null;
    }
    if (mInputEventReceiver != null) {
        mInputEventReceiver.dispose(); // 輸入事件清理
        mInputEventReceiver = null;
    }
    try {
        mWindowSession.remove(mWindow); // 通知wms移除window
    } catch (RemoteException e) {
        Log.e(mTag, "RemoteException remove window " + mWindow + " in " + this, e);
    }

    // Dispose the input channel after removing the window so the Window Manager
    // doesn't interpret the input channel being closed as an abnormal termination.
    if (mInputChannel != null) {
        mInputChannel.dispose(); // IMS管道清理
        mInputChannel = null;
    }

    mDisplayManager.unregisterDisplayListener(mDisplayListener);
    unscheduleTraversals();
}

3.3 ViewRootImpl::doRemoveView

void doRemoveView(ViewRootImpl root) {
    synchronized (mLock) {
        final int index = mRoots.indexOf(root);
        if (index >= 0) {
            mRoots.remove(index);
            mParams.remove(index);
            final View view = mViews.remove(index);
            mDyingViews.remove(view);
        }
    }
}
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市失晴,隨后出現(xiàn)的幾起案子剧腻,更是在濱河造成了極大的恐慌,老刑警劉巖涂屁,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件书在,死亡現(xiàn)場離奇詭異,居然都是意外死亡拆又,警方通過查閱死者的電腦和手機儒旬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來帖族,“玉大人栈源,你說我怎么就攤上這事∈悖” “怎么了凉翻?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長捻激。 經(jīng)常有香客問我制轰,道長,這世上最難降的妖魔是什么胞谭? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任垃杖,我火速辦了婚禮,結(jié)果婚禮上丈屹,老公的妹妹穿的比我還像新娘调俘。我一直安慰自己,他們只是感情好旺垒,可當我...
    茶點故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布彩库。 她就那樣靜靜地躺著,像睡著了一般先蒋。 火紅的嫁衣襯著肌膚如雪骇钦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天竞漾,我揣著相機與錄音眯搭,去河邊找鬼窥翩。 笑死,一個胖子當著我的面吹牛鳞仙,可吹牛的內(nèi)容都是我干的寇蚊。 我是一名探鬼主播,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼棍好,長吁一口氣:“原來是場噩夢啊……” “哼仗岸!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起借笙,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤爹梁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后提澎,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體姚垃,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年盼忌,在試婚紗的時候發(fā)現(xiàn)自己被綠了积糯。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡谦纱,死狀恐怖看成,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情跨嘉,我是刑警寧澤川慌,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站祠乃,受9級特大地震影響梦重,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜亮瓷,卻給世界環(huán)境...
    茶點故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一琴拧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嘱支,春花似錦蚓胸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至汛聚,卻和暖如春锹安,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工八毯, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留搓侄,地道東北人瞄桨。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓话速,卻偏偏與公主長得像,于是被迫代替她去往敵國和親芯侥。 傳聞我的和親對象是個殘疾皇子泊交,可洞房花燭夜當晚...
    茶點故事閱讀 45,507評論 2 359

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