Android 8.0系統(tǒng)啟動流程_Launcher(四)

本系列主要介紹Android8.0系統(tǒng)啟動過程中涉及到的init妓柜、Zygote箱季、SystemServer和Launcher。
在之前的三篇文章中棍掐,講解了如下的過程:

  • 初始化化:電源上電藏雏,加載BootLoader程序; 啟動init.cpp作煌,解析init.rc配置文件掘殴;
  • 啟動Zygote進(jìn)程:啟動虛擬機(jī)和注冊JNI方法赚瘦;注冊Socket服務(wù)端,預(yù)加載資源奏寨;執(zhí)行runSelectLoop()方法等待其他進(jìn)程的注冊起意;
  • 啟動SystemServer進(jìn)程:通過Zygote啟動,創(chuàng)建Binder線程池病瞳、執(zhí)行main方法揽咕;開啟 三個(gè)系統(tǒng)服務(wù)(引導(dǎo)、核心和其他)套菜。

在完成以上三個(gè)過程后心褐,我們的系統(tǒng)就開始加載Launcher應(yīng)用,查看源碼可以發(fā)現(xiàn)Launcher是作為一個(gè)APP應(yīng)用執(zhí)行的笼踩,其一般位于系統(tǒng)的packages/apps目錄下逗爹,可以通過該應(yīng)用啟動系統(tǒng)中其他應(yīng)用程序,提供快捷訪問圖標(biāo)嚎于。

一掘而、Launcher的啟動

在這里插入圖片描述

1.1啟動準(zhǔn)備

在SystemServer啟動時(shí),會先啟動引導(dǎo)服務(wù)于购,其中包括PMS(PackageManagerService)和AMS(ActivityManagerService)袍睡,其中PMS主要作用是系統(tǒng)中的APK的解析和安裝;AMS主要用于四大組件的啟動 和管理肋僧,因此LauncherActivity通過AMS啟動斑胜。
完成引導(dǎo)服務(wù)的啟動后,開啟啟動其他服務(wù)嫌吠。啟動Launcher的入口為AMS的systemReady方法止潘,

frameworks\base\services\java\com\android\server\SystemServer.java
 private void startOtherServices() {
 ...
 //在此之前會做大量的準(zhǔn)備工作,包括AMS辫诅、PMS 和NetworkScoreService等各種服務(wù)凭戴,完成以上操作后,表示activity manager可以運(yùn)行
 mActivityManagerService.systemReady(() -> {
            Slog.i(TAG, "Making services ready");
            traceBeginAndSlog("StartActivityManagerReadyPhase");
            mSystemServiceManager.startBootPhase(
                    SystemService.PHASE_ACTIVITY_MANAGER_READY);
                    ...
                    }
                    ...
 ...
}
frameworks\base\services\core\java\com\android\server\am\ActivityManagerService.java
 public void systemReady(final Runnable goingCallback, TimingsTraceLog traceLog) {
        traceLog.traceBegin("PhaseActivityManagerReady");
        synchronized(this) {
...
             mStackSupervisor.resumeFocusedStackTopActivityLocked();
            mUserController.sendUserSwitchBroadcastsLocked(-1, currentUserId);
...
    }   
}

1.2找到Launcher 的Activity

AMS通過調(diào)用ActivityStack實(shí)現(xiàn)對堆棧中Activity對象的管理炕矮,我們的最終目的是查找到Launcher應(yīng)用所在的Activity是如何被調(diào)用起來的么夫?
其流程如下:
ActivityStackSuperior#resumeFocusedStackTopActivityLocked() ->ActivityStack#resumeTopActivityUncheckedLocked()->resumeTopActivityInnerLocked()
在resumeTopActivityInnerLocked中,通過調(diào)用startHomeActivityLocked方法肤视,開啟啟動Launcher的Activty档痪。

frameworks\base\services\core\java\com\android\server\am\ActivityStackSupervisor.java
 boolean resumeFocusedStackTopActivityLocked(
            ActivityStack targetStack, ActivityRecord target, ActivityOptions targetOptions) {
            
 if (targetStack != null && isFocusedStack(targetStack)) {
            return targetStack.resumeTopActivityUncheckedLocked(target, targetOptions);
        }
        ...
        return false;
        
frameworks\base\services\core\java\com\android\server\am\ActivityStack.java
boolean resumeTopActivityUncheckedLocked(ActivityRecord prev, ActivityOptions options) {
if (mStackSupervisor.inResumeTopActivity) {
            return false;
        }
         boolean result = false;
        try {
            mStackSupervisor.inResumeTopActivity = true;
            result = resumeTopActivityInnerLocked(prev, options);
        } finally {
            mStackSupervisor.inResumeTopActivity = false;
        }
        final ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */);
        if (next == null || !next.canTurnScreenOn()) {
            checkReadyForSleep();
        }
        return result;
}
frameworks\base\services\core\java\com\android\server\am\ActivityStackSupervisor.java
 private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) {
 ...
return isOnHomeDisplay() &&
                mStackSupervisor.resumeHomeStackTask(prev, reason);
                ...
                 if (r != null && !r.finishing) {
            moveFocusableActivityStackToFrontLocked(r, myReason);
            return resumeFocusedStackTopActivityLocked(mHomeStack, prev, null);
        }
        return mService.startHomeActivityLocked(mCurrentUser, myReason);
...
}

1.3 啟動Launcher

frameworks\base\services\core\java\com\android\server\am\ActivityManagerService.java
 boolean startHomeActivityLocked(int userId, String reason) {
 //1 判斷工廠模式和topAction
if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL
                && mTopAction == null) {
            return false;
        }
        //2 創(chuàng)建啟動Launcher的Intent
        Intent intent = getHomeIntent();
        ActivityInfo aInfo = resolveActivityInfo(intent, STOCK_PM_FLAGS, userId);
        if (aInfo != null) {
            intent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name));
            aInfo = new ActivityInfo(aInfo);
            aInfo.applicationInfo = getAppInfoForUser(aInfo.applicationInfo, userId);
            ProcessRecord app = getProcessRecordLocked(aInfo.processName,
                    aInfo.applicationInfo.uid, true);
            if (app == null || app.instr == null) {
                intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
                final int resolvedUserId = UserHandle.getUserId(aInfo.applicationInfo.uid); 
                final String myReason = reason + ":" + userId + ":" + resolvedUserId;
                //3 啟動Launcdr
                mActivityStarter.startHomeActivityLocked(intent, aInfo, myReason);
            }
        } else {
            Slog.wtf(TAG, "No home screen found for " + intent, new Throwable());
        }
        return true;
}

在注釋1處是對運(yùn)行模式和Action的判斷,其中運(yùn)行模式包括:非工廠模式邢滑、低級工廠模式和高級工廠模式腐螟。而Action的是是指啟動該Intent的Action,默認(rèn)是Intent.ACTION_MAIN,表示是該應(yīng)用的第一個(gè)啟動的Activity遭垛,一般在創(chuàng)建應(yīng)用時(shí)尼桶,AndroidMainfest.xm中都會包含唯一個(gè)該標(biāo)簽的Action操灿。
而對于桌面應(yīng)用锯仪,會增加Intent.HOME的標(biāo)簽,如果我們想自定義桌面應(yīng)用趾盐,可在該應(yīng)用的AndroidMainfest中的啟動Activity的Action添加android.Intent.action.HOME庶喜,如下所示:

 <application
       ...
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.HOME"/>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>

啟動Launer是在ActivityStarter的startHomeActivityLocked方法中完成,通過ActivityStackSupervisor將該Intent移動至HomeStack(用于存儲Launcher的變量)的頂部救鲤,最終調(diào)用startActivityLocked方法啟動該Intnet久窟,實(shí)現(xiàn)對Launcer的啟動。

 void startHomeActivityLocked(Intent intent, ActivityInfo aInfo, String reason) {
        mSupervisor.moveHomeStackTaskToTop(reason);
       ...
    }

二 本缠、Launcher桌面圖標(biāo)顯示

完成Launcer的啟動后斥扛,作為一個(gè)獨(dú)立的APP,Launcher開始執(zhí)行應(yīng)用的加載和桌面圖標(biāo)的顯示丹锹。


在這里插入圖片描述

2.1 加載APP

packages\apps\Launcher3\src\com\android\launcher3\Launcher.java
protected void onCreate(Bundle savedInstanceState) {
...
//加載桌面顏色信息和監(jiān)聽主題變化
 WallpaperColorInfo wallpaperColorInfo = WallpaperColorInfo.getInstance(this);
  wallpaperColorInfo.setOnThemeChangeListener(this);
...
//加載桌面應(yīng)用信息
 LauncherAppState app = LauncherAppState.getInstance(this);
 ...
 mModel = app.setLauncher(this);
 ...
 //加載和設(shè)置桌面view
 mLauncherView = LayoutInflater.from(this).inflate(R.layout.launcher, null);
  setupViews();
  ...
   if (!mModel.startLoader(currentScreen)) {
            mDragLayer.setAlpha(0);
        } else {
            mWorkspace.setCurrentPage(currentScreen);
            setWorkspaceLoading(true);
        }
}
//設(shè)置launcher的監(jiān)聽稀颁,初始化model
packages\apps\Launcher3\src\com\android\launcher3\LauncherAppState
 LauncherModel setLauncher(Launcher launcher) {
        getLocalProvider(mContext).setLauncherProviderChangeListener(launcher);
        mModel.initialize(launcher); //傳入Launcher對象
        return mModel;
    }

在設(shè)置監(jiān)聽時(shí),傳入的Callbacks 對象時(shí)Launcher楣黍,是為了在APP加載完成時(shí)匾灶,方便通過接口回調(diào)的形式返回值Launcher,
下面開始加載App租漂。

packages\apps\Launcher3\src\com\android\launcher3\LauncherModel
  public void initialize(Callbacks callbacks) {
        synchronized (mLock) {
            Preconditions.assertUIThread();
            mCallbacks = new WeakReference<>(callbacks);
        }
    }
    //Callbacks 接口包含的內(nèi)容如下阶女,主要用到bindAllApplications
public interface Callbacks extends LauncherAppWidgetHost.ProviderChangedListener {
      ...
        public void bindAllApplications(ArrayList<AppInfo> apps);
        public void bindAppsAddedOrUpdated(ArrayList<AppInfo> apps);
        public void bindAppsAdded(ArrayList<Long> newScreens,
                                  ArrayList<ItemInfo> addNotAnimated,
                                  ArrayList<ItemInfo> addAnimated);
        public void bindPromiseAppProgressUpdated(PromiseAppInfo app);
        public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated, UserHandle user);
        public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
        public void bindRestoreItemsChange(HashSet<ItemInfo> updates);
        public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher);
        public void bindAppInfosRemoved(ArrayList<AppInfo> appInfos);
        public void bindAllWidgets(MultiHashMap<PackageItemInfo, WidgetItem> widgets);
      ...
    }

加載的Task分析如下

...
packages\apps\Launcher3\src\com\android\launcher3\LauncherModel
//開始加載
public boolean startLoader(int synchronousBindPage) {
       ...
        synchronized (mLock) {
        //清除之前的回調(diào)緩存
            if (mCallbacks != null && mCallbacks.get() != null) {
                final Callbacks oldCallbacks = mCallbacks.get();
                // Clear any pending bind-runnables from the synchronized load process.
                mUiExecutor.execute(new Runnable() {
                            public void run() {
                                oldCallbacks.clearPendingBinds();
                            }
                        });
                // If there is already one running, tell it to stop.
                stopLoader();
                LoaderResults loaderResults = new LoaderResults(mApp, sBgDataModel,
                        mBgAllAppsList, synchronousBindPage, mCallbacks);//包含Callbacks對象
                if (mModelLoaded && !mIsLoaderTaskRunning) {    //已經(jīng)加載完成且沒有正在加載
                   //加載完成后,開始將結(jié)果回調(diào)至Launcher
                    loaderResults.bindWorkspace();
                    loaderResults.bindAllApps(); //加載的回調(diào)
                    loaderResults.bindDeepShortcuts();
                    loaderResults.bindWidgets();
                    return true;
                } else {
               //開始不斷的加載 工作區(qū)間哩治、所有的APP秃踩、快捷圖標(biāo)和抽屜控件
                    startLoaderForResults(loaderResults);
                }
            }
        }
        return false;
    }

packages\apps\Launcher3\src\com\android\launcher3\LauncherModel
public void startLoaderForResults(LoaderResults results) {
        synchronized (mLock) {
             //停止加載
            stopLoader();
            //創(chuàng)建新的加載task
            mLoaderTask = new LoaderTask(mApp, mBgAllAppsList, sBgDataModel, results);
            //在WorkerThread中執(zhí)行該task
            runOnWorkerThread(mLoaderTask);
        }
    }
   //加載中涉及的線程初始化
 packages\apps\Launcher3\src\com\android\launcher3\LauncherModel
@Thunk static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
    static {
        sWorkerThread.start();
    }
@Thunk static final Handler sWorker = new Handler(sWorkerThread.getLooper());

2.2 加載回調(diào)處理

通過LauncherModel的加載線程,獲取到了系統(tǒng)中所有的apps的信息业筏,同時(shí)通過其Callbacks的接口吞瞪,很方便的將結(jié)果傳遞出去,

packages\apps\Launcher3\src\com\android\launcher3\Launcher.java
public void bindAllApplications(final ArrayList<AppInfo> apps) {
        Runnable r = new RunnableWithId(RUNNABLE_ID_BIND_APPS) {
            public void run() {
                bindAllApplications(apps);
            }
        };
     ...
            //加載完成后AllAppsContainerView數(shù)據(jù)更新
            mAppsView.setApps(apps);
        }
        if (mLauncherCallbacks != null) {
            mLauncherCallbacks.bindAllApplications(apps);
        }
    }

數(shù)據(jù)回調(diào)值Launcher的bindAllApplications驾孔,開始對AllAppsContainerView界面進(jìn)行數(shù)據(jù)設(shè)置芍秆,其設(shè)置的控制控件為AlphabeticalAppsList,通過調(diào)用AlphabeticalAppsList實(shí)現(xiàn)對數(shù)據(jù)的設(shè)置處理翠勉,其實(shí)現(xiàn)流程和Recycleview的設(shè)置比較類似妖啥。

packages\apps\Launcher3\src\com\android\launcher3\allapps\AllAppsContainerView.java
 public void setApps(List<AppInfo> apps) {
        mApps.setApps(apps);
    }

//數(shù)據(jù)傳遞控制控件中
packages\apps\Launcher3\src\com\android\launcher3\allapps\AlphabeticalAppsList.java
public void setApps(List<AppInfo> apps) {
        mComponentToAppMap.clear();
        addOrUpdateApps(apps);
    }

packages\apps\Launcher3\src\com\android\launcher3\allapps\AllAppsContainerView.java
 @Override
    protected void onFinishInflate() {
...
         mAppsRecyclerView = findViewById(R.id.apps_list_view); //可以看到桌面應(yīng)用部分的界面為RecyclerView
        mAppsRecyclerView.setApps(mApps);
        mAppsRecyclerView.setLayoutManager(mLayoutManager);
        mAppsRecyclerView.setAdapter(mAdapter);
        mAppsRecyclerView.setHasFixedSize(true);
...

}

AllAppsContainerView會在XML布局文件加載完成后,調(diào)用onFinishInflate方法对碌,使加載的數(shù)據(jù)最終顯示在桌面上荆虱。形成我們看到的桌面圖標(biāo)。

2.3 點(diǎn)擊桌面圖標(biāo)的跳轉(zhuǎn)至應(yīng)用

在AllAppsContainerView設(shè)置adapter后,由于RecyclerView不包含setOnItemClickListener方法怀读,因此一般是在Adapter中自定義實(shí)現(xiàn)诉位,可通過回調(diào)的形式將點(diǎn)擊事件傳遞出去,Launcher的執(zhí)行流程如下:

//packages\apps\Launcher3\src\com\android\launcher3\allapps\AllAppsContainerView.java
 public AllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
      ...
        //創(chuàng)建新的Adapter
        mAdapter = new AllAppsGridAdapter(mLauncher, mApps, mLauncher, this);
        mSpringAnimationHandler = mAdapter.getSpringAnimationHandler();
        mApps.setAdapter(mAdapter);
       ...
    }

設(shè)置點(diǎn)擊的監(jiān)聽回調(diào)

//packages\apps\Launcher3\src\com\android\launcher3\allapps\AllAppsGridAdapter.java
 public AllAppsGridAdapter(Launcher launcher, AlphabeticalAppsList apps, View.OnClickListener
            iconClickListener, View.OnLongClickListener iconLongClickListener) {
      ...
        mIconClickListener = iconClickListener;
      ...
    }

點(diǎn)擊事件的處理

packages\apps\Launcher3\src\com\android\launcher3\Launcher.java
 public void onClick(View v) {
...
 Object tag = v.getTag();
        if (tag instanceof ShortcutInfo) {
            onClickAppShortcut(v);
        } else if (tag instanceof FolderInfo) {
            if (v instanceof FolderIcon) {
                onClickFolderIcon(v);
            }
        } else if ((v instanceof PageIndicator) ||
            (v == mAllAppsButton && mAllAppsButton != null)) {
            onClickAllAppsButton(v);
        } else if (tag instanceof AppInfo) {
            startAppShortcutOrInfoActivity(v);
        } else if (tag instanceof LauncherAppWidgetInfo) {
            if (v instanceof PendingAppWidgetHostView) {
                onClickPendingWidget((PendingAppWidgetHostView) v);
            }
        }
}

//點(diǎn)擊桌面應(yīng)用的快捷圖標(biāo)的處理
protected void onClickAppShortcut(final View v) {
...
 startAppShortcutOrInfoActivity(v);
...
}

 private void startAppShortcutOrInfoActivity(View v) {
        ItemInfo item = (ItemInfo) v.getTag();
        Intent intent;
        if (item instanceof PromiseAppInfo) {
            PromiseAppInfo promiseAppInfo = (PromiseAppInfo) item;
            intent = promiseAppInfo.getMarketIntent();
        } else {
            intent = item.getIntent();
        }
     ...
     //跳轉(zhuǎn)至相應(yīng)的應(yīng)用
        boolean success = startActivitySafely(v, intent, item);
...
    }

至此Launcher的啟動過程便分析完菜枷,總結(jié)如下:

  1. 啟動:SystemServer啟動PMS和AMS苍糠,通過AMS的systemReady開始加載;
  2. 查找:查找流程如下ActivityStackSuperior#resumeFocusedStackTopActivityLocked() ->ActivityStack#resumeTopActivityUncheckedLocked()->resumeTopActivityInnerLocked()啤誊;
  3. 顯示:在resumeTopActivityInnerLocked中通過調(diào)用startHomeActivityLocked方法岳瞭,啟動Launcher的Activty,通過LauncherModel的加載線程處理蚊锹,獲取到所有的Apps信息瞳筏,借助Callbacks的接口回調(diào)的形式將數(shù)據(jù)返回值Launcher,然后將數(shù)據(jù)賦值給AllAppsContainerView牡昆,使Apps的桌面圖標(biāo)顯示至桌面中姚炕。
  4. 跳轉(zhuǎn):通過設(shè)置點(diǎn)擊事件的回調(diào)處理,點(diǎn)擊桌面圖標(biāo)后丢烘,通過startActivitySafely跳轉(zhuǎn)至應(yīng)用的Main界面柱宦。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市铅协,隨后出現(xiàn)的幾起案子捷沸,更是在濱河造成了極大的恐慌,老刑警劉巖狐史,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件痒给,死亡現(xiàn)場離奇詭異,居然都是意外死亡骏全,警方通過查閱死者的電腦和手機(jī)苍柏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來姜贡,“玉大人试吁,你說我怎么就攤上這事÷タ龋” “怎么了熄捍?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長母怜。 經(jīng)常有香客問我余耽,道長,這世上最難降的妖魔是什么苹熏? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任碟贾,我火速辦了婚禮币喧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘袱耽。我一直安慰自己杀餐,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布朱巨。 她就那樣靜靜地躺著史翘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蔬崩。 梳的紋絲不亂的頭發(fā)上恶座,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天搀暑,我揣著相機(jī)與錄音沥阳,去河邊找鬼。 笑死自点,一個(gè)胖子當(dāng)著我的面吹牛桐罕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播桂敛,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼功炮,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了术唬?” 一聲冷哼從身側(cè)響起薪伏,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎粗仓,沒想到半個(gè)月后嫁怀,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡借浊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年塘淑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蚂斤。...
    茶點(diǎn)故事閱讀 39,711評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡存捺,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出曙蒸,到底是詐尸還是另有隱情捌治,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布纽窟,位于F島的核電站肖油,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏师倔。R本人自食惡果不足惜构韵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一周蹭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧疲恢,春花似錦凶朗、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至杂数,卻和暖如春宛畦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背揍移。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工次和, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人那伐。 一個(gè)月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓踏施,卻偏偏與公主長得像,于是被迫代替她去往敵國和親罕邀。 傳聞我的和親對象是個(gè)殘疾皇子畅形,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評論 2 353

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