Intent Flags VS launchMode

  本文试图说明 Activity launchMode 原理,各种常用的 Intent Flag 原理 ,以及launchMode 和 Intent Flag 的区别,相似点等;以及有可能的使用场景(源码基于 7.0 分析)。

SingleTask VS Intent.FLAG_NEW_TASK

  Activity 中有四大启动模式,较常用的一种为SingleTask官方注释如下:

"singleTask" can only begin a task. They are always at the root of 
the activity stack. Moreover, the device can hold only one instance of 
the activity at a time — only one such task.

  大致翻译如下:如果一个Activity设置为SingleTask,那么它必须在当前栈的底部,并且在当前栈中,只能有一个该Activity的实例。
  上述说法其实是错误的,一个被SingleTask标识的Activity是可以不在栈底的。并且在一个栈中,只能有一个该Activity实例。那么 SingleTask 的原理是什么呢。
  不同应用的Activity以及Activity的跳转是由AMS来完成的,当启动一个Activity时,最后都是由 AMS 来完成,最后会调用到 ActivityStarter的startActivityUnchecked()方法,该方法代码如下:

  // Note: This method should only be called from {@link startActivity}.
    private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
                                       IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
                                       int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
                                       ActivityRecord[] outActivity) {
           //  1.1 解析 LaunchMode , 设置初始化状态
           setInitialState(r, options, inTask, doResume, startFlags, sourceRecord, voiceSession,
                voiceInteractor);
           // 1.2 实现 LaunchMode 到 LaunchFlag 转换操作
           computeLaunchingTaskFlags();
           ...
           // 1.3 此处为关键点,寻找可重用的 ActivityRecord实例
           ActivityRecord reusedActivity = getReusableIntentActivity();
           ...
           // 1.4 如果 reusedActivity 不为 null ,走相关逻辑
           if(reusedActivity != null){
                 //后续具体分析..
           }
           ...
     }               

  应用在真正启动一个 Activity 之前,会先走上述1.1~1.3的初始化过程,下面分别来解释一下 1.1 ~1.3具体做了哪些工作。

1.1:setInitialState() 初始化工作

在 setInitialState() 过程中,会首先做一些初始化的操作,如记录该 ActivityRecord 的启动模式,activityResult 校验等等,代码如下:

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;
        mVoiceInteractor = voiceInteractor;

        mLaunchBounds = getOverrideBounds(r, options, inTask);
        // 常见的启动模式的标识,赋值给成员变量
        // 标识将要启动的 Activity 的 LaunchMode
        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());
        mLaunchTaskBehind = r.mLaunchTaskBehind
                && !mLaunchSingleTask && !mLaunchSingleInstance
                && (mLaunchFlags & FLAG_ACTIVITY_NEW_DOCUMENT) != 0;
         
        // 此处也是一个重要方法,与 startActivityForResult() 密切相关
        sendNewTaskResultRequestIfNeeded();
        
        ...
    }

  从上述初始化操作可以看出,将目标Activity的Launch Mode 赋值给相关的成员变量,并且在之后有一步 sendNewTaskResultRequestIfNeeded() 的操作,那么这个操作是干什么的呢?该方法代码如下:

private void sendNewTaskResultRequestIfNeeded() {
        if (mStartActivity.resultTo != null && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0
                && mStartActivity.resultTo.task.stack != null) {
            // For whatever reason this activity is being launched into a new task...
            // yet the caller has requested a result back.  Well, that is pretty messed up,
            // so instead immediately send back a cancel and let the new task continue launched
            // as normal without a dependency on its originator.
            Slog.w(TAG, "Activity is launching as a new task, so cancelling activity result.");
            mStartActivity.resultTo.task.stack.sendActivityResultLocked(-1, mStartActivity.resultTo,
                    mStartActivity.resultWho, mStartActivity.requestCode, RESULT_CANCELED, null);
            mStartActivity.resultTo = null;
        }
    }

  从该方法注释可以看出,如果以startActivityForResult() 方式启动(RequestCode > 0),并且Intent Flag 有设置 FLAG_ACTIVITY_NEW_TASK 标识,那么当前 Activity 会立即收到 CANCEL 回调。
  上述问题也是一个比较常见的问题,从代码可以看出,如果想要正确的收到 onActivityResult() 回调,那么在启动 Activity 的 Intent 中,一定不能加 FLAG_ACTIVITY_NEW_TASK 标识。
  分析完上述 1.1 之后,接着来看 1.2 computeLaunchingTaskFlags() 具体做了什么事情。

1.2 : computeLaunchingTaskFlags()

代码如下:

private void computeLaunchingTaskFlags() {
        // If the caller is not coming from another activity, but has given us an explicit task into
        // which they would like us to launch the new activity, then let's see about doing that.
        // 此处不在本文的分析范围内,如果用户是通过 Application Context 或者 
        // Service 启动的 目标 Activity ,并且有指定要宿主到哪个 task
        // 该判断条件才会成立。
        if (mSourceRecord == null && mInTask != null && mInTask.stack != null) {
            final Intent baseIntent = mInTask.getBaseIntent();
            final ActivityRecord root = mInTask.getRootActivity();
            if (baseIntent == null) {
                ActivityOptions.abort(mOptions);
                throw new IllegalArgumentException("Launching into task without base intent: "
                        + mInTask);
            }

            // If this task is empty, then we are adding the first activity -- it
            // determines the root, and must be launching as a NEW_TASK.
            if (mLaunchSingleInstance || mLaunchSingleTask) {
                if (!baseIntent.getComponent().equals(mStartActivity.intent.getComponent())) {
                    ActivityOptions.abort(mOptions);
                    throw new IllegalArgumentException("Trying to launch singleInstance/Task "
                            + mStartActivity + " into different task " + mInTask);
                }
                if (root != null) {
                    ActivityOptions.abort(mOptions);
                    throw new IllegalArgumentException("Caller with mInTask " + mInTask
                            + " has root " + root + " but target is singleInstance/Task");
                }
            }

            // If task is empty, then adopt the interesting intent launch flags in to the
            // activity being started.
            if (root == null) {
                final int flagsOfInterest = FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK
                        | FLAG_ACTIVITY_NEW_DOCUMENT | FLAG_ACTIVITY_RETAIN_IN_RECENTS;
                mLaunchFlags = (mLaunchFlags & ~flagsOfInterest)
                        | (baseIntent.getFlags() & flagsOfInterest);
                mIntent.setFlags(mLaunchFlags);
                mInTask.setIntent(mStartActivity);
                mAddingToTask = true;

                // If the task is not empty and the caller is asking to start it as the root of
                // a new task, then we don't actually want to start this on the task. We will
                // bring the task to the front, and possibly give it a new intent.
            } else if ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
                mAddingToTask = false;

            } else {
                mAddingToTask = true;
            }

            mReuseTask = mInTask;
        } else {
            mInTask = null;
            // Launch ResolverActivity in the source task, so that it stays in the task bounds
            // when in freeform workspace.
            // Also put noDisplay activities in the source task. These by itself can be placed
            // in any task/stack, however it could launch other activities like ResolverActivity,
            // and we want those to stay in the original task.
            if ((mStartActivity.isResolverActivity() || mStartActivity.noDisplay) && mSourceRecord != null
                    && mSourceRecord.isFreeform())  {
                mAddingToTask = true;
            }
        }

        // 重点分析此处 , 如果启动目标 Activity 为 Activity Context ,那么会为 mSourceRecord 
        // 赋值,如果是从非 Activity 启动,比如 Application Context 等,那么mSourceRecord
        // 为 null 。
        if (mInTask == null) {
            if (mSourceRecord == null) {
                // This activity is not being started from another...  in this
                // case we -always- start a new task.
                // 如果是从非 Activity 入口过来,强制加上 FLAG_NEW_TASK 标识。
                if ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) == 0 && mInTask == null) {
                    Slog.w(TAG, "startActivity called from non-Activity context; forcing " +
                            "Intent.FLAG_ACTIVITY_NEW_TASK for: " + mIntent);
                    mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
                }
            } else if (mSourceRecord.launchMode == LAUNCH_SINGLE_INSTANCE) {
                // The original activity who is starting us is running as a single
                // instance...  this new activity it is starting must go on its
                // own task.
                // 如果 当前 Activity Launch Mode 为 singleInstance,代表不允许目标
                // Activity 与宿主 Activity 栈实例共用一个 task 栈,所以也会强制加上
                // FLAG_ACTIVITY_NEW_TASK 标识。
                mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
            } else if (mLaunchSingleInstance || mLaunchSingleTask) {
                // The activity being started is a single instance...  it always
                // gets launched into its own task.
                // 如果目标 Activity 的 LaunchMode 为 singleTask 或者 singleInstance 
                // 那么也强制加上 FLAG_ACTIVITY_NEW_TASK 标识。
                mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
            }
        }
    }

  该方法的说明已经在上述代码中注释的比较清楚,总结一些,可以罗列为以下几点:

  • 如果是从非 Activity 入口过来,比如 Application Context 、Service 等 , 强制加上 FLAG_ACTIVITY_NEW_TASK 标识。
  • 如果目标 Activity 启动模式为 SingleTask 或者 SingleInstance ,那么也强制加上 FLAG_ACTIVITY_NEW_TASK 标识。
  • 如果源 Activity 启动模式为 SingleInstance , 那么肯定不允许目标 Activity 与源 Activity 公用一个 Task 栈,所以也强制加上 FLAG_ACTIVITY_NEW_TASK 标识。
1.3 :寻找可重用的 ActivityRecord 实例

getReusableIntentActivity() 方法代码如下:

/**
     * Decide whether the new activity should be inserted into an existing task. Returns null
     * if not or an ActivityRecord with the task into which the new activity should be added.
     */
    private ActivityRecord getReusableIntentActivity() {
        // We may want to try to place the new activity in to an existing task.  We always
        // do this if the target activity is singleTask or singleInstance; we will also do
        // this if NEW_TASK has been requested, and there is not an additional qualifier telling
        // us to still place it in a new task: multi task, always doc mode, or being asked to
        // launch this as a new task behind the current one.
        boolean putIntoExistingTask = ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0 &&
                (mLaunchFlags & FLAG_ACTIVITY_MULTIPLE_TASK) == 0)
                || mLaunchSingleInstance || mLaunchSingleTask;
        // If bring to front is requested, and no result is requested and we have not been given
        // an explicit task to launch in to, and we can find a task that was started with this
        // same component, then instead of launching bring that one to the front.
        putIntoExistingTask &= mInTask == null && mStartActivity.resultTo == null;
        ActivityRecord intentActivity = null;
        if (mOptions != null && mOptions.getLaunchTaskId() != -1) {
            final TaskRecord task = mSupervisor.anyTaskForIdLocked(mOptions.getLaunchTaskId());
            intentActivity = task != null ? task.getTopActivity() : null;
        } else if (putIntoExistingTask) {
            if (mLaunchSingleInstance) {
                // There can be one and only one instance of single instance activity in the
                // history, and it is always in its own unique task, so we do a special search.
               intentActivity = mSupervisor.findActivityLocked(mIntent, mStartActivity.info, false);
            } else if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0) {
                // For the launch adjacent case we only want to put the activity in an existing
                // task if the activity already exists in the history.
                intentActivity = mSupervisor.findActivityLocked(mIntent, mStartActivity.info,
                        !mLaunchSingleTask);
            } else {
                // Otherwise find the best task to put the activity in.
                intentActivity = mSupervisor.findTaskLocked(mStartActivity);
            }
        }
        return intentActivity;
    }

  从上述代码注释可以看出,该方法主要作用为判断目标 Activity 是否要插入到一个已经存在的栈中,主要判断依据罗列如下:

  • 对于 LaunchMode 为 singleTask 或 singleInstance 的 Activity,强制尝试去寻找是否已经存在一个已经存在的 Task。
  • 对于 含有 FLAG_ACTIVITY_NEW_TASK 标识的 Intent,也强制尝试去寻找。
  • 若目标 Activity LaunchMode 为 singleInstance ,强制去找是否已经存在相同的 ActivityRecord 实例。
  • 若LaunchMode 不为 singleInstance,则强制去寻找已经存在的 Task 实例,特别说明的是,此处寻找是否存在相同的 task 实例是通过判断 taskAffinity 来完成的,具体代码逻辑在 mSupervisor.findTaskLocked(mStartActivity) 方法中,此处不再详细说明。

  通过上述方法可以看出,如果一个 Activity 的 LaunchMode 被标识为 singleInstance , 那么它在 AMS 中永远只存在唯一的一份实例。对于 singleTask 或 FLAG_ACTIVITY_NEW_TASK , 都尝试先根据该 Activity 对应的 taskAffinity 去找是否已经存在相关的栈,如果存在,则将目标 Activity 放入该栈中。
  从上述1.1 ~ 1.3 的分析,发现 launchMode 为 singleTask 与 Intent FLAG_ACTIVITY_NEW_TASK 并没有什么区别,singleTask 在 1.2 computeLaunchingTaskFlags() 方法中也会强制加上 FLAG_ACTIVITY_NEW_TASK 标识,那么两者的区别到底是什么呢?

1.4 : reusedActivity 不为 null 处理

从1.3 处分析可知,对于 launchMode 为 singleTask 或者 Intent Flag 为 FLAG_ACTIVITY_NEW_TASK 的标识,都会首先尝试去找一个已经存在的栈,此处如果可以找到,则将栈中的一个 ActivityRecord 实例赋值给 reusedActivity ,然后继续走接下来的流程,代码如下:

if (reusedActivity != null) {
            ...
            // 重点分析此处方法
            setTaskFromIntentActivity(reusedActivity);
            if (!mAddingToTask && mReuseTask == null) {
                // We didn't do anything...  but it was needed (a.k.a., client don't use that
                // intent!)  And for paranoia, make sure we have correctly resumed the top activity.
                resumeTargetStackIfNeeded();
                if (outActivity != null && outActivity.length > 0) {
                    outActivity[0] = reusedActivity;
                }
                return START_TASK_TO_FRONT;
            }
        }

  重点分析 setTaskFromIntentActivity(reusedActivity) 方法,其中 reusedActivity 代表 1.3 找到的 ActivityRecord 实例, 该方法代码如下:

private void setTaskFromIntentActivity(ActivityRecord intentActivity) {
        if ((mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
                == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) {
            // The caller has requested to completely replace any existing task with its new
            // activity. Well that should not be too hard...
            // 如果 Intent flag 同时设置有 FLAG_ACTIVITY_CLEAR_TASK 与 FLAG_ACTIVITY_NEW_TASK
            // 那么会把当前的栈清空,重新开启一个栈
            mReuseTask = intentActivity.task;
            // 清空栈操作
            mReuseTask.performClearTaskLocked();
            mReuseTask.setIntent(mStartActivity);
            // When we clear the task - focus will be adjusted, which will bring another task
            // to top before we launch the activity we need. This will temporary swap their
            // mTaskToReturnTo values and we don't want to overwrite them accidentally.
            mMovedOtherTask = true;
        } else if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
                || mLaunchSingleInstance || mLaunchSingleTask) {
            // 如果目标 Activity launchMode 为 singleTask或
            // 者singleInstance , 或者 Intent flag 含有 FLAG_CLEAR_TOP 标识 ,那么首先
            // 去清除 栈里面的元素,直到找到可重用的 ActivityRecord 为止。
           // 此处是一个关键点
            ActivityRecord top = intentActivity.task.performClearTaskLocked(mStartActivity,
                    mLaunchFlags);
            if (top == null) {
                // A special case: we need to start the activity because it is not currently
                // running, and the caller has asked to clear the current task to have this
                // activity at the top.
                mAddingToTask = true;
                // Now pretend like this activity is being started by the top of its task, so it
                // is put in the right place.
                mSourceRecord = intentActivity;
                final TaskRecord task = mSourceRecord.task;
                if (task != null && task.stack == null) {
                    // Target stack got cleared when we all activities were removed above.
                    // Go ahead and reset it.
                    mTargetStack = computeStackFocus(mSourceRecord, false /* newTask */,
                            null /* bounds */, mLaunchFlags, mOptions);
                    mTargetStack.addTask(task,
                            !mLaunchTaskBehind /* toTop */, "startActivityUnchecked");
                }
            }
        } else if (mStartActivity.realActivity.equals(intentActivity.task.realActivity)) {
            // In this case the top activity on the task is the same as the one being launched,
            // so we take that as a request to bring the task to the foreground. If the top
            // activity in the task is the root activity, deliver this new intent to it if it
            // desires.
            if (((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0 || mLaunchSingleTop)
                    && intentActivity.realActivity.equals(mStartActivity.realActivity)) {
                ActivityStack.logStartActivity(AM_NEW_INTENT, mStartActivity,
                        intentActivity.task);
                if (intentActivity.frontOfTask) {
                    intentActivity.task.setIntent(mStartActivity);
                }
                intentActivity.deliverNewIntentLocked(mCallingUid, mStartActivity.intent,
                        mStartActivity.launchedFromPackage);
            } else if (!intentActivity.task.isSameIntentFilter(mStartActivity)) {
                // In this case we are launching the root activity of the task, but with a
                // different intent. We should start a new instance on top.
                mAddingToTask = true;
                mSourceRecord = intentActivity;
            }
        } else if ((mLaunchFlags & FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) == 0) {
            // In this case an activity is being launched in to an existing task, without
            // resetting that task. This is typically the situation of launching an activity
            // from a notification or shortcut. We want to place the new activity on top of the
            // current task.
            mAddingToTask = true;
            mSourceRecord = intentActivity;
        } else if (!intentActivity.task.rootWasReset) {
            // In this case we are launching into an existing task that has not yet been started
            // from its front door. The current task has been brought to the front. Ideally,
            // we'd probably like to place this new task at the bottom of its stack, but that's
            // a little hard to do with the current organization of the code so for now we'll
            // just drop it.
            intentActivity.task.setIntent(mStartActivity);
        }
    }

从上述代码的注释,可以很清楚的得到以下结论:

  • 如果 Intent flag 同时设置有 FLAG_ACTIVITY_CLEAR_TASK 与 FLAG_ACTIVITY_NEW_TASK 那么会把当前的栈清空。目标Activity 启动的时候,与栈内之前已经存在的 Activity 不是一个实例。
  • 如果目标 Activity launchMode 为 singleTask或者 singleInstance ,那么首先清除栈里面的元素,直到栈顶元素为将要启动的目标 ActivityRecord 实例为止。
  • 若Intent Flag 中包含 FLAG_ACTIVITY_CLEAR_TOP , 则也执行清栈操作,但是会把已经存在的 ActivityRecord 首先 finish掉,然后重新启动目标 ActivityRecord 实例。
  自此可以 FLAG_ACTIVITY_NEW_TASK 和 singleTask 的区别了,singleTask 会执行清除栈的操作,直到栈顶元素为已经存在的目标 ActivityRecord 实例,并不会启动一个新的 Activity 实例。如果launchMode 为标准启动模式,并且仅仅是将IntentFlag 为 FLAG_ACTIVITY_NEW_TASK ,那么仅仅只会复用这个已经存在的栈,并不会执行清除栈的操作,而是仅仅新建一个新的ActivityRecord 实例出来,把它放在栈顶而已。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容