Android源碼分析之App啟動流程(二)

續(xù)前節(jié)拇砰,我們已經(jīng)知道咖熟,Zygote孵化完進程之后會invoke ActivityThread#main方法做鹰,現(xiàn)在繼續(xù)看剩下的部分。該方法代碼如下:

/frameworks/base/core/java/android/app/ActivityThread.java

public static void main(String[] args) {
    ...

    Looper.prepareMainLooper();

    ActivityThread thread = new ActivityThread();
    thread.attach(false);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    ...
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

可以看到這里首先開啟了Looper消息循環(huán)泞莉,這部分知識我們以后再來分析哪雕。下面主要看下ActivityThread#attach(boolean)方法,代碼如下:

private void attach(boolean system) {
    sCurrentActivityThread = this;
    mSystemThread = system;
    if (!system) {
        ...
        RuntimeInit.setApplicationObject(mAppThread.asBinder());
        final IActivityManager mgr = ActivityManagerNative.getDefault();
        try {
            mgr.attachApplication(mAppThread);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
        ...
    } else {
        ...
    }

    ...
}

因為不是系統(tǒng)APP鲫趁,所以只需要看if里的代碼就好了斯嚎。之前已經(jīng)分析過,ActivityManagerNative#getDefault()方法獲取到的是ActivityManagerProxy實例挨厚,然后這個實例通過BinderProxyActivityManagerService進行通信堡僻。接下來代碼如下:

/frameworks/base/core/java/android/app/ActivityManagerNative.java

public void attachApplication(IApplicationThread app) throws RemoteException
{
    Parcel data = Parcel.obtain();
    Parcel reply = Parcel.obtain();
    data.writeInterfaceToken(IActivityManager.descriptor);
    data.writeStrongBinder(app.asBinder());
    mRemote.transact(ATTACH_APPLICATION_TRANSACTION, data, reply, 0);
    reply.readException();
    data.recycle();
    reply.recycle();
}

和之前的做法非常一致,接下來會執(zhí)行到ActivityManagerService#onTransact方法疫剃,而相關(guān)代碼在父類ActivityManagerNative中钉疫,代碼如下:

case ATTACH_APPLICATION_TRANSACTION: {
    data.enforceInterface(IActivityManager.descriptor);
    IApplicationThread app = ApplicationThreadNative.asInterface(
            data.readStrongBinder());
    if (app != null) {
        attachApplication(app);
    }
    reply.writeNoException();
    return true;
}

這里看到IApplicationThread實例是通過ApplicationThreadNative#asInterface獲取的,根據(jù)先前的經(jīng)驗巢价,這里應(yīng)該是一個ApplicationThreadProxy實例牲阁,相關(guān)代碼如下:

/frameworks/base/core/java/android/app/ApplicationThreadNative.java

static public IApplicationThread asInterface(IBinder obj) {
    if (obj == null) {
        return null;
    }
    IApplicationThread in =
        (IApplicationThread)obj.queryLocalInterface(descriptor);
    if (in != null) {
        return in;
    }

    return new ApplicationThreadProxy(obj);
}

接下來就可以確認(rèn),ActivityManagerService#attachApplication(IApplicationThread)方法中的IApplicationThread實例是ApplicationThreadProxy壤躲,代碼如下:

/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

public final void attachApplication(IApplicationThread thread) {
    synchronized (this) {
        int callingPid = Binder.getCallingPid();
        final long origId = Binder.clearCallingIdentity();
        attachApplicationLocked(thread, callingPid);
        Binder.restoreCallingIdentity(origId);
    }
}

private final boolean attachApplicationLocked(IApplicationThread thread,
        int pid) {
    ...
    try {
        ...
        thread.bindApplication(processName, appInfo, providers, app.instrumentationClass,
                profilerInfo, app.instrumentationArguments, app.instrumentationWatcher,
                app.instrumentationUiAutomationConnection, testMode,
                mBinderTransactionTrackingEnabled, enableTrackAllocation,
                isRestrictedBackupMode || !normalMode, app.persistent,
                new Configuration(mConfiguration), app.compat,
                getCommonServicesLocked(app.isolated),
                mCoreSettingsObserver.getCoreSettingsLocked());
        updateLruProcessLocked(app, false, null);
        app.lastRequestedGc = app.lastLowMemory = SystemClock.uptimeMillis();
    } catch (Exception e) {
        ...
    }

    ...
    
    // See if the top visible activity is waiting to run in this process...
    if (normalMode) {
        try {
            if (mStackSupervisor.attachApplicationLocked(app)) {
                didSomething = true;
            }
        } catch (Exception e) {
            Slog.wtf(TAG, "Exception thrown launching activities in " + app, e);
            badApp = true;
        }
    }
    ...
    return true;
}

然后看下這里ApplicationThreadProxy#bindApplication方法咨油,代碼如下:

/frameworks/base/core/java/android/app/ApplicationThreadNative.java

public final void bindApplication(String packageName, ApplicationInfo info,
        List<ProviderInfo> providers, ComponentName testName, ProfilerInfo profilerInfo,
        Bundle testArgs, IInstrumentationWatcher testWatcher,
        IUiAutomationConnection uiAutomationConnection, int debugMode,
        boolean enableBinderTracking, boolean trackAllocation, boolean restrictedBackupMode,
        boolean persistent, Configuration config, CompatibilityInfo compatInfo,
        Map<String, IBinder> services, Bundle coreSettings) throws RemoteException {
    ...
    mRemote.transact(BIND_APPLICATION_TRANSACTION, data, null,
            IBinder.FLAG_ONEWAY);
    data.recycle();
}

這里的思路和ActivityManagerNative十分一致,通過ApplicationThreadProxyBinderProxy來完成ApplicationThreadActivityManagerService通信柒爵,然后由ApplicationThreadNative處理返回的結(jié)果役电,并調(diào)用ApplicationThread的對應(yīng)方法。接下來看下ApplicationThread#bindApplication方法:

/frameworks/base/core/java/android/app/ActivityThread.java

public final void bindApplication(String processName, ApplicationInfo appInfo,
        List<ProviderInfo> providers, ComponentName instrumentationName,
        ProfilerInfo profilerInfo, Bundle instrumentationArgs,
        IInstrumentationWatcher instrumentationWatcher,
        IUiAutomationConnection instrumentationUiConnection, int debugMode,
        boolean enableBinderTracking, boolean trackAllocation,
        boolean isRestrictedBackupMode, boolean persistent, Configuration config,
        CompatibilityInfo compatInfo, Map<String, IBinder> services, Bundle coreSettings) {

    if (services != null) {
        // Setup the service cache in the ServiceManager
        ServiceManager.initServiceCache(services);
    }

    setCoreSettings(coreSettings);

    AppBindData data = new AppBindData();
    data.processName = processName;
    data.appInfo = appInfo;
    data.providers = providers;
    data.instrumentationName = instrumentationName;
    data.instrumentationArgs = instrumentationArgs;
    data.instrumentationWatcher = instrumentationWatcher;
    data.instrumentationUiAutomationConnection = instrumentationUiConnection;
    data.debugMode = debugMode;
    data.enableBinderTracking = enableBinderTracking;
    data.trackAllocation = trackAllocation;
    data.restrictedBackupMode = isRestrictedBackupMode;
    data.persistent = persistent;
    data.config = config;
    data.compatInfo = compatInfo;
    data.initProfilerInfo = profilerInfo;
    sendMessage(H.BIND_APPLICATION, data);
}

這里把一些需要設(shè)置的屬性棉胀,通過一個叫AppBindData的方式包裝起來法瑟,然后通過Handler方式,發(fā)送給了一個叫HHandler實例唁奢,這個實例在ActivityThread中霎挟,我們看下它在handleMessage中對BIND_APPLICATION這個消息的處理:

case BIND_APPLICATION:
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
    AppBindData data = (AppBindData)msg.obj;
    handleBindApplication(data);
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    break;

private void handleBindApplication(AppBindData data) {
    ...
    // Continue loading instrumentation.
    if (ii != null) {
        ...
        mInstrumentation.init(this, instrContext, appContext, component,
                data.instrumentationWatcher, data.instrumentationUiAutomationConnection);

        if (mProfiler.profileFile != null && !ii.handleProfiling
                && mProfiler.profileFd == null) {
            mProfiler.handlingProfiling = true;
            final File file = new File(mProfiler.profileFile);
            file.getParentFile().mkdirs();
            Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024);
        }
    } else {
        mInstrumentation = new Instrumentation();
    }

    ...

    try {
        // If the app is being launched for full backup or restore, bring it up in
        // a restricted environment with the base application class.
        Application app = data.info.makeApplication(data.restrictedBackupMode, null);
        mInitialApplication = app;

        ...

        // Do this after providers, since instrumentation tests generally start their
        // test thread at this point, and we don't want that racing.
        try {
            mInstrumentation.onCreate(data.instrumentationArgs);
        }
        catch (Exception e) {
            ...
        }

        try {
            mInstrumentation.callApplicationOnCreate(app);
        } catch (Exception e) {
            ...
        }
    } finally {
        StrictMode.setThreadPolicy(savedPolicy);
    }
}

handleBindApplication做了非常多的工作,APP的名稱麻掸、資源酥夭、屏幕相關(guān)等操作都在這里進行了處理,但這不是我們今天關(guān)注的重點脊奋,我們主要看最下邊的try-catch熬北,這里首先創(chuàng)建了一個Application實例,然后調(diào)用了mInstrumentation.onCreatemInstrumentation.callApplicationOnCreate诚隙,這兩個方法一個是空的讶隐,一個調(diào)用了Application#onCreate(),沒有做更多的事情久又,這里暫時不關(guān)注它巫延。這里這個Application實例是通過LoadedApk#makeApplication完成的效五,代碼如下:

/frameworks/base/core/java/android/app/LoadedApk.java

public Application makeApplication(boolean forceDefaultAppClass,
        Instrumentation instrumentation) {
    if (mApplication != null) {
        return mApplication;
    }

    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "makeApplication");

    Application app = null;

    String appClass = mApplicationInfo.className;
    if (forceDefaultAppClass || (appClass == null)) {
        appClass = "android.app.Application";
    }

    try {
        java.lang.ClassLoader cl = getClassLoader();
        if (!mPackageName.equals("android")) {
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                    "initializeJavaContextClassLoader");
            initializeJavaContextClassLoader();
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        }
        ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
        app = mActivityThread.mInstrumentation.newApplication(
                cl, appClass, appContext);
        appContext.setOuterContext(app);
    } catch (Exception e) {
        ...
    }
    mActivityThread.mAllApplications.add(app);
    mApplication = app;

    if (instrumentation != null) {
        try {
            instrumentation.callApplicationOnCreate(app);
        } catch (Exception e) {
            ...
        }
    }

    ...
    return app;
}

可以看到這個Application是通過Instrumentation#newApplication方法完成的,代碼如下:

/frameworks/base/core/java/android/app/Instrumentation.java

public Application newApplication(ClassLoader cl, String className, Context context)
        throws InstantiationException, IllegalAccessException, 
        ClassNotFoundException {
    return newApplication(cl.loadClass(className), context);
}

static public Application newApplication(Class<?> clazz, Context context)
        throws InstantiationException, IllegalAccessException, 
        ClassNotFoundException {
    Application app = (Application)clazz.newInstance();
    app.attach(context);
    return app;
}

這里看到Application是通過ClassLoader創(chuàng)建出來的炉峰,隨后調(diào)用了它的attach方法畏妖,該方法如下:

/frameworks/base/core/java/android/app/Application.java

/* package */ final void attach(Context context) {
    attachBaseContext(context);
    mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}

至此,Application就創(chuàng)建完畢了疼阔,并且Application#onCreate()方法也執(zhí)行完畢了戒劫。接下來就是啟動Activity的過程,在之前分析ActivityManagerService#attachApplicationLocked方法中竿开,執(zhí)行完畢thread.bindApplication之后又執(zhí)行了mStackSupervisor.attachApplicationLocked(app)谱仪,也就是創(chuàng)建完Application之后玻熙,啟動需要運行的Activity否彩,相應(yīng)的代碼如下:

/frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java

boolean attachApplicationLocked(ProcessRecord app) throws RemoteException {
    final String processName = app.processName;
    boolean didSomething = false;
    for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
        ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
        for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
            final ActivityStack stack = stacks.get(stackNdx);
            if (!isFocusedStack(stack)) {
                continue;
            }
            ActivityRecord hr = stack.topRunningActivityLocked();
            if (hr != null) {
                if (hr.app == null && app.uid == hr.info.applicationInfo.uid
                        && processName.equals(hr.processName)) {
                    try {
                        if (realStartActivityLocked(hr, app, true, true)) {
                            didSomething = true;
                        }
                    } catch (RemoteException e) {
                        Slog.w(TAG, "Exception in new application when starting activity "
                                + hr.intent.getComponent().flattenToShortString(), e);
                        throw e;
                    }
                }
            }
        }
    }
    if (!didSomething) {
        ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
    }
    return didSomething;
}

因為是首次運行,所以會執(zhí)行到if (realStartActivityLocked(hr, app, true, true))代碼塊嗦随,如下所示:

final boolean realStartActivityLocked(ActivityRecord r, ProcessRecord app,
        boolean andResume, boolean checkConfig) throws RemoteException {
    ...
    final ActivityStack stack = task.stack;
    try {
        ...
        app.forceProcessStateUpTo(mService.mTopProcessState);
        app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
                System.identityHashCode(r), r.info, new Configuration(mService.mConfiguration),
                new Configuration(task.mOverrideConfig), r.compat, r.launchedFromPackage,
                task.voiceInteractor, app.repProcState, r.icicle, r.persistentState, results,
                newIntents, !andResume, mService.isNextTransitionForward(), profilerInfo);

        ...
    } catch (RemoteException e) {
        ...
    }
    ...
    return true;
}

現(xiàn)在列荔,終于開始加載Activity了,代碼如下:

/frameworks/base/core/java/android/app/ActivityThread.java

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

    ...
    sendMessage(H.LAUNCH_ACTIVITY, r);
}

可以看到這里依然是通過H來交互的枚尼,代碼如下:

case LAUNCH_ACTIVITY: {
    ...
    
    handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
    ...

    Activity a = performLaunchActivity(r, customIntent);

    if (a != null) {
        r.createdConfig = new Configuration(mConfiguration);
        reportSizeConfigurations(r);
        Bundle oldState = r.state;
        handleResumeActivity(r.token, false, r.isForward,
                !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
        ...
    } else {
        ...
    }
}

這里首先通過performLaunchActivity獲取到需要加載的Activity贴浙,然后調(diào)用handleResumeActivity方法。我們先看前者署恍,代碼如下:

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ...

    Activity activity = null;
    try {
        java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
        activity = mInstrumentation.newActivity(
                cl, component.getClassName(), r.intent);
        ...
    } catch (Exception e) {
        ...
    }

    try {
        Application app = r.packageInfo.makeApplication(false, mInstrumentation);
        ...

        if (activity != null) {
            Context appContext = createBaseContextForActivity(r, activity);
            ...
            
            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);
            ...
            if (theme != 0) {
                activity.setTheme(theme);
            }

            activity.mCalled = false;
            if (r.isPersistable()) {
                mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
            } else {
                mInstrumentation.callActivityOnCreate(activity, r.state);
            }
            ...
            if (!r.activity.mFinished) {
                activity.performStart();
                r.stopped = false;
            }
            if (!r.activity.mFinished) {
                if (r.isPersistable()) {
                    if (r.state != null || r.persistentState != null) {
                        mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
                                r.persistentState);
                    }
                } else if (r.state != null) {
                    mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
                }
            }
            if (!r.activity.mFinished) {
                activity.mCalled = false;
                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnPostCreate(activity, r.state,
                            r.persistentState);
                } else {
                    mInstrumentation.callActivityOnPostCreate(activity, r.state);
                }
                ...
            }
        }
        r.paused = true;

        mActivities.put(r.token, r);

    } catch (SuperNotCalledException e) {
        throw e;

    } catch (Exception e) {
        ...
    }

    return activity;
}

這里首先通過Instrumentation#newActivity方法創(chuàng)建了一個Activity崎溃,然后調(diào)用了它的一些生命周期方法,包括它的Theme盯质、Window等都是在這時候設(shè)置的袁串,還是先看下創(chuàng)建的過程,代碼如下:

public Activity newActivity(ClassLoader cl, String className,
        Intent intent)
        throws InstantiationException, IllegalAccessException,
        ClassNotFoundException {
    return (Activity)cl.loadClass(className).newInstance();
}

和創(chuàng)建Application一樣呼巷,也是通過ClassLoader完成的囱修,Activity創(chuàng)建完成后接著調(diào)用了Activity#attach方法,在最開始時候提到過這個方法王悍,并說它需要的ActivityThread是通過這個方法傳入的破镰,這里終于找到調(diào)用的地方了。接下來依次調(diào)用了ActivityonCreate压储、onStart鲜漩、onRestoreInstanceStateonPostCreate方法,最后回到handleLaunchActivity集惋,調(diào)用了handleResumeActivity宇整。接下來的代碼就涉及到了Activity加載View了,我們之后再分析芋膘。

上一篇:Android源碼分析之App啟動流程(一)

下一篇:Android源碼分析之Activity啟動與View繪制流程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鳞青,一起剝皮案震驚了整個濱河市霸饲,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌臂拓,老刑警劉巖厚脉,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異胶惰,居然都是意外死亡傻工,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門孵滞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來中捆,“玉大人,你說我怎么就攤上這事坊饶⌒刮保” “怎么了?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵匿级,是天一觀的道長蟋滴。 經(jīng)常有香客問我,道長痘绎,這世上最難降的妖魔是什么津函? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮孤页,結(jié)果婚禮上尔苦,老公的妹妹穿的比我還像新娘。我一直安慰自己行施,他們只是感情好允坚,可當(dāng)我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著悲龟,像睡著了一般屋讶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上须教,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天皿渗,我揣著相機與錄音,去河邊找鬼轻腺。 笑死乐疆,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的贬养。 我是一名探鬼主播挤土,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼误算!你這毒婦竟也來了仰美?” 一聲冷哼從身側(cè)響起迷殿,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎咖杂,沒想到半個月后庆寺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡诉字,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年懦尝,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片壤圃。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡陵霉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出伍绳,到底是詐尸還是另有隱情踊挠,我是刑警寧澤,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布墨叛,位于F島的核電站止毕,受9級特大地震影響模蜡,放射性物質(zhì)發(fā)生泄漏漠趁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一忍疾、第九天 我趴在偏房一處隱蔽的房頂上張望闯传。 院中可真熱鬧,春花似錦卤妒、人聲如沸甥绿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽共缕。三九已至,卻和暖如春士复,著一層夾襖步出監(jiān)牢的瞬間图谷,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工阱洪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留便贵,地道東北人。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓冗荸,卻偏偏與公主長得像承璃,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蚌本,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,941評論 2 355

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