Android Window與WindowManager 理解與源碼分析

Window顧名思義就是窗口织阅,Android Window的實(shí)現(xiàn)類是PhoneWindow阶祭。WindowManager是訪問Window的入口批幌,通過它可以創(chuàng)建Window经伙,WindowManager的具體實(shí)現(xiàn)在WindowService中扶叉,Window與WindowService之間的交互是一種IPC過程勿锅。Android中的界面都是通過Window來呈現(xiàn)的,比如Activity枣氧、Dialog和Toast等溢十,他們的界面都是附加在Window上的,因此View的實(shí)際管理者是Window达吞。

1.Window與WindowManager

在了解Window的工作機(jī)制之前我們先來看下如何使用WindowManager添加一個(gè)Window张弛。

        WindowManager windowManager = getWindowManager();
        Button btAdd = new Button(this);
        btAdd.setText("手動添加按鈕");
        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT);
        layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
        layoutParams.gravity = Gravity.LEFT;
        layoutParams.x = 100;
        layoutParams.y = 200;
        windowManager.addView(btAdd,layoutParams);

上面代碼是將一個(gè)Button添加到坐標(biāo)(100,200)的位置酪劫。下面簡單介紹下WindowManager.LayoutParams中的flags與type這兩個(gè)參數(shù)吞鸭。

flags表示的是Window的屬性,有很多選擇項(xiàng)覆糟,簡單介紹幾種刻剥。

FLAG_NOT_FOCUSABLE

表示W(wǎng)indow不需要獲取焦點(diǎn),又不需要接受任何輸入事件滩字,次標(biāo)記還會同時(shí)啟用FLAG_NOT_TOUCH_MODAL造虏,最終事件會傳遞給下層有焦點(diǎn)的Window。

FLAG_NOT_TOUCH_MODAL:

此模式下系統(tǒng)會將當(dāng)前Window區(qū)域以外的單擊事件傳遞給底層的Window麦箍,當(dāng)前區(qū)域以內(nèi)的單擊事件則自己處理漓藕。

FLAG_SHOW_WHEN_LOCKED

開啟當(dāng)前模式,可以讓W(xué)indow顯示在鎖屏界面上挟裂。

如果了解其他的屬性享钞,建議還是看下源碼:

 /** Window flag: as long as this window is visible to the user, allow
         *  the lock screen to activate while the screen is on.
         *  This can be used independently, or in combination with
         *  {@link #FLAG_KEEP_SCREEN_ON} and/or {@link #FLAG_SHOW_WHEN_LOCKED} */
        public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON     = 0x00000001;

        /** Window flag: everything behind this window will be dimmed.
         *  Use {@link #dimAmount} to control the amount of dim. */
        public static final int FLAG_DIM_BEHIND        = 0x00000002;

        /** Window flag: blur everything behind this window.
         * @deprecated Blurring is no longer supported. */
        @Deprecated
        public static final int FLAG_BLUR_BEHIND        = 0x00000004;

type表示W(wǎng)indow的類型,一般Window有三種類型:應(yīng)用Window诀蓉、子Window和系統(tǒng)Window栗竖。應(yīng)用類的Window對應(yīng)著一個(gè)Activity。子Window是不能單獨(dú)存在的交排,他需要在特定的父Window之中划滋,比如常見的Dialog就是一個(gè)子Window。系統(tǒng)Window需要聲明特殊的權(quán)限才能創(chuàng)建埃篓,比如Toast跟系統(tǒng)狀態(tài)欄等。

Window是分層的根资,每個(gè)Window都有對應(yīng)的z-ordered架专,層級大的會覆蓋在層級小的Window的上面,這和HTML中的z-index的概念是完全一致的玄帕。在三類Window中部脚,應(yīng)用Window的層級范圍是1~99,子Window的層級范圍是1000~1999裤纹,系統(tǒng)Window的層級范圍是2000~2999委刘,這些層級范圍對應(yīng)著WindowManager.LayoutParams的type參數(shù)丧没。如果想要Window位于所有Window的最頂層,那么采用較大的層級即可锡移。很顯然系統(tǒng)Window的層級是最大的呕童,而且系統(tǒng)層級有很多值,一般我們可以選用TYPE_SYSTEM_OVERLAY或者TYPE_SYSTEM_ERROR淆珊,如果采用TYPE_SYSTEM_ERROR夺饲,只需要為type參數(shù)指定這個(gè)層級即可:mLayoutParams.type = LayoutParams.TYPE_SYSTEM_ERROR;同時(shí)聲明權(quán)限:<uses-permissionandroid: name= "android.permission .SYSTEM_ALERT_WINDOW"/>施符。因?yàn)橄到y(tǒng)類型的Window是需要檢查權(quán)限的往声,如果不在AndroidManifest 中使用相應(yīng)的權(quán)限,那么創(chuàng)建Window的時(shí)候就會報(bào)錯(cuò)戳吝。

WindowManager的功能比較簡單浩销,常用的就是三個(gè)方法:addView、updateViewLayout和removeView听哭,這三個(gè)方法都定義在ViewManager中撼嗓,WindowManager繼承了ViewManager。

/** Interface to let you add and remove child views to an Activity. To get an instance
  * of this class, call {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.
  */
public interface ViewManager
{
    /**
     * Assign the passed LayoutParams to the passed View and add the view to the window.
     * <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming
     * errors, such as adding a second view to a window without removing the first view.
     * <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a
     * secondary {@link Display} and the specified display can't be found
     * (see {@link android.app.Presentation}).
     * @param view The view to be added to this window.
     * @param params The LayoutParams to assign to view.
     */
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

對于我們開發(fā)者來說欢唾,WindowManager常用的就只有這三個(gè)功能且警,當(dāng)然這三個(gè)方法也就足夠用了。WindowManager操作Window其實(shí)就是在操作里面的View礁遣。通過這些方法斑芜,我們可以實(shí)現(xiàn)諸如隨意拖拽位置的Window等效果。

2.Window的內(nèi)部機(jī)制

Window是一個(gè)抽象類祟霍,每個(gè)Window都對應(yīng)一個(gè)View跟一個(gè)ViewRootImpl杏头,Window跟View是通過ViewRootImpl建立聯(lián)系的,因此Window并不實(shí)際存在沸呐,它是以View的形式存在的醇王。從WindowManager的定義跟主要方法也能看出,View是Window存在的實(shí)體崭添。下面就具體介紹下Window的addView寓娩、updateViewLayout和removeView。

2.1Window的添加過程呼渣。

Window的添加過程需要通過WindowManager的addView來實(shí)現(xiàn)棘伴,不過WindowManager是一個(gè)接口,真正實(shí)現(xiàn)是在WindowManagerImpl中屁置,三個(gè)主要操作焊夸,先上源碼:

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }
    @Override
    public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.updateViewLayout(view, params);
    }
    @Override
    public void removeView(View view) {
        mGlobal.removeView(view, false);
    }

上面的源碼很明顯,WindowManagerImpl也沒有直接實(shí)現(xiàn)Window的三大操作蓝角,而是由WindowManagerGlobal來處理的阱穗,代碼段:private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); 可以看出饭冬,WindowManagerGlobal以工廠的形式向外提供自己的實(shí)例。WindowManagerImpl這種工作模式是典型的橋接模式揪阶,將所有的操作委托給WindowManagerGlobal來實(shí)現(xiàn)昌抠。具體看下addView的源碼:

1.檢查參數(shù)是否合法,子Window還需要調(diào)整布局:

......
public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (display == null) {
            throw new IllegalArgumentException("display must not be null");
        }
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else {
            // If there's no parent, then hardware acceleration for this view is
            // set from the application's hardware acceleration setting.
            final Context context = view.getContext();
            if (context != null
                    && (context.getApplicationInfo().flags
                            & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
            }
        }
......

2.創(chuàng)建ViewRootImpl并將View添加到列表中

WindowManagerGlobal中的幾個(gè)重要列表:

    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>();
    private final ArraySet<View> mDyingViews = new ArraySet<View>();

mViews存儲的是Window中對應(yīng)的View遣钳,mRoots則是Window中對應(yīng)的對應(yīng)的ViewRootImpl扰魂,mParams則是對應(yīng)的布局。mDyingViews存儲的是正在被刪除的View蕴茴。

......
 root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
......            

上面源碼表示了addView添加View的過程劝评。

3.通過ViewRootImpl來更新界面,完成Window的添加過程

......
// do this last because it fires off messages to start doing things
            try {
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
......

ViewRootImpl的setView方法在界面View的時(shí)候有說到倦淀,在setView內(nèi)部蒋畜,通過requestLayout方法實(shí)現(xiàn)View的更新。

......
  // Schedule the first layout -before- adding to the window
                // manager, to make sure we do the relayout before receiving
                // any other events from the system.
                requestLayout();
......

接著通過WindowSession來完成Window的添加過程撞叽。下面的源碼中姻成,mWindowSession是IWindowSession的實(shí)例,這是一個(gè)Binder對象愿棋,真正的實(shí)現(xiàn)類是Session科展,也就是說Window的添加過程是一次IPC調(diào)用。

 try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                } catch (RemoteException e) {
                    mAdded = false;
                    mView = null;
                    mAttachInfo.mRootView = null;
                    mInputChannel = null;
                    mFallbackEventHandler.setView(null);
                    unscheduleTraversals();
                    setAccessibilityFocus(null, null);
                    throw new RuntimeException("Adding window failed", e);
                } finally {
                    if (restore) {
                        attrs.restore();
                    }
                }

Session內(nèi)部會通過WindowManagerService來實(shí)現(xiàn)Window的添加過程糠雨。

介紹到這里才睹,各位就發(fā)現(xiàn),Window的添加請求是交給WindowManagerService去處理的甘邀,WindowManagerService內(nèi)部會為每一個(gè)應(yīng)用保留一個(gè)單獨(dú)的Session琅攘。具體的代碼的邏輯大家看下源碼,這里主要介紹部分源碼松邪,還是以流程為主坞琴。

2.2Window的刪除過程

Window的刪除過程與添加過程一樣,都是先通過WindowManagerImpl然后通過WindowManagerGlobal來實(shí)現(xiàn)的:

 public void removeView(View view, boolean immediate) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }

        synchronized (mLock) {
            int index = findViewLocked(view, true);
            View curView = mRoots.get(index).getView();
            removeViewLocked(index, immediate);
            if (curView == view) {
                return;
            }

            throw new IllegalStateException("Calling with view " + view
                    + " but the ViewAncestor is attached to " + curView);
        }
    }

removeView的過程還是比較簡潔的逗抑,先findViewLocked找到待刪除的View的索引剧辐,這個(gè)索引是上面說的ArrayList mViews的index,然后刪除掉這個(gè)就可以了锋八。

 private int findViewLocked(View view, boolean required) {
        final int index = mViews.indexOf(view);
        if (required && index < 0) {
            throw new IllegalArgumentException("View=" + view + " not attached to window manager");
        }
        return index;
    }
 private void removeViewLocked(int index, boolean immediate) {
        ViewRootImpl root = mRoots.get(index);
        View view = root.getView();

        if (view != null) {
            InputMethodManager imm = InputMethodManager.getInstance();
            if (imm != null) {
                imm.windowDismissed(mViews.get(index).getWindowToken());
            }
        }
        boolean deferred = root.die(immediate);
        if (view != null) {
            view.assignParent(null);
            if (deferred) {
                mDyingViews.add(view);
            }
        }
    }

在WindowManager中提供了兩種刪除的接口:removeView跟removeViewImmediate浙于,他們分別表示異步跟同步刪除,removeViewImmediate方法一般不會使用挟纱,以免刪除Window發(fā)生意外錯(cuò)誤。我們重點(diǎn)看下異步刪除的情況腐宋。代碼段6可以看到紊服,刪除操作是通過ViewRootImpl的die方法完成的檀轨,具體看下這個(gè)方法:

/**
     * @param immediate True, do now if not in traversal. False, put on queue and do later.
     * @return True, request has been queued. False, request has been completed.
     */
    boolean die(boolean immediate) {
        // Make sure we do execute immediately if we are in the middle of a traversal or the damage
        // done by dispatchDetachedFromWindow will cause havoc on return.
        if (immediate && !mIsInTraversal) {
            doDie();
            return false;
        }

        if (!mIsDrawing) {
            destroyHardwareRenderer();
        } else {
            Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
                    "  window=" + this + ", title=" + mWindowAttributes.getTitle());
        }
        mHandler.sendEmptyMessage(MSG_DIE);
        return true;
    }

看上面的代碼,你會發(fā)現(xiàn)欺嗤,die方法只是發(fā)了一個(gè)請求参萄,然后就返回了,再看代碼段6煎饼,View被加到mDyingViews中了讹挎。異步刪除可以看到發(fā)送了一個(gè)message,MSG_DIE吆玖,然后ViewRootImpl的handler會處理此消息然后調(diào)用die方法枫浙,同步的話就直接刪除了澄者。這也是這兩種刪除方式的區(qū)別。真正刪除View的邏輯在doDie方法的dispatchDetachedFromWindow方法中。

 void doDie() {
        checkThread();
        if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);
        synchronized (this) {
            if (mRemoved) {
                return;
            }
            mRemoved = true;
            if (mAdded) {
                dispatchDetachedFromWindow();
            }

            if (mAdded && !mFirst) {
                destroyHardwareRenderer();

                if (mView != null) {
                    int viewVisibility = mView.getVisibility();
                    boolean viewVisibilityChanged = mViewVisibility != viewVisibility;
                    if (mWindowAttributesChanged || viewVisibilityChanged) {
                        // If layout params have been changed, first give them
                        // to the window manager to make sure it has the correct
                        // animation info.
                        try {
                            if ((relayoutWindow(mWindowAttributes, viewVisibility, false)
                                    & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
                                mWindowSession.finishDrawing(mWindow);
                            }
                        } catch (RemoteException e) {
                        }
                    }

                    mSurface.release();
                }
            }

            mAdded = false;
        }
        WindowManagerGlobal.getInstance().doRemoveView(this);
    }
 void dispatchDetachedFromWindow() {
        if (mView != null && mView.mAttachInfo != null) {
            mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
            mView.dispatchDetachedFromWindow();
        }
        mAccessibilityInteractionConnectionManager.ensureNoConnection();
        mAccessibilityManager.removeAccessibilityStateChangeListener(
                mAccessibilityInteractionConnectionManager);
        mAccessibilityManager.removeHighTextContrastStateChangeListener(
                mHighContrastTextManager);
        removeSendWindowContentChangedCallback();
        destroyHardwareRenderer();
        setAccessibilityFocus(null, null);
        mView.assignParent(null);
        mView = null;
        mAttachInfo.mRootView = null;
        mSurface.release();
        if (mInputQueueCallback != null && mInputQueue != null) {
            mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
            mInputQueue.dispose();
            mInputQueueCallback = null;
            mInputQueue = null;
        }
        if (mInputEventReceiver != null) {
            mInputEventReceiver.dispose();
            mInputEventReceiver = null;
        }
        try {
            mWindowSession.remove(mWindow);
        } catch (RemoteException e) {
        }
        // Dispose the input channel after removing the window so the Window Manager
        // doesn't interpret the input channel being closed as an abnormal termination.
        if (mInputChannel != null) {
            mInputChannel.dispose();
            mInputChannel = null;
        }
        mDisplayManager.unregisterDisplayListener(mDisplayListener);
        unscheduleTraversals();
    }

從上面的源碼可以看到掷匠,dispatchDetachedFromWindow主要做了3件事:

(1)垃圾回收相關(guān)工作

(2)通過Session的remove方法刪除Window

(3)調(diào)用View的dispatchDetachedFromWindow方法,內(nèi)部調(diào)用View的onDetachedFromWindow方法,這個(gè)也是做一些資源回收比較合適的時(shí)機(jī),比如終止動畫、停止線程等汹粤。

最終doDie方法調(diào)用WindowManagerGlobal的doRemoveView刷新數(shù)據(jù)嘱兼。

2.3Window的更新過程

介紹完Window的刪除芹壕,Window的更新過程直接上源碼:

 public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;

        view.setLayoutParams(wparams);

        synchronized (mLock) {
            int index = findViewLocked(view, true);
            ViewRootImpl root = mRoots.get(index);
            mParams.remove(index);
            mParams.add(index, wparams);
            root.setLayoutParams(wparams, false);
        }
    }

看源碼還是很簡單的踢涌,首先更新LayoutParams,然后通過ViewRootImpl的setLayoutParams更新ViewRootImpl中的LayoutParams背苦,然后ViewRootImpl通過scheduleTraversals對View重新布局行剂,包括測量,布局固阁,繪制這三個(gè)過程备燃。除了View本身重繪之外并齐,ViewRootImpl會通過Session來更新Window視圖况褪,同樣也是有WindowManagerService的relayoutWindow來實(shí)現(xiàn)的测垛,也是一個(gè)IPC過程。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末食侮,一起剝皮案震驚了整個(gè)濱河市目胡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌誉己,老刑警劉巖眉尸,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件地消,死亡現(xiàn)場離奇詭異戒劫,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)淘邻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進(jìn)店門茵典,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人宾舅,你說我怎么就攤上這事统阿。” “怎么了筹我?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵扶平,是天一觀的道長。 經(jīng)常有香客問我蔬蕊,道長结澄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任岸夯,我火速辦了婚禮麻献,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘猜扮。我一直安慰自己勉吻,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布破镰。 她就那樣靜靜地躺著餐曼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鲜漩。 梳的紋絲不亂的頭發(fā)上源譬,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天,我揣著相機(jī)與錄音孕似,去河邊找鬼踩娘。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的养渴。 我是一名探鬼主播雷绢,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼理卑!你這毒婦竟也來了翘紊?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤藐唠,失蹤者是張志新(化名)和其女友劉穎帆疟,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宇立,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡踪宠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了妈嘹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片柳琢。...
    茶點(diǎn)故事閱讀 40,013評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖润脸,靈堂內(nèi)的尸體忽然破棺而出柬脸,到底是詐尸還是另有隱情,我是刑警寧澤津函,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布肖粮,位于F島的核電站,受9級特大地震影響尔苦,放射性物質(zhì)發(fā)生泄漏涩馆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一允坚、第九天 我趴在偏房一處隱蔽的房頂上張望魂那。 院中可真熱鬧,春花似錦稠项、人聲如沸涯雅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽活逆。三九已至,卻和暖如春拗胜,著一層夾襖步出監(jiān)牢的瞬間蔗候,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工埂软, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留锈遥,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像所灸,于是被迫代替她去往敵國和親丽惶。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評論 2 355

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