FrameWork層源碼的分析(1)-應用程序內Activity的啟動流程

主目錄見:Android高級進階知識(這是總目錄索引)
?今天開始我們將漸漸進入Framework的學習,然后進而為以后的插件化做準備,要學會如何開發一套插件化框架,顯然這是第一步,我們今天就來講講應用程序內啟動Activity的流程,首先呢,我先把整個流程的圖給大家看看,以便于大家學習源碼的時候能有一個對照:

Activity啟動

從圖中可以看出,我們的中間跨進程交互都是用的AIDL的知識,如果大家不了解AIDL的話,那么我推薦看下從framework分析AIDL生成文件這篇文章或者看下更基礎點的文章,大家自己決定,想必大家看完應該會知道。

一.目標

分析源碼當然得有目標,我們的目標很明確:
1.了解整套Activity的啟動過程;
2.為插件化框架開發做準備知識。

二.啟動流程分析

我們先來明確下應用程序Activity啟動的兩個主要方式:

1.在Home桌面點擊應用程序圖標啟動程序即launcher啟動程序的主Activity。
2.在應用程序中調用startActivity()和startActivityForResult()來啟動另外一個Activity。這個應用程序可以位于同個應用程序或者其他應用程序。

我們今天主要是講第二種方式

public class MainActivity extends Activity  implements OnClickListener {    
    ......    
    
    @Override    
    public void onClick(View v) {    
            Intent intent = new Intent(MainActivity.this,TargetActivity.class);    
            startActivity(intent);
    }    
}    

1.因為MainActivity繼承了Activity所以我們看Activity的startActivity()方法,然后經過startActivityForResult和execStartActivity最終會調用到Instrumentation的execStartActivity:

  public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
    IApplicationThread whoThread = (IApplicationThread) contextThread;
        Uri referrer = target != null ? target.onProvideReferrer() : null;
        if (referrer != null) {
            intent.putExtra(Intent.EXTRA_REFERRER, referrer);
        }
//省略掉自動測試所添加的ActivityMonitor類相關代碼
......... 
        try {
            intent.migrateExtraStreamToClipData();
            intent.prepareToLeaveProcess(who);
//獲取遠程AMS(ActivityManagerService)
            int result = ActivityManagerNative.getDefault()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);
//檢查Activity啟動是否成功
            checkStartActivityResult(result, intent);
        } catch (RemoteException e) {
            throw new RuntimeException("Failure from system", e);
        }
        return null;
    }

這里我們要注意這里面傳的參數,這樣我們接下來要走的話不會迷失:

whoThread:Binder對象,是主進程的Context對象;
token:一個Binder對象,指向了服務端一個ActivityRecord對象;
target:目標Activity即要啟動的Activity;
options:要傳輸的Bundle對象,里面攜帶數據。

2 .我們程序這里的ActivityManagerNative.getDefault()這里獲取到的是ActivityManagerService的遠程接口的代理即ActivityManagerProxy對象(如果不清楚可以看下AIDL的相關知識),所以這里的就是調用ActivityManagerProxy的startActivity方法:

 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();
        data.writeInterfaceToken(IActivityManager.descriptor);
        data.writeStrongBinder(caller != null ? caller.asBinder() : null);
        data.writeString(callingPackage);
        intent.writeToParcel(data, 0);
        data.writeString(resolvedType);
        data.writeStrongBinder(resultTo);
        data.writeString(resultWho);
        data.writeInt(requestCode);
        data.writeInt(startFlags);
        if (profilerInfo != null) {
            data.writeInt(1);
            profilerInfo.writeToParcel(data, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
        } else {
           data.writeInt(0);
        }
        if (options != null) {
            data.writeInt(1);
            options.writeToParcel(data, 0);
        } else {
            data.writeInt(0);
        }
        mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0);
        reply.readException();
        int result = reply.readInt();
        reply.recycle();
        data.recycle();
        return result;
    }

這里的參數也比較多,這里的resolvedType,resultWho,profilerInfo均為null,參數caller為ApplicationThread類型的Binder實體,resultTo是為一個Binder實體的遠程接口,參數requestCode為-1,startFlags為0,options為null。最后通過ActivityManagerProxy對象調用startActivity方法的實質是調用BinderProxy.transact向Binder驅動發送START_ACTIVITY_TRACSACTION命令。然后我們應用程序就到AMS中了。
3.過Binder驅動程序就進入到ActivityManagerService的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,
                resultWho, requestCode, startFlags, profilerInfo, bOptions,
                UserHandle.getCallingUserId());
    }

4.我們看到這個方法又調用了內部的startActivityAsUser然后會調用ActivityStarter(這個類是新版的android我這里版本是android-25才有的)的一系列方法:startActivityMayWait=>startActivityLocked=>startActivityUnchecked然后會到達ActivityStackSupervisor類的resumeFocusedStackTopActivityLocked方法接著到達ActivityStack類的resumeTopActivityUncheckedLocked=>resumeTopActivityInnerLocked接著又會走到ActivityStack類的resumeTopActivityInnerLocked方法然后會先調用startPausingLocked來停掉前面一個Activity即前一個Activity要先執行onPause方法:

 final boolean startPausingLocked(boolean userLeaving, boolean uiSleeping,
            ActivityRecord resuming, boolean dontWait) {
........
   prev.app.thread.schedulePauseActivity(prev.appToken, prev.finishing,
                        userLeaving, prev.configChangeFlags, dontWait);
........
}

5.這里的prev.app.thread是一個ApplicationThread對象的遠程接口,通過調用這個遠程接口的schedulePauseActivity(ApplicationThreadProxy.schedulePauseActivity)來通知前面一個Activity進入Paused狀態。我們這里指的這里也是一個AIDL交互,我們不詳細說明,我們直接看ActivityThread中的schedulePauseActivity方法:

  public final void schedulePauseActivity(IBinder token, boolean finished,
                boolean userLeaving, int configChanges, boolean dontReport) {
            int seq = getLifecycleSeq();
            if (DEBUG_ORDER) Slog.d(TAG, "pauseActivity " + ActivityThread.this
                    + " operation received seq: " + seq);
            sendMessage(
                    finished ? H.PAUSE_ACTIVITY_FINISHING : H.PAUSE_ACTIVITY,
                    token,
                    (userLeaving ? USER_LEAVING : 0) | (dontReport ? DONT_REPORT : 0),
                    configChanges,
                    seq);
        }

6.這里的sendMessage是發送一條消息,主要是發送到內部類H里面的handleMessage方法里面這里的消息code是PAUSE_ACTIVITY:

  case PAUSE_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
                    SomeArgs args = (SomeArgs) msg.obj;
                    handlePauseActivity((IBinder) args.arg1, false,
                            (args.argi1 & USER_LEAVING) != 0, args.argi2,
                            (args.argi1 & DONT_REPORT) != 0, args.argi3);
                    maybeSnapshot();
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break;

7.這樣程序就調用了handlePauseActivity方法:

    private void handlePauseActivity(IBinder token, boolean finished,
            boolean userLeaving, int configChanges, boolean dontReport, int seq) {
        ActivityClientRecord r = mActivities.get(token);
.......... 
            // Tell the activity manager we have paused.
            if (!dontReport) {
                try {
                    ActivityManagerNative.getDefault().activityPaused(token);
                } catch (RemoteException ex) {
                    throw ex.rethrowFromSystemServer();
                }
            }
            mSomeActivitiesChanged = true;
        }
    }

8.我們看到程序通過AIDL( ActivityManagerNative.getDefault()獲取的是AMS的遠程代理)調用到AMS的activityPaused方法:

   @Override
    public final void activityPaused(IBinder token) {
        final long origId = Binder.clearCallingIdentity();
        synchronized(this) {
            ActivityStack stack = ActivityRecord.getStackLocked(token);
            if (stack != null) {
                stack.activityPausedLocked(token, false);
            }
        }
        Binder.restoreCallingIdentity(origId);
    }

9.我們看到這里調用了ActivityStack的activityPausedLocked方法:

 final void activityPausedLocked(IBinder token, boolean timeout) {
        if (DEBUG_PAUSE) Slog.v(TAG_PAUSE,
            "Activity paused: token=" + token + ", timeout=" + timeout);

        final ActivityRecord r = isInStackLocked(token);
        if (r != null) {
            mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
            if (mPausingActivity == r) {
                if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to PAUSED: " + r
                        + (timeout ? " (due to timeout)" : " (pause complete)"));
                completePauseLocked(true, null);
                return;
            } else {
            ..........
            }
        }
        mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
    }

10.我們看到程序這里會調用completePauseLocked方法來停止然后接著調用ActivityStackSupervisor類的resumeFocusedStackTopActivityLocked方法接著到達ActivityStack類的resumeTopActivityUncheckedLocked=>resumeTopActivityInnerLocked接著又會走到ActivityStack類的resumeTopActivityInnerLocked方法然后又轉到ActivityStackSupervisor類的startSpecificActivityLocked:

  void startSpecificActivityLocked(ActivityRecord r,
            boolean andResume, boolean checkConfig) {
        // Is this activity's application already running?
        ProcessRecord app = mService.getProcessRecordLocked(r.processName,
                r.info.applicationInfo.uid, true);

        r.task.stack.setLaunchTime(r);

        if (app != null && app.thread != null) {
            try {
                if ((r.info.flags&ActivityInfo.FLAG_MULTIPROCESS) == 0
                        || !"android".equals(r.info.packageName)) {
                    // Don't add this if it is a platform component that is marked
                    // to run in multiple processes, because this is actually
                    // part of the framework so doesn't make sense to track as a
                    // separate apk in the process.
                    app.addPackage(r.info.packageName, r.info.applicationInfo.versionCode,
                            mService.mProcessStats);
                }
                realStartActivityLocked(r, app, andResume, checkConfig);
                return;
            } catch (RemoteException e) {
                Slog.w(TAG, "Exception when starting activity "
                        + r.intent.getComponent().flattenToShortString(), e);
            }

            // If a dead object exception was thrown -- fall through to
            // restart the application.
        }

        mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0,
                "activity", r.intent.getComponent(), false, false, true);
    }

11.前面首先會判斷要啟動的Activity的進程是否啟動起來了,如果已經啟動起來了會直接調用realStartActivityLocked,進行Activity的啟動就可以了。但是如果我們配置了process這個屬性,由于這是我們第一次啟動Activity,所以進程還是沒有開啟的,所以我們程序會調用AMS的startProcessLocked方法:

    private final void startProcessLocked(ProcessRecord app,
            String hostingType, String hostingNameStr) {
        startProcessLocked(app, hostingType, hostingNameStr, null /* abiOverride */,
                null /* entryPoint */, null /* entryPointArgs */);
    }

我們認真注意傳進來的參數分別是什么值其中entryPoint為null,然后我們直接看另一個startProcessLocked方法:

ActivityManagerService.startProcessLocked(ProcessRecord app, String hostingType,
            String hostingNameStr, String abiOverride, String entryPoint, String[] entryPointArgs){
            ......
            boolean isActivityProcess = (entryPoint == null);
            if (entryPoint == null) entryPoint = "android.app.ActivityThread";
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Start proc: " +
                    app.processName);
            checkTime(startTime, "startProcess: asking zygote to start proc");
            Process.ProcessStartResult startResult = Process.start(entryPoint,
                    app.processName, uid, uid, gids, debugFlags, mountExternal,
                    app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,
                    app.info.dataDir, entryPointArgs);
            ......
}

所以在調用上面方法的時候這里因為entryPoint傳過來的是null的,所以我們的entryPoint是為android.app.ActivityThread的值,這個時候我們通過Process.start()傳入的第一個參數是android.app.ActivityThread,這個參數在我們進程啟動完成之后,來決定后續代碼的執行 。
12.我們知道android的進程是通過zygote孵化出來的,而ActivityManagerService是運行在System進程,Zygote是運行在Zygote進程中的。Process.start方法的實現是在zygoteSendArgsAndGetResult函數最終實現的,是向socket服務端寫數據,把創建進程的請求通過socket通訊方式來讓zygote的管理進程創建新進程。在向zygote進程發送的通信請求時,需要組裝數據,具體實現我們看Process類的start方法:

  private static ProcessStartResult startViaZygote(final String processClass,
                                  final String niceName,
                                  final int uid, final int gid,
                                  final int[] gids,
                                  int debugFlags, int mountExternal,
                                  int targetSdkVersion,
                                  String seInfo,
                                  String abi,
                                  String instructionSet,
                                  String appDataDir,
                                  String[] extraArgs)
                                  throws ZygoteStartFailedEx {
        synchronized(Process.class) {
            ArrayList<String> argsForZygote = new ArrayList<String>();

            // --runtime-args, --setuid=, --setgid=,
            // and --setgroups= must go first
            argsForZygote.add("--runtime-args");
            argsForZygote.add("--setuid=" + uid);
            argsForZygote.add("--setgid=" + gid);
//省略參數拼裝的一些方法
.............
            return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
        }
    }

以上的方法省略了一些拼裝參數的方法,程序最終調用zygoteSendArgsAndGetResult()方法來向socket服務端發送創建進程的請求,這樣zygote就會來創建新的進程。

    private static ProcessStartResult zygoteSendArgsAndGetResult(
            ZygoteState zygoteState, ArrayList<String> args)
            throws ZygoteStartFailedEx {
        try {
            // Throw early if any of the arguments are malformed. This means we can
            // avoid writing a partial response to the zygote.
            int sz = args.size();
            for (int i = 0; i < sz; i++) {
                if (args.get(i).indexOf('\n') >= 0) {
                    throw new ZygoteStartFailedEx("embedded newlines not allowed");
                }
            }

            /**
             * See com.android.internal.os.ZygoteInit.readArgumentList()
             * Presently the wire format to the zygote process is:
             * a) a count of arguments (argc, in essence)
             * b) a number of newline-separated argument strings equal to count
             *
             * After the zygote process reads these it will write the pid of
             * the child or -1 on failure, followed by boolean to
             * indicate whether a wrapper process was used.
             */
            final BufferedWriter writer = zygoteState.writer;
            final DataInputStream inputStream = zygoteState.inputStream;

            writer.write(Integer.toString(args.size()));
            writer.newLine();

            for (int i = 0; i < sz; i++) {
                String arg = args.get(i);
                writer.write(arg);
                writer.newLine();
            }

            writer.flush();

            // Should there be a timeout on this?
            ProcessStartResult result = new ProcessStartResult();

            // Always read the entire result from the input stream to avoid leaving
            // bytes in the stream for future process starts to accidentally stumble
            // upon.
            result.pid = inputStream.readInt();
            result.usingWrapper = inputStream.readBoolean();

            if (result.pid < 0) {
                throw new ZygoteStartFailedEx("fork() failed");
            }
            return result;
        } catch (IOException ex) {
            zygoteState.close();
            throw new ZygoteStartFailedEx(ex);
        }
    }

13.這里就會向我們的zygote發送一個創建進程請求。然后創建新的進程就會在zygote進行,這里我們不過多去解析zygote怎么創建新的進程。最后程序會在ActivityStackSupervisor的attachApplicationLocked方法里面調用realStartActivityLocked方法:

final boolean realStartActivityLocked(ActivityRecord r, ProcessRecord app,
            boolean andResume, boolean checkConfig) throws RemoteException {
            ......
            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);
            ......
            }

14.這里的app.thread是ApplicationThread,所以這里會調用ApplicationThread的scheduleLaunchActivity方法最后調用到ActivityThread的handleLaunchActivity方法:

    private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
   .......
        Activity a = performLaunchActivity(r, customIntent);

        if (a != null) {
    .......
        } else {
            // If there was an error, for any reason, tell the activity manager to stop us.
            try {
                ActivityManagerNative.getDefault()
                    .finishActivity(r.token, Activity.RESULT_CANCELED, null,
                            Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
        }
    }

15.我們看到上面方法里面會調用performLaunchActivity方法,這個方法就是真正啟動Activity了,我們看下這個具體方法:

 private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
......
        Activity activity = null;
        try {
//這里調用Instrumentation的newActivity來創建一個新的Activity
            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);

........
                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 (customIntent != null) {
                    activity.mIntent = customIntent;
                }
                r.lastNonConfigurationInstances = null;
                activity.mStartedActivity = false;
                int theme = r.activityInfo.getThemeResource();
                if (theme != 0) {
                    activity.setTheme(theme);
                }

                activity.mCalled = false;
                if (r.isPersistable()) {
//這個方法里面會調用目標Activity的onCreate方法
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
                if (!activity.mCalled) {
                    throw new SuperNotCalledException(
                        "Activity " + r.intent.getComponent().toShortString() +
                        " did not call through to super.onCreate()");
                }
                r.activity = activity;
                r.stopped = true;
.......
        } catch (SuperNotCalledException e) {
            throw e;

        } catch (Exception e) {
......
        }

        return activity;
    }

16.我們看到這個方法就是真正創建Activity的地方了,首先會調用Instrumentation的newActivity方法:

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

17.這里很容易看懂就是用類加載器加載一個新的class,然后返回他的實例。到這一步Activity的實例也就被創建出來了,接著我們看會調用Activity的onCreate方法,這個會在Instrumentation的callActivityOnCreate方法里面:

 public void callActivityOnCreate(Activity activity, Bundle icicle) {
        prePerformCreate(activity);
        activity.performCreate(icicle);
        postPerformCreate(activity);
    }

我們看到這個方法里面會調用activity的performCreate方法,這個方法如下:

    final void performCreate(Bundle icicle) {
        restoreHasCurrentPermissionRequest(icicle);
        onCreate(icicle);
        mActivityTransitionState.readState(icicle);
        performCreateCommon();
    }

到這里我們的Activity就啟動完畢了,如果你中間有什么過程不懂的可以參照我們前面畫的圖來對照,基本流程就知道了,也不至于被繞暈。

總結:這次的Activity啟動講解主要是讓大家有個流程上的認識,到時如果在插件化框架上有用到我會認真把某一個地方拿出來講解,如果有講解的不對的地方歡迎大家指出哈,希望大家共同進步。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,769評論 2 372

推薦閱讀更多精彩內容