WindowManagerService本地窗口動畫

一、前言

android的WindowManagerService(簡稱wms)是系統(tǒng)框架一個非常龐大復(fù)雜的一個系統(tǒng)模塊,它主要由三大塊組成:wms數(shù)據(jù)結(jié)構(gòu),wms大遍歷,wms的窗口動畫


wms總體圖.png

wms數(shù)據(jù)結(jié)構(gòu)就是wms的所有WindowState(繼承windowcontainer)集合的數(shù)據(jù)結(jié)構(gòu),比如有ActivityRecord(包含1個或者多個WindowState),比如有WindowState,其中ActivityRecord具體表現(xiàn)實例就是Activity,WindowState具體表現(xiàn)實例有狀態(tài)欄、導(dǎo)航鍵、輸入法等。


window.png

wms大遍歷(performSurfacePlacement)就是對當(dāng)前所有存在的window進行窗口大小計算和窗口繪制狀態(tài)更新,最后把窗口Surface更新到surfaceflinger。

wms的窗口動畫是其中一個比較重要的子功能,wms的窗口動畫負責(zé)窗口間的切換動畫的實現(xiàn)。

接下來我們從android動畫原理開始來逐步介紹wms的窗口動畫

二、android動畫的一個demo

android動畫主要有三種類型:view的動畫、window的動畫、畫布對象的動畫(ondraw里面的畫圖api)
首先我們來看一個android動畫的簡單實現(xiàn)的demo

Choreographer mChoreographer = Choreographer.getInstance();
Animation mAnimation = null;

public void start(Animation anim) {
    mAnimation = anim;
    scheduleAnimation();
}

private void scheduleAnimation() {
    mChoreographer.postFrameCallback(Choreographer.CALLBACK_ANIMTION, mUpdateRunnable, null);
}

private Runnable mUpdateRunnable = new Runnable() {
    @Override
    public void run() {
        if (mAnimation != null) {
            long time = SystemClock.uptimeMillis();
            Transformation transform = new Transformation();
            //根據(jù)當(dāng)前time計算transform
            boolean more = mAnimation.getTransformation(time, transform);
            
            //根據(jù)transform進行渲染,改變view的屬性(大小、位置、透明度等)?改變窗口的屬性(大小、位置、透明度等)?
            PERFORM_RENDER_WITH_TRANSFORMATION(transform);
            
            //通過time的計算可以計算出動畫是否繼續(xù)還是結(jié)束
            if (more) {
                scheduleAnimation();
            } else {
                mAnimation = null;
            }
        }
    }
};

從這個例子可以看出android的動畫就是借用Choreographer來通過vsync原理逐幀控制動畫的播放(需要對Choreographer有一定的了解),中間update變量transform包含了動畫的基本元素:Matrix、透明度,然后根據(jù)這兩個元素對顯示對象(view或者畫布對象或者window?)進行當(dāng)前時間的繪制,逐幀顯示,最終用戶看到的就是一個動畫,從systrace可以看到


動畫systrace.png

ValueAnimator屬性動畫的實現(xiàn)原理也是類似于這個demo的實現(xiàn)

三、WindowManagerService窗口動畫機制

android的WindowManagerService窗口動畫機制一直在優(yōu)化進步,主要體現(xiàn)在:
1、在androidP以前的版本,主要是通過WindowAnimator主動畫類中的mChoreographer來通過vsync原理逐幀控制窗口動畫的播放
具體窗口的動畫變化由WindowStateAnimator的stepAnimationLocked來控制,通過改變窗口的大小、位置、透明度(通過SurfaceControl代理實現(xiàn)對surfaceflinger的調(diào)用),來最終達到窗口動畫的實現(xiàn)


wms歷史版本動畫時序圖.png

有興趣的可以去仔細研究下這部分代碼的實現(xiàn),雖然是歷史版本的舊代碼,但是這個對wms的學(xué)習(xí)理解有很大的幫助。

/*frameworks/base/services/core/java/com/android/wm/WindowAnimator.java */

/** Locked on mService.mWindowMap. */
private void animateLocked(long frameTimeNs) {

這個方案有個很大的缺陷,那就是動畫的所有實現(xiàn)的代碼都包含在wms的主鎖mGlobalLock里面,從動畫主要方法的命名后綴locked可以得知,那么意味動畫會跟wms其他所有流程搶CPU資源,就容易導(dǎo)致wms主鎖的卡頓,在某些復(fù)雜的用戶場景下,容易導(dǎo)致手機的卡頓,給用戶帶來糟糕的體驗。

2、在androidP及之后的版本,google對窗口動畫進行了重構(gòu),主要思想是通過ValueAnimator屬性動畫來播放窗口動畫,把窗口動畫播放從wms主鎖脫離出來,這樣動畫就不會占用wms資源,從而達到優(yōu)化系統(tǒng)框架運行速度的效果,同時把部分動畫放到app遠端播放(比如狀態(tài)欄、導(dǎo)航鍵動畫,比如多任務(wù)動畫),達到系統(tǒng)和APP雙端協(xié)調(diào)播放復(fù)雜的跨端動畫效果

3、wms的新窗口動畫主要分為兩種類型,LocalAnimationAdapter和RemoteAnimationAdapter,分別實現(xiàn)了wms本地窗口動畫和遠程窗口動畫。遠程窗口動畫機制,主要是為了實現(xiàn)android的兩個新功能特意開發(fā)的機制,一個是從桌面點擊app圖標(biāo)進入app的入場動畫和app退出的出場動畫,一個是在app界面,通過拖動底部指示條進入桌面的滑動效果動畫,這兩個動畫效果最先是iphone實現(xiàn)的,google為了仿iphone的實現(xiàn),所以開發(fā)了遠程動畫機制,最終能達到iphone的動畫效果,提高了android手機的復(fù)雜動畫效果

4、本文主要介紹android新動畫的流程和實現(xiàn)的原理,主要介紹了LocalAnimationAdapter本地窗口動畫實現(xiàn)原理

四、新動畫機制Local窗口動畫流程

本地窗口動畫具體場景:可以在設(shè)置首頁點擊其中一項菜單,進入設(shè)置某項子菜單,然后就會有一個Local窗口動畫的播放

LocalAnimationAdapter,字面意思就是wms本地窗口動畫,該動畫在SurfaceAnimationThread(線程名android.anim.lf)線程播放,注意看該類的注釋
(本文剩余源碼基于androidS原生源碼)

/*frameworks/base/services/core/java/com/android/wm/SurfaceAnimationThread.java */

/**
 * Thread for running {@link SurfaceAnimationRunner} that does not hold the window manager lock.
 */
public final class SurfaceAnimationThread extends ServiceThread {

這個才是新動畫機制的核心要義,不占用wms主鎖,就不會占用wms的資源,這個已經(jīng)是對wms很大的優(yōu)化了,android歷史版本因為wms鎖卡頓的問題太多了

接下來我們通過閱讀源碼來分析LocalAnimationAdapter的實現(xiàn)流程,先看下整體的Local窗口動畫時序圖


Local窗口動畫時序圖.png

1、動畫播放源頭類AppTransitionController的方法handleAppTransitionReady
在一次wms大遍歷(performSurfacePlacement)流程結(jié)束之后,就會檢查app transition是否已經(jīng)準(zhǔn)備好,opening 的app準(zhǔn)備好需要滿足app的starting窗口是否已經(jīng)displayed或者app的window是否已經(jīng)alldrawn,只要滿足其中一個條件,就說明app的窗口動畫流程可以開始了了。

/*frameworks/base/services/core/java/com/android/wm/AppTransitionController.java */

    void handleAppTransitionReady() {
        mTempTransitionReasons.clear();
        //檢查app transition是否已經(jīng)準(zhǔn)備好
        if (!transitionGoodToGo(mDisplayContent.mOpeningApps, mTempTransitionReasons)
                || !transitionGoodToGo(mDisplayContent.mChangingContainers,
                        mTempTransitionReasons)) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady");

        ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "**** GOOD TO GO");

首先會獲取當(dāng)前需要opening和closing的app window列表(ActivityRecord類型)

/*frameworks/base/services/core/java/com/android/wm/AppTransitionController.java */

        final ActivityRecord topOpeningApp =
                getTopApp(mDisplayContent.mOpeningApps, false /* ignoreHidden */);
        final ActivityRecord topClosingApp =
                getTopApp(mDisplayContent.mClosingApps, false /* ignoreHidden */);

然后在applyAnimations方法里面對window列表進行遍歷WindowContainer的動畫applyAnimation方法的調(diào)用

/*frameworks/base/services/core/java/com/android/wm/AppTransitionController.java */

     private void applyAnimations(ArraySet<WindowContainer> wcs, ArraySet<ActivityRecord> apps,
            @TransitionOldType int transit, boolean visible, LayoutParams animLp,
            boolean voiceInteraction) {
        final int wcsCount = wcs.size();
        for (int i = 0; i < wcsCount; i++) {
            final WindowContainer wc = wcs.valueAt(i);
            final ArrayList<ActivityRecord> transitioningDescendants = new ArrayList<>();
            for (int j = 0; j < apps.size(); ++j) {
                final ActivityRecord app = apps.valueAt(j);
                if (app.isDescendantOf(wc)) {
                    transitioningDescendants.add(app);
                }
            }
            wc.applyAnimation(animLp, transit, visible, voiceInteraction, transitioningDescendants);
        }
    }

2、在WindowContainer,會先收集getAnimationAdpater當(dāng)前window的動畫適配器

/*frameworks/base/services/core/java/com/android/wm/WindowContainer.java */

    protected void applyAnimationUnchecked(WindowManager.LayoutParams lp, boolean enter,
            @TransitionOldType int transit, boolean isVoiceInteraction,
            @Nullable ArrayList<WindowContainer> sources) {
 
        final Pair<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp,
                transit, enter, isVoiceInteraction);

如果是普通的窗口動畫,比如app內(nèi)部activity的切換,當(dāng)前的場景是設(shè)置主菜單跳轉(zhuǎn)子菜單,根據(jù)當(dāng)前場景獲取到具體的transit,transit=TRANSIT_OLD_ACTIVITY_OPEN,然后再結(jié)合enter為true或者false,可以最終可以找到設(shè)置主菜單的動畫xml資源是activity_open_exit.xml,設(shè)置子菜單的動畫xml資源是activity_open_enter.xml,在獲取到具體xml資源名字后,通過AnimationUtils.loadAnimation方法把xml資源轉(zhuǎn)成Animation對象。
之后就會創(chuàng)建一個WindowAnimationSpec對象,并把Animation對象作為構(gòu)造方法的第一個參數(shù)傳給了WindowAnimationSpec

/*frameworks/base/services/core/java/com/android/wm/WindowContainer.java */

                final Animation a = loadAnimation(lp, transit, enter, isVoiceInteratction);
                AnimationAdapter adapter = new LocalAnimationAdapter(
                        //創(chuàng)建了一個WindowAnimatonSpec對象作為LocalAnimationAdapter的初始化參數(shù)
                        new WindowAnimationSpec(a, mTmpPoint, mTmpRect,
                                getDisplayContent().mAppTransition.canSkipFirstFrame(),
                                appRootTaskClipMode, true /* isAppAnimation */, windowCornerRadius),
                        getSurfaceAnimationRunner());

這里創(chuàng)建LocalAnimationAdapter對象的時候同時創(chuàng)建了一個WindowAnimatonSpec對象作為LocalAnimationAdapter的初始化參數(shù),這個類WindowAnimatonSpec比較重要,是在后續(xù)窗口動畫播放的時候具體的實現(xiàn)類,后面再分析
在獲取到具體的Adaper對象之后,就開始執(zhí)行startAnimation方法,這個方法里面主要調(diào)用了mSurfaceAnimator對象,來實現(xiàn)startAnimation

/*frameworks/base/services/core/java/com/android/wm/WindowContainer.java */

        mSurfaceAnimator.startAnimation(t, anim, hidden, type, animationFinishedCallback,
                mSurfaceFreezer);

tip: WindowContainer這個類是wms的最重要類之一,它是所有window的基類,充分學(xué)習(xí)理解該類可以對wms的所有window的樹狀圖有一定的理解

3、SurfaceAnimator類,字面上的意思就是window動畫實現(xiàn)是交給它來實現(xiàn)surfacecontrol的動畫(舊窗口動畫是通過WindowSurfaceController控制surfacecontrol),該類的就是窗口動畫的中控,它的主要作用是在startAnimation的時候,對要進行動畫的surfacecontrol創(chuàng)建一個parent的surfacecontrol類型的mLeash對象,leash的翻譯是用皮帶系住的意思,相當(dāng)于把要進行動畫的surfacecontrol用皮帶系住,通過操控mLeash對象來實現(xiàn)窗口的大小、位置、透明度等動畫屬性的改變。

/*frameworks/base/services/core/java/com/android/wm/SurfaceAnimator.java */

    void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
            @AnimationType int type,
            @Nullable OnAnimationFinishedCallback animationFinishedCallback,
            @Nullable SurfaceFreezer freezer) {
        cancelAnimation(t, true /* restarting */, true /* forwardCancel */);
        mAnimation = anim;
        mAnimationType = type;
        mAnimationFinishedCallback = animationFinishedCallback;
        final SurfaceControl surface = mAnimatable.getSurfaceControl();
        mLeash = freezer != null ? freezer.takeLeashForAnimation() : null;
        if (mLeash == null) {
            //重點關(guān)注這個mLeash對象,該對象是窗口動畫專屬surfacecontrol包裝對象
            mLeash = createAnimationLeash(mAnimatable, surface, t, type,
                    mAnimatable.getSurfaceWidth(), mAnimatable.getSurfaceHeight(), 0 /* x */,
                    0 /* y */, hidden, mService.mTransactionFactory);
            mAnimatable.onAnimationLeashCreated(t, mLeash);
        }
        mAnimatable.onLeashAnimationStarting(t, mLeash);
        mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);
    }

然后在動畫結(jié)束之后,mLeash對象會走銷毀的流程,同時動畫的surfacecontrol進行reparent還原操作。

/*frameworks/base/services/core/java/com/android/wm/SurfaceAnimator.java */

    static boolean removeLeash(Transaction t, Animatable animatable, @NonNull SurfaceControl leash,
            boolean destroy) {
        boolean scheduleAnim = false;
        final SurfaceControl surface = animatable.getSurfaceControl();
        final SurfaceControl parent = animatable.getParentSurfaceControl();
        final boolean reparent = surface != null;
        if (reparent) {
            if (surface.isValid() && parent != null && parent.isValid()) {
                t.reparent(surface, parent);
                scheduleAnim = true;
            }
        }

窗口動畫中控最終調(diào)用了動畫的適配類LocalAnimationAdapter的startAnimation方法。

/*frameworks/base/services/core/java/com/android/wm/SurfaceAnimator.java */

        mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);

4、LocalAnimationAdapter是窗口動畫的適配類,繼承自AnimationAdaper,LocalAnimationAdapter實現(xiàn)的是wms本地窗口動畫,所以LocalAnimationAdapter可以理解成本地窗口動畫的中轉(zhuǎn)類。在startAnimation方法里面,調(diào)用了SurfaceAnimationRunner來最終實現(xiàn)動畫的播放。

/*frameworks/base/services/core/java/com/android/wm/LocalAnimationAdapter.java */

    @Override
    public void startAnimation(SurfaceControl animationLeash, Transaction t,
            @AnimationType int type, OnAnimationFinishedCallback finishCallback) {
        mAnimator.startAnimation(mSpec, animationLeash, t,
                () -> finishCallback.onAnimationFinished(type, this));
    }

5、SurfaceAnimationRunner是本地窗口動畫真正的實現(xiàn)類,主要需要關(guān)注的方法是startAnimationLocked,首先這個方法已經(jīng)通過mChoreographer切換到SurfaceAnimationThread線程來執(zhí)行,然后創(chuàng)建了ValueAnimator屬性動畫對象,交由ValueAnimator屬性動畫對象的addUpdateListener方法來實現(xiàn)逐幀控制動畫mLeash對象(surfacecontrol類型)的變化,具體的update方法的實現(xiàn)是在WindowAnimatonSpec類的apply方法里面。

/*frameworks/base/services/core/java/com/android/wm/SurfaceAnimationRunner.java */

    private void startAnimationLocked(RunningAnimation a) {
        final ValueAnimator anim = mAnimatorFactory.makeAnimator();

        anim.overrideDurationScale(1.0f);
        anim.setDuration(a.mAnimSpec.getDuration());
        anim.addUpdateListener(animation -> {
            synchronized (mCancelLock) {
                if (!a.mCancelled) {
                    final long duration = anim.getDuration();
                    long currentPlayTime = anim.getCurrentPlayTime();
                    if (currentPlayTime > duration) {
                        currentPlayTime = duration;
                    }
                    applyTransformation(a, mFrameTransaction, currentPlayTime);
                }
            }

            scheduleApplyTransaction();
        });

        ………………
        a.mAnim = anim;
        mRunningAnimations.put(a.mLeash, a);

        //窗口動畫最終調(diào)用了屬性動畫播放
        anim.start();
        anim.doAnimationFrame(mChoreographer.getFrameTime());
    }

再來看下apply方法的具體實現(xiàn),通過之前以具體xml資源創(chuàng)建的mAnimation對象,根據(jù)當(dāng)前時間片currentPlayTime獲取到當(dāng)前的tmp.transformation,對leash對象實現(xiàn)了Matrix(大小,位置),Alpha,Crop等transformation變化,再通過Transaction 交給surfaceflinger顯示,從而實現(xiàn)了動畫當(dāng)前時間片的顯示效果。對比舊動畫機制,這個transformation變化是在WindowStateAnimator類里面實現(xiàn)的。為什么要重點關(guān)注這個方法呢?因為如果窗口動畫出bug了(位置大小不對?透明度異常?),就可以在這個方法里面打印window的相關(guān)參數(shù)來初步定位原因。

/*frameworks/base/services/core/java/com/android/wm/WindowAnimatonSpec.java */

    @Override
    public void apply(Transaction t, SurfaceControl leash, long currentPlayTime) {
        final TmpValues tmp = mThreadLocalTmps.get();
        tmp.transformation.clear();
        mAnimation.getTransformation(currentPlayTime, tmp.transformation);
        tmp.transformation.getMatrix().postTranslate(mPosition.x, mPosition.y);
        t.setMatrix(leash, tmp.transformation.getMatrix(), tmp.floats);
        t.setAlpha(leash, tmp.transformation.getAlpha());

        boolean cropSet = false;
        if (mRootTaskClipMode == ROOT_TASK_CLIP_NONE) {
            if (tmp.transformation.hasClipRect()) {
                t.setWindowCrop(leash, tmp.transformation.getClipRect());
                cropSet = true;
            }
        } else {
            mTmpRect.set(mRootTaskBounds);
            if (tmp.transformation.hasClipRect()) {
                mTmpRect.intersect(tmp.transformation.getClipRect());
            }
            t.setWindowCrop(leash, mTmpRect);
            cropSet = true;
        }
    }

6、ValueAnimator類,從上面的介紹可以得知,窗口動畫的最終本質(zhì)就是一個ValueAnimator屬性動畫,理解了這一點,就相當(dāng)于把窗口動畫簡單化了,最終的實現(xiàn)就類比于我們普通app的屬性動畫的實現(xiàn)(app屬性動畫的對象是view,窗口屬性動畫的對象是window),只不過整個流程比較復(fù)雜而已,但是最終的實現(xiàn)原理是一樣的,殊途同歸,這個才是android窗口動畫機制的精髓所在。

/*frameworks/base/services/core/java/com/android/wm/SurfaceAnimationRunner.java */

ValueAnimator anim = mAnimatorFactory.makeAnimator();
anim.addUpdateListener(animation -> {
            applyTransformation(a, mFrameTransaction, currentPlayTime);
        });
anim.start();

總結(jié)

本文只是講解了WindowManagerService窗口動畫之本地窗口動畫的原生實現(xiàn)流程,主要是WindowManagerService的子類比較多,所以我們從動畫的源頭handleAppTransitionReady一步一步分析了它的整個流程,從整個流程來看,本地窗口動畫的邏輯比較清晰,線路也比較單一,比較容易學(xué)習(xí)和理解,最終我們看到,窗口動畫的原理就是一個屬性動畫,在動畫update方法里面操控了窗口surface的屬性變化,從而實現(xiàn)了窗口動畫的逐幀播放。另外還有一個重點需要關(guān)注到,那就是wms的窗口動畫不需要占用wms主鎖,而且是單獨線程,這樣的設(shè)計也能在一定程度上優(yōu)化系統(tǒng)卡頓的問題。

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

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