Activity啟動(dòng)流程臭挽,進(jìn)階高級(jí)

在日常開發(fā)中我們經(jīng)常從一個(gè)Activity跳轉(zhuǎn)到另一個(gè)Activity芳室,調(diào)用的代碼無非有以下兩種

startActivity(intent);
startActivityForResult(intent, requestCode);

其實(shí)startActivity()最終也會(huì)調(diào)用startActivityForResult()來實(shí)現(xiàn)界面的跳轉(zhuǎn),下面我們就從startActivityForResult()來梳理Activity的啟動(dòng)流程;

@Override
public void startActivity(Intent intent) {
    this.startActivity(intent, null);
}
@Override
public void startActivity(Intent intent, @Nullable Bundle options) {
    if (options != null) {
        startActivityForResult(intent, -1, options);
    } else {
        startActivityForResult(intent, -1);
    }
}
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode) {
    startActivityForResult(intent, requestCode, null);
}
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
            @Nullable Bundle options) {
    if (mParent == null) {
        options = transferSpringboardActivityOptions(options);
        Instrumentation.ActivityResult ar =
            mInstrumentation.execStartActivity(
                this, mMainThread.getApplicationThread(), mToken, this,
                intent, requestCode, options);
        if (ar != null) {
            mMainThread.sendActivityResult(
                mToken, mEmbeddedID, requestCode, ar.getResultCode(),
                ar.getResultData());
        }
        if (requestCode >= 0) {
            mStartedActivity = true;
        }
        cancelInputsAndStartExitTransition(options);
    } else {
        if (options != null) {
            mParent.startActivityFromChild(this, intent, requestCode, options);
        } else {
            mParent.startActivityFromChild(this, intent, requestCode);
        }
    }
}

關(guān)鍵方法mInstrumentation.execStartActivity()斜做,返回回調(diào)結(jié)果场躯。Instrumentation拿來測試過的同學(xué)并不陌生谈为,這里且當(dāng)它是個(gè)黑科技工具。

pic

進(jìn)入execStartActivity方法踢关。

public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
    ...
    int result = ActivityManagerNative.getDefault()
        .startActivity(whoThread, who.getBasePackageName(), intent,
                intent.resolveTypeIfNeeded(who.getContentResolver()),
                token, target != null ? target.mEmbeddedID : null,
                requestCode, 0, null, options);
    checkStartActivityResult(result, intent);
    ...
}

這里解釋下前4個(gè)參數(shù):

  • who:正在啟動(dòng)該Activity的上下文
  • contextThread:正在啟動(dòng)該Activity的上下文線程伞鲫,這里為ApplicationThread
  • token:正在啟動(dòng)該Activity的標(biāo)識(shí)
  • target:正在啟動(dòng)該Activity的Activity,也就是回調(diào)結(jié)果的Activity
image

我們先來看看下面的checkStartActivityResult方法签舞。

public static void checkStartActivityResult(int res, Object intent) {
    if (res >= ActivityManager.START_SUCCESS) {
        return;
    }
    switch (res) {
        case ActivityManager.START_INTENT_NOT_RESOLVED:
        case ActivityManager.START_CLASS_NOT_FOUND:
            if (intent instanceof Intent && ((Intent)intent).getComponent() != null)
                throw new ActivityNotFoundException(
                        "Unable to find explicit activity class "
                        + ((Intent)intent).getComponent().toShortString()
                        + "; have you declared this activity in your AndroidManifest.xml?");
            throw new ActivityNotFoundException(
                    "No Activity found to handle " + intent);
        case ActivityManager.START_PERMISSION_DENIED:
            throw new SecurityException("Not allowed to start activity "
                    + intent);
        case ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT:
            throw new AndroidRuntimeException(
                    "FORWARD_RESULT_FLAG used while also requesting a result");
        case ActivityManager.START_NOT_ACTIVITY:
            throw new IllegalArgumentException(
                    "PendingIntent is not an activity");
        case ActivityManager.START_NOT_VOICE_COMPATIBLE:
            throw new SecurityException(
                    "Starting under voice control not allowed for: " + intent);
        case ActivityManager.START_VOICE_NOT_ACTIVE_SESSION:
            throw new IllegalStateException(
                    "Session calling startVoiceActivity does not match active session");
        case ActivityManager.START_VOICE_HIDDEN_SESSION:
            throw new IllegalStateException(
                    "Cannot start voice activity on a hidden session");
        case ActivityManager.START_CANCELED:
            throw new AndroidRuntimeException("Activity could not be started for "
                    + intent);
        default:
            throw new AndroidRuntimeException("Unknown error code "
                    + res + " when starting " + intent);
    }
}

這些異常就是根據(jù)返回碼和intent拋出相應(yīng)異常秕脓,最熟悉的就是activity沒有在AndroidManifest里面注冊(cè)了。
在這個(gè)方法中儒搭,我們還發(fā)現(xiàn)了熟悉的字眼startActivity吠架,但調(diào)用者卻很陌生ActivityManagerNative.getDefault()

static public IActivityManager getDefault() {
    return gDefault.get();
}
private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
    protected IActivityManager create() {
        IBinder b = ServiceManager.getService("activity");
        ...
        IActivityManager am = asInterface(b);
        ...
        return am;
    }
};
static public IActivityManager asInterface(IBinder obj) {
    ...
    return new ActivityManagerProxy(obj);
}

這樣看下來好像是ServiceManager構(gòu)建了一個(gè)key為activity的對(duì)象,該對(duì)象作為ActivityManagerProxy的參數(shù)實(shí)例化創(chuàng)建單例并get返回搂鲫。這里先不作解析傍药,繼續(xù)連接上面的流程ActivityManagerNative.getDefault().startActivity(…)。我們已經(jīng)知道startActivity方法其實(shí)是ActivityManagerProxy調(diào)的魂仍,我們?cè)賮砜纯础?/p>

public int startActivity(IApplicationThread caller, String callingPackage, Intent intent,
            String resolvedType, IBinder resultTo, String resultWho, int requestCode,
            int startFlags, ProfilerInfo profilerInfo, Bundle options) throws RemoteException {
    Parcel data = Parcel.obtain();
    Parcel reply = Parcel.obtain();
    ...
    mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0);
    ...
    reply.recycle();
    data.recycle();
    return result;
}
image

我們通過代理類ActivityManagerProxy調(diào)用了startActivity方法拐辽,讓我們來搜尋一下AMS的startActivity方法。

@Override
public final int startActivity(IApplicationThread caller, String callingPackage,
        Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
        int startFlags, ProfilerInfo profilerInfo, Bundle bOptions) {
    return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,resulUserHandle.getCallingUserId());
}
@Override
public final int startActivityAsUser(IApplicationThread caller, String callingPackage,
        Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
        int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, int userId) {
    enforceNotIsolatedCaller("startActivity");
    userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),userId, false, ALLOW_FULL_ONLY, "startActivity", null);
    return mActivityStarter.startActivityMayWait(caller, -1, callingPackage, intent,resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,profilerInfo, null, null, bOptions, false, userId, null, null);
}

其中enforceNotIsolatedCaller方法是檢查是否屬于被隔離的對(duì)象擦酌。mUserController.handleIncomingUser檢查操作權(quán)限然后返回一個(gè)用戶id(涉及到Linux俱诸,有興趣的同學(xué)可以翻閱翻閱,這不是我們的重點(diǎn))赊舶,startActivityAsUser方法大概就是檢測下權(quán)限睁搭,然后返回由mActivityStarter(ActivityStarter赶诊,可以這么說,關(guān)于Activity啟動(dòng)的所有信息都在這了介袜,然后根據(jù)信息把Activity具體分配到哪個(gè)任務(wù)棧)調(diào)用的startActivityMayWait方法甫何。


image

繼續(xù)跟蹤ActivityStarter的startActivityMayWait的方法出吹。

final int startActivityMayWait(IApplicationThread caller, int callingUid,
            String callingPackage, Intent intent, String resolvedType,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            IBinder resultTo, String resultWho, int requestCode, int startFlags,
            ProfilerInfo profilerInfo, IActivityManager.WaitResult outResult, Configuration config,
            Bundle bOptions, boolean ignoreTargetSecurity, int userId,
            IActivityContainer iContainer, TaskRecord inTask) {
    ...
    ResolveInfo rInfo = mSupervisor.resolveIntent(intent, resolvedType, userId);
    ...
    ActivityInfo aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, profilerInfo);
    ...
    final ProcessRecord heavy = mService.mHeavyWeightProcess;
    if (heavy != null && (heavy.info.uid != aInfo.applicationInfo.uid
            || !heavy.processName.equals(aInfo.processName))) {
        ...
    }
    ...
    int res = startActivityLocked(caller, intent, ephemeralIntent, resolvedType,
            aInfo, rInfo, voiceSession, voiceInteractor,
            resultTo, resultWho, requestCode, callingPid,
            callingUid, callingPackage, realCallingPid, realCallingUid, startFlags,
            options, ignoreTargetSecurity, componentSpecified, outRecord, container,
            inTask);
    ...
}
final int startActivityLocked(IApplicationThread caller, Intent intent, Intent ephemeralIntent,
            String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid,
            String callingPackage, int realCallingPid, int realCallingUid, int startFlags,
            ActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified,
            ActivityRecord[] outActivity, ActivityStackSupervisor.ActivityContainer container,
            TaskRecord inTask) {
    ...
    ProcessRecord callerApp = null;
    ...
    ActivityRecord sourceRecord = null;
    ActivityRecord resultRecord = null;
    ...
    ActivityRecord r = new ActivityRecord(mService, callerApp, callingUid, callingPackage,
            intent, resolvedType, aInfo, mService.mConfiguration, resultRecord, resultWho,
            requestCode, componentSpecified, voiceSession != null, mSupervisor, container,
            options, sourceRecord);
    ...
    err = startActivityUnchecked(r, sourceRecord, voiceSession, voiceInteractor, startFlags,
            true, options, inTask);
    ...
    return err;
}

繼續(xù)跟進(jìn)核心方法 startActivityUnchecked()

private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask) {
    setInitialState(r, options, inTask, doResume, startFlags, sourceRecord, voiceSession,
    voiceInteractor);
    computeLaunchingTaskFlags();
    ...
    mIntent.setFlags(mLaunchFlags);
    mReusedActivity = getReusableIntentActivity();
    ...
    boolean newTask = false;
    ...
     if (mStartActivity.resultTo == null && mInTask == null && !mAddingToTask
        && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
        newTask = true;
        ...
    }
    ...
    mTargetStack.startActivityLocked(mStartActivity, newTask, mKeepCurTransition, mOptions);
    ...
    mSupervisor.resumeFocusedStackTopActivityLocked(mTargetStack, mStartActivity,
                        mOptions);
    ...
}

先看setInitialState

private void setInitialState(ActivityRecord r, ActivityOptions options, TaskRecord inTask,
            boolean doResume, int startFlags, ActivityRecord sourceRecord,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor) {
    reset();
    mStartActivity = r;
    mIntent = r.intent;
    mOptions = options;
    mCallingUid = r.launchedFromUid;
    mSourceRecord = sourceRecord;
    mVoiceSession = voiceSession;
    ...
    mLaunchSingleTop = r.launchMode == LAUNCH_SINGLE_TOP;
    mLaunchSingleInstance = r.launchMode == LAUNCH_SINGLE_INSTANCE;
    mLaunchSingleTask = r.launchMode == LAUNCH_SINGLE_TASK;
    mLaunchFlags = adjustLaunchFlagsToDocumentMode(
            r, mLaunchSingleInstance, mLaunchSingleTask, mIntent.getFlags());
     ...
}

這里初始化了比較重要的mStartActivity遇伞,mIntent,mCallingUid捶牢,mSourceRecord鸠珠,mLaunchFlags。mLaunchFlags用來記錄我們Activity的啟動(dòng)方式秋麸,省略部分都是根據(jù)啟動(dòng)方式來初始化一堆變量或進(jìn)行操作渐排。

final void startActivityLocked(ActivityRecord r, boolean newTask, boolean keepCurTransition,
            ActivityOptions options) {
    ...
    mWindowManager.setAppVisibility(r.appToken, true);
    ...
}

mWindowManager.setAppVisibility(r.appToken, true);這句話表示這個(gè)Activity已經(jīng)具備了顯示的條件。接下來我們長話短說灸蟆,大體來看已經(jīng)差不多了驯耻,通知后,我們會(huì)調(diào)用ActivityStackSupervisor的resumeFocusedStackTopActivityLocked方法炒考,接著調(diào)ActivityStack的resumeTopActivityUncheckedLocked方法可缚,再調(diào)自己的resumeTopActivityInnerLocked方法,如果Activity已存在需要可見狀態(tài)斋枢,那么會(huì)調(diào)IApplicationThread的scheduleResumeActivity方法帘靡,且經(jīng)過一系列判斷可見Activity;反之調(diào)ActivityStackSupervisor的startSpecificActivityLocked的方法瓤帚,那么繼續(xù)會(huì)調(diào)realStartActivityLocked的方法


image

終于調(diào)ApplicationThread的scheduleLaunchActivity方法啦描姚!

@Override
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
        ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
        CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
        int procState, Bundle state, PersistableBundle persistentState,
        List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
        boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
    updateProcessState(procState, false);
    ActivityClientRecord r = new ActivityClientRecord();
    r.token = token;
    r.ident = ident;
    r.intent = intent;
    r.referrer = referrer;
    r.voiceInteractor = voiceInteractor;
    r.activityInfo = info;
    r.compatInfo = compatInfo;
    r.state = state;
    r.persistentState = persistentState;
    r.pendingResults = pendingResults;
    r.pendingIntents = pendingNewIntents;
    r.startsNotResumed = notResumed;
    r.isForward = isForward;
    r.profilerInfo = profilerInfo;
    r.overrideConfig = overrideConfig;
    updatePendingConfiguration(curConfig);
    sendMessage(H.LAUNCH_ACTIVITY, r);
}

看最后,handler發(fā)送message戈次,那么這個(gè)handler是什么呢轩勘?

private class H extends Handler {
    ...
    public void handleMessage(Message msg) {
        if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
        switch (msg.what) {
            case LAUNCH_ACTIVITY: {
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
                r.packageInfo = getPackageInfoNoCheck(
                        r.activityInfo.applicationInfo, r.compatInfo);
                handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            } break;
            ...
    }
}
private class H extends Handler {
    ...
    public void handleMessage(Message msg) {
        if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
        switch (msg.what) {
            case LAUNCH_ACTIVITY: {
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
                r.packageInfo = getPackageInfoNoCheck(
                        r.activityInfo.applicationInfo, r.compatInfo);
                handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            } break;
            ...
    }
}

我們直接看我們發(fā)的消息,它調(diào)用了ActivityThread的handleLaunchActivity方法怯邪。

WindowManagerGlobal.initialize();//初始化WMS
Activity a = performLaunchActivity(r, customIntent);//創(chuàng)建并啟動(dòng)
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ActivityInfo aInfo = r.activityInfo;//取出組件信息绊寻,并一頓操作
    ...
    Activity activity = null;
    try {
        java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
        activity = mInstrumentation.newActivity(
                cl, component.getClassName(), r.intent);
         //開始創(chuàng)建Activity實(shí)例,通過類加載器創(chuàng)建擎颖,看參數(shù)就知道了
         ...
         Application app = r.packageInfo.makeApplication(false, mInstrumentation);
         //獲取Application
         ...
         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);
         //與window建立關(guān)聯(lián)
         ...
         mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
         //callActivityOnCreate->activity.performCreate->onCreate榛斯,之后就很熟悉了
         ...
    }
}
public Application makeApplication(boolean forceDefaultAppClass,
            Instrumentation instrumentation) {
    if (mApplication != null) {
        return mApplication;
    }
    //如果mApplication不為空則直接返回,這也是為什么Application為單例
    ...
    Application app = null;
    ...
    ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
    app = mActivityThread.mInstrumentation.newApplication(
                cl, appClass, appContext);
    //創(chuàng)建Application搂捧,跟Activity一樣驮俗,都是用類加載器創(chuàng)建
    ...
    mApplication = app;//保存下來
    ...
    instrumentation.callApplicationOnCreate(app);
    //callApplicationOnCreate->onCreate
    ...
}
image

總結(jié):

image

至此,activity就啟動(dòng)了允跑,至于activity啟動(dòng)之后王凑,又經(jīng)歷了哪些過程搪柑,可以看我的另一篇文章 Android 中的setContentView源碼解析

參考博客

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末索烹,一起剝皮案震驚了整個(gè)濱河市工碾,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌百姓,老刑警劉巖渊额,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異垒拢,居然都是意外死亡旬迹,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門求类,熙熙樓的掌柜王于貴愁眉苦臉地迎上來奔垦,“玉大人,你說我怎么就攤上這事尸疆〈涣裕” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵寿弱,是天一觀的道長犯眠。 經(jīng)常有香客問我,道長脖捻,這世上最難降的妖魔是什么阔逼? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮地沮,結(jié)果婚禮上嗜浮,老公的妹妹穿的比我還像新娘。我一直安慰自己摩疑,他們只是感情好危融,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著雷袋,像睡著了一般吉殃。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上楷怒,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天蛋勺,我揣著相機(jī)與錄音,去河邊找鬼鸠删。 笑死抱完,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的刃泡。 我是一名探鬼主播巧娱,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼碉怔,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了禁添?” 一聲冷哼從身側(cè)響起撮胧,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎老翘,沒想到半個(gè)月后芹啥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡酪捡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年叁征,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了纳账。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片逛薇。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖疏虫,靈堂內(nèi)的尸體忽然破棺而出永罚,到底是詐尸還是另有隱情,我是刑警寧澤卧秘,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布呢袱,位于F島的核電站,受9級(jí)特大地震影響翅敌,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一关带、第九天 我趴在偏房一處隱蔽的房頂上張望推捐。 院中可真熱鬧,春花似錦遭顶、人聲如沸张峰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽喘批。三九已至,卻和暖如春铣揉,著一層夾襖步出監(jiān)牢的瞬間饶深,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國打工逛拱, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留敌厘,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓橘券,卻偏偏與公主長得像额湘,于是被迫代替她去往敵國和親卿吐。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

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