探秘Android之WindowManager

在Android的世界里茁计,我們可以通過WindowManager將一個(gè)視圖添加到屏幕上镰吵。下面就是實(shí)現(xiàn)此需求的兩條關(guān)鍵語句:

WindowManager wm = (WindowManager) contex.getSystemService(Context.WINDOW_SERVICE);

...

wm.addView(view, layoutParam);

這篇文章將以這兩條語句作為切入點(diǎn),探究一下與WindowManager相關(guān)的源碼(基于5.1.1系統(tǒng))翼雀。

WindowManager是什么

根據(jù)WindowManager的定義

public interface WindowManager extends ViewManager

可以看出WindowManager是一個(gè)繼承自ViewManager的接口膘融。所以WindowManager繼承了ViewManager中的兩個(gè)主要函數(shù):

public void addView(View view, ViewGroup.LayoutParams params);
public void removeView(View view);

從函數(shù)名便可推斷這兩個(gè)函數(shù)的作用分別是往屏幕添加視圖以及移除之前添加的視圖。

既然WindowManager只是一個(gè)接口划咐,那必然有類實(shí)現(xiàn)了這個(gè)接口拴念。讓我們回到這條語句

WindowManager wm = (WindowManager) contex.getSystemService(Context.WINDOW_SERVICE);

去探究一下通過contex.getSystemService(Context.WINDOW_SERVICE)拿到的WindowManager到底是什么。

因?yàn)榕cUI緊密相關(guān)的ContextActivity褐缠,所以假定這里的context是一個(gè)Activity政鼠。

首先看一下ActivitygetSystemService的實(shí)現(xiàn):

@Override
5033    public Object getSystemService(@ServiceName @NonNull String name) {
5034        if (getBaseContext() == null) {
5035            throw new IllegalStateException(
5036                    "System services not available to Activities before onCreate()");
5037        }
5038
5039        if (WINDOW_SERVICE.equals(name)) {
5040            return mWindowManager;
5041        } else if (SEARCH_SERVICE.equals(name)) {
5042            ensureSearchManager();
5043            return mSearchManager;
5044        }
5045        return super.getSystemService(name);
5046    }

通過5039行的if語句可以看出,如果參數(shù)nameWINDOW_SERVICE則直接返回Activity的成員變量mWindowManager. 接下來需要找到mWindowManager被初始化的地方队魏。它的初始化緊隨Activity的初始化缔俄。而Activity的初始化在ActivityThread.performLaunchActivity中進(jìn)行。這里的ActivityThread就是我們通常所說的主線程或者UI線程。摘取ActivityThread.performLaunchActivity中的幾條關(guān)鍵語句:

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
       Activity activity = null;
...
        activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
...
        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);

當(dāng)activity被創(chuàng)建出來后其attach方法即被調(diào)用俐载。

5922    final void attach(Context context, ActivityThread aThread,
5923            Instrumentation instr, IBinder token, int ident,
5924            Application application, Intent intent, ActivityInfo info,
5925            CharSequence title, Activity parent, String id,
5926            NonConfigurationInstances lastNonConfigurationInstances,
5927            Configuration config, String referrer, IVoiceInteractor voiceInteractor) {

           ...      

5932        mWindow = PolicyManager.makeNewWindow(this);

           ...

5966        mWindow.setWindowManager(
5967                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
5968                mToken, mComponent.flattenToString(),
5969                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
5970        if (mParent != null) {
5971            mWindow.setContainer(mParent.getWindow());
5972        }
5973        mWindowManager = mWindow.getWindowManager();
5974        mCurrentConfig = config;
5975    }

在第5932行蟹略,Activity的成員變量mWindow是一個(gè)Window類對(duì)象。Window是對(duì)Activity或者Dialog的視圖的一種上層抽象遏佣。Window是一個(gè)抽象類挖炬,而PhoneWindow繼承了Window并且實(shí)現(xiàn)了其中的一些關(guān)鍵方法。PhoneWindow中有一個(gè)類型為DecorView的成員變量mDecor状婶,表示的是Activity對(duì)應(yīng)的視圖的最外層容器意敛。PolicyManager.makeNewWindow(this)正好返回了一個(gè)PhoneWindow對(duì)象。

接下來在5966行膛虫,Window.setWindowManager被調(diào)用草姻。該函數(shù)需要四個(gè)參數(shù)。首先我們看第一個(gè)參數(shù)(WindowManager)context.getSystemService(Context.WINDOW_SERVICE). 在這里context.getSystemService(Context.WINDOW_SERVICE)又一次被調(diào)用并且返回一個(gè)WindowManager對(duì)象稍刀。不過這里的context不是某個(gè)Activity對(duì)象撩独,而是一個(gè)ContextImpl對(duì)象。接下來看一下ContextImpl.getSystemService的實(shí)現(xiàn)账月。

@Override
public Object getSystemService(String name) {
    ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
    return fetcher == null ? null : fetcher.getService(this);
}

可以看到getSystemService會(huì)根據(jù)參數(shù)nameSYSTEM_SERVICE_MAP中拿到對(duì)應(yīng)的ServiceFetcher對(duì)象综膀,然后通過ServiceFetcher.getService返回具體的對(duì)象。在ContextImpl中有一個(gè)函數(shù)的作用是往SYSTEM_SERVICE_MAP中放入(name, ServiceFetcher)映射局齿。

private static void registerService(String serviceName, ServiceFetcher fetcher) {
    if (!(fetcher instanceof StaticServiceFetcher)) {
        fetcher.mContextCacheIndex = sNextPerContextServiceCacheIndex++;
    }
    SYSTEM_SERVICE_MAP.put(serviceName, fetcher);
}

另外在ContextImpl中有一個(gè)static語句塊剧劝,里面通過多次調(diào)用registerService方法將所有可能的(name, ServiceFetcher)映射放入了SYSTEM_SERVICE_MAP. 這里我們特別關(guān)注一下WINDOW_SERVICE:

634     registerService(WINDOW_SERVICE, new ServiceFetcher() {
635             Display mDefaultDisplay;
636             public Object getService(ContextImpl ctx) {
637                 Display display = ctx.mDisplay;
638                 if (display == null) {
639                     if (mDefaultDisplay == null) {
640                         DisplayManager dm = (DisplayManager)ctx.getOuterContext().getSystemService(Context.DISPLAY_SERVICE);
642                         mDefaultDisplay = dm.getDisplay(Display.DEFAULT_DISPLAY);
643                     }
644                     display = mDefaultDisplay;
645                 }
646                 return new WindowManagerImpl(display);
647             }});

可以看到getService返回了一個(gè)WindowManagerImpl對(duì)象。之前提到WindowManager只是一個(gè)接口抓歼,而這里的WindowManagerImpl正好實(shí)現(xiàn)了WindowManager.

public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    private final Display mDisplay;
    private final Window mParentWindow;

    private IBinder mDefaultToken;

    public WindowManagerImpl(Display display) {
        this(display, null);
    }

    private WindowManagerImpl(Display display, Window parentWindow) {
        mDisplay = display;
        mParentWindow = parentWindow;
    }

    ...
}

有一點(diǎn)需要注意:這里用到了WindowManagerImpl的只需一個(gè)參數(shù)的構(gòu)造函數(shù)讥此。通過上面的類的定義可以看到,WindowManagerImpl還有一個(gè)需要兩個(gè)參數(shù)的構(gòu)造函數(shù)谣妻,這個(gè)構(gòu)造函數(shù)的調(diào)用接下來就會(huì)看到暂论。還有我們可以看到WindowManagerImpl中有一個(gè)類型為WindowManagerGlobal的成員變量mGlobal,使用了單例模式拌禾,它的作用將在下節(jié)說明。

現(xiàn)在回到Activity.attach方法的5966行展哭,我們已經(jīng)知道第一個(gè)參數(shù)是一個(gè)WindowManagerImpl對(duì)象湃窍。接著就看一下Window.setWindowManager的具體實(shí)現(xiàn)。

539     public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
540             boolean hardwareAccelerated) {
541         mAppToken = appToken;
542         mAppName = appName;
543         mHardwareAccelerated = hardwareAccelerated
544                 || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
545         if (wm == null) {
546             wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
547         }
548         mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
549     }

這里有兩個(gè)比較關(guān)鍵的點(diǎn):

  • 第541行匪傍,將參數(shù)appToken賦給了Window的成員變量mAppToken. appToken是一個(gè)Binder對(duì)象您市,在ActivityManagerService, WindowManagerService等系統(tǒng)服務(wù)中通過它來標(biāo)識(shí)Activity. 讓Window的成員變量mAppToken指向appToken,就好比這個(gè)Window得到了與之對(duì)應(yīng)的Activity的身份通行證役衡。這樣一來茵休,WindowManagerService就能知道這個(gè)Window是屬于哪個(gè)Activity了。

  • 第548行,調(diào)用WindowManagerImpl.createLocalWindowManager創(chuàng)建了一個(gè)新的WindowManagerImpl對(duì)象榕莺,并賦給了成員變量mWindowManager. WindowManagerImpl.createLocalWindowManager的實(shí)現(xiàn)如下:

      public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
              return new WindowManagerImpl(mDisplay, parentWindow);
      }
    

    可以看到這里用到了WindowManagerImpl中需要兩個(gè)參數(shù)的構(gòu)造函數(shù)俐芯,第二個(gè)參數(shù)是對(duì)應(yīng)的Window.

再次回到Activity.attach方法。第5973行將Window的成員變量mWindowManager指向的WindowManagerImpl對(duì)象賦給了Activity的成員變量mWindowManager. 至此钉鸯,我們就知道了通過Activity.getSystemService(Context.WINDOW_SERVICE);拿到的是一個(gè)WindowManager的實(shí)現(xiàn)類WindowManagerImpl的對(duì)象吧史。對(duì)于非ActivityContext,調(diào)用它們的getSystemService方法實(shí)際上會(huì)調(diào)用ContextImpl.getSystemService. 這個(gè)方法返回的也是一個(gè)WindowManagerImpl的對(duì)象唠雕。只不過這個(gè)WindowManagerImpl的對(duì)象的成員變量mParentWindownull贸营,也就是沒有關(guān)聯(lián)任何Window對(duì)象。

WindowManager.addView做了什么

接下來探究一下WindowManager.addView到底做了什么岩睁。

通過上面的分析钞脂,我們已經(jīng)知道實(shí)現(xiàn)WindowManager這個(gè)接口的是WindowManagerImpl類,因此直接看一下WindowManagerImpl.addView的實(shí)現(xiàn):

@Override
public void addView(@NonNull View view, 
@NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.addView(view, params, mDisplay, mParentWindow);
}

函數(shù)就只有兩句話捕儒,我們看關(guān)鍵的第二句冰啃。這里的mGlobal就是之前提到過的WindowManagerGlobal對(duì)象。它是一個(gè)單例肋层,也就意味著在一個(gè)應(yīng)用進(jìn)程里亿笤,雖然每一個(gè)Activity會(huì)對(duì)應(yīng)不同的WindowManagerImpl對(duì)象,但是它們的視圖是由唯一的一個(gè)WindowManagerGlobal對(duì)象統(tǒng)一管理栋猖。

在看WindowManagerGlobal.addView的實(shí)現(xiàn)之前净薛,有必要先說明一下WindowManagerGlobal中有三個(gè)比較重要的ArrayList:

private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();

這三個(gè)ArrayList分別保存了View, ViewRootImplWindowManager.LayoutParams.

接下來就具體分析一下WindowManagerGlobal.addView.

204    public void addView(View view, ViewGroup.LayoutParams params,
205            Display display, Window parentWindow) {
206        if (view == null) {
207            throw new IllegalArgumentException("view must not be null");
208        }
209        if (display == null) {
210            throw new IllegalArgumentException("display must not be null");
211        }
212        if (!(params instanceof WindowManager.LayoutParams)) {
213            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
214        }
215
216        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
217        if (parentWindow != null) {
218            parentWindow.adjustLayoutParamsForSubWindow(wparams);
219        } else {
220            // If there's no parent and we're running on L or above (or in the
221            // system context), assume we want hardware acceleration.
222            final Context context = view.getContext();
223            if (context != null
224                    && context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
225                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
226            }
227        }
228
229        ViewRootImpl root;
230        View panelParentView = null;
231
232        synchronized (mLock) {
233            // Start watching for system property changes.
234            if (mSystemPropertyUpdater == null) {
235                mSystemPropertyUpdater = new Runnable() {
236                    @Override public void More ...run() {
237                        synchronized (mLock) {
238                            for (int i = mRoots.size() - 1; i >= 0; --i) {
239                                mRoots.get(i).loadSystemProperties();
240                            }
241                        }
242                    }
243                };
244                SystemProperties.addChangeCallback(mSystemPropertyUpdater);
245            }
246
247            int index = findViewLocked(view, false);
248            if (index >= 0) {
249                if (mDyingViews.contains(view)) {
250                    // Don't wait for MSG_DIE to make it's way through root's queue.
251                    mRoots.get(index).doDie();
252                } else {
253                    throw new IllegalStateException("View " + view
254                            + " has already been added to the window manager.");
255                }
256                // The previous removeView() had not completed executing. Now it has.
257            }
258
259            // If this is a panel window, then find the window it is being
260            // attached to for future reference.
261            if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
262                    wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
263                final int count = mViews.size();
264                for (int i = 0; i < count; i++) {
265                    if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
266                        panelParentView = mViews.get(i);
267                    }
268                }
269            }
270
271            root = new ViewRootImpl(view.getContext(), display);
272
273            view.setLayoutParams(wparams);
274
275            mViews.add(view);
276            mRoots.add(root);
277            mParams.add(wparams);
278        }
279
280        // do this last because it fires off messages to start doing things
281        try {
282            root.setView(view, wparams, panelParentView);
283        } catch (RuntimeException e) {
284            // BadTokenException or InvalidDisplayException, clean up.
285            synchronized (mLock) {
286                final int index = findViewLocked(view, false);
287                if (index >= 0) {
288                    removeViewLocked(index, true);
289                }
290            }
291            throw e;
292        }
293    }

206-214行是對(duì)參數(shù)的正確性檢查。

然后217行有一個(gè)if語句:如果parentWindow不為null則調(diào)用其adjustLayoutParamsForSubWindow方法蒲拉。這里的parentWindow實(shí)際上指向的是WindowManagerImpl的成員變量mParentWindow. 如果我們?nèi)匀患俣ó?dāng)前的Context是一個(gè)Activity, 那么parentWindow就非空肃拜。因此Window.adjustLayoutParamsForSubWindow被調(diào)用:

551     void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
552         CharSequence curTitle = wp.getTitle();
553         if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
554             wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
                ...
581         } else {
582             if (wp.token == null) {
583                 wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
584             }
585             if ((curTitle == null || curTitle.length() == 0)
586                     && mAppName != null) {
587                 wp.setTitle(mAppName);
588             }
589         }
            ...
596     }

這里最關(guān)鍵的是第583行,將Window里保存的Activity的標(biāo)識(shí)mAppToken賦給了傳進(jìn)來的WindowManager.LayoutParamstoken. 由于這個(gè)WindowManager.LayoutParams最后會(huì)傳給WindowManagerService, 因此WindowManagerService可以通過這個(gè)token知道是哪個(gè)Activity要添加視圖雌团。

回到WindowManagerGlobal.addView, 248-257行會(huì)對(duì)要添加的view進(jìn)行重復(fù)添加的檢查燃领。如果發(fā)現(xiàn)是重復(fù)添加則拋出異常。

如果當(dāng)前要添加的view從屬于某個(gè)之前已經(jīng)添加的view锦援,261-269行就會(huì)去找出那個(gè)已經(jīng)添加的view.

接著在271行猛蔽,一個(gè)ViewRootImpl對(duì)象被創(chuàng)建出來鸵贬。ViewRootImpl在應(yīng)用進(jìn)程這邊管理視圖的過程中擔(dān)任了重要的角色小泉。像Activity對(duì)應(yīng)的視圖的measure, layout, draw等過程都是從ViewRootImpl開始。另外ViewRootImpl還負(fù)責(zé)應(yīng)用進(jìn)程和WindowManagerService進(jìn)程之間的通信臀防。

275-277行將view, rootwparams分別加入各自對(duì)應(yīng)的ArrayList. View, ViewRootImplWindowManager.LayoutParams這三者是通過ArrayList的下標(biāo)一一對(duì)應(yīng)的略板。

最后282行調(diào)用了ViewRootImpl.setView, 在這個(gè)方法中ViewRootImpl會(huì)去通知WindowManagerService將新的視圖添加到屏幕上毁枯。

總結(jié)

這篇文章所講的內(nèi)容可以濃縮成下面這張圖。

WindowManager

一個(gè)Activity會(huì)有一個(gè)成員變量指向一個(gè)WindowManager的具體實(shí)現(xiàn)類WindowManagerImpl對(duì)象叮称。該WindowManagerImpl對(duì)象會(huì)保存該Activity對(duì)應(yīng)的一個(gè)PhoneWindow對(duì)象的引用种玛。一個(gè)PhoneWindow又會(huì)引用一個(gè)DecorView對(duì)象藐鹤。在一個(gè)應(yīng)用進(jìn)程中,會(huì)有一個(gè)WindowManagerGlobal的單例管理應(yīng)用中所有Activity對(duì)應(yīng)的視圖赂韵。WindowManagerGlobal中有三個(gè)關(guān)鍵的ArrayList娱节,分別保存了View, ViewRootImplWindowManager.LayoutParams.

上文所講的這些內(nèi)容都是發(fā)生在應(yīng)用進(jìn)程里的事情。在系統(tǒng)里真正負(fù)責(zé)視圖管理的是WindowManagerService進(jìn)程右锨。其中括堤,ViewRootImpl擔(dān)任了溝通應(yīng)用進(jìn)程和WindowManagerService進(jìn)程的角色。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末绍移,一起剝皮案震驚了整個(gè)濱河市悄窃,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蹂窖,老刑警劉巖轧抗,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異瞬测,居然都是意外死亡横媚,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門月趟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來灯蝴,“玉大人,你說我怎么就攤上這事孝宗∏钤辏” “怎么了?”我有些...
    開封第一講書人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵因妇,是天一觀的道長问潭。 經(jīng)常有香客問我,道長婚被,這世上最難降的妖魔是什么狡忙? 我笑而不...
    開封第一講書人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮址芯,結(jié)果婚禮上灾茁,老公的妹妹穿的比我還像新娘。我一直安慰自己谷炸,他們只是感情好北专,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著淑廊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪特咆。 梳的紋絲不亂的頭發(fā)上季惩,一...
    開封第一講書人閱讀 51,692評(píng)論 1 305
  • 那天录粱,我揣著相機(jī)與錄音,去河邊找鬼画拾。 笑死啥繁,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的青抛。 我是一名探鬼主播旗闽,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蜜另!你這毒婦竟也來了适室?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤举瑰,失蹤者是張志新(化名)和其女友劉穎捣辆,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體此迅,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡汽畴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了耸序。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片忍些。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖坎怪,靈堂內(nèi)的尸體忽然破棺而出罢坝,到底是詐尸還是另有隱情,我是刑警寧澤芋忿,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布炸客,位于F島的核電站,受9級(jí)特大地震影響戈钢,放射性物質(zhì)發(fā)生泄漏痹仙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一殉了、第九天 我趴在偏房一處隱蔽的房頂上張望开仰。 院中可真熱鬧,春花似錦薪铜、人聲如沸众弓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽谓娃。三九已至,卻和暖如春蜒滩,著一層夾襖步出監(jiān)牢的瞬間滨达,已是汗流浹背奶稠。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留捡遍,地道東北人锌订。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像画株,于是被迫代替她去往敵國和親辆飘。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

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