程序员自我修养之解析WindowManagerService

一、WMS的作用

窗口管理

WMS是窗口的管理者,它负责窗口的启动、添加和删除,另外窗口的大小和层级也是由WMS进行管理的。窗口管理的核心成员有DisplayContent、WindowToken和WindowState。

窗口动画

窗口间进行切换时,使用窗口动画可以显得更炫一些,窗口动画由WMS的动画子系统来负责,动画子系统的管理者为WindowAnimator。

输入系统的中转站

通过对窗口的触摸从而产生触摸事件,InputManagerService(IMS)会对触摸事件进行处理,它会寻找一个最合适的窗口来处理触摸反馈信息,WMS是窗口的管理者,因此,WMS“理所应当”的成为了输入系统的中转站。

Surface管理

窗口并不具备有绘制的功能,因此每个窗口都需要有一块Surface来供自己绘制。为每个窗口分配Surface是由WMS来完成的。

二、WMS的核心成员介绍

frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java

final WindowManagerPolicy mPolicy;
final IActivityManager mActivityManager;
final ActivityManagerInternal mAmInternal;
final AppOpsManager mAppOps;
final DisplaySettings mDisplaySettings;
...
final ArraySet<Session> mSessions = new ArraySet<>();
final WindowHashMap mWindowMap = new WindowHashMap();
final ArrayList<AppWindowToken> mFinishedStarting = new ArrayList<>();
final ArrayList<AppWindowToken> mFinishedEarlyAnim = new ArrayList<>();
final ArrayList<AppWindowToken> mWindowReplacementTimeouts = new ArrayList<>();
final ArrayList<WindowState> mResizingWindows = new ArrayList<>();
final ArrayList<WindowState> mPendingRemove = new ArrayList<>();
WindowState[] mPendingRemoveTmp = new WindowState[20];
final ArrayList<WindowState> mDestroySurface = new ArrayList<>();
final ArrayList<WindowState> mDestroyPreservedSurface = new ArrayList<>();
final H mH = new H();
final WindowAnimator mAnimator;
 final InputManagerService mInputManager
  • mPolicy:WindowManagerPolicy

WindowManagerPolicy(WMP)类型的变量。WindowManagerPolicy是窗口管理策略的接口类,用来定义一个窗口策略所要遵循的通用规范,并提供了WindowManager所有的特定的UI行为。它的具体实现类为PhoneWindowManager,这个实现类在WMS创建时被创建。WMP允许定制窗口层级和特殊窗口类型以及关键的调度和布局。

  • mSessions:ArraySet<Session>

ArraySet类型的变量,元素类型为Session.它主要用于进程间通信,其他的应用程序进程想要和WMS进程进行通信就需要经过Session,并且每个应用程序进程都会对应一个Session,WMS保存这些Session用来记录所有向WMS提出窗口管理服务的客户端。

  • mWindowMap:WindowHashMap

WindowHashMap类型的变量,WindowHashMap继承了HashMap,它限制了HashMap的key值的类型为IBinder,value值的类型为WindowState。WindowState用于保存窗口的信息,在WMS中它用来描述一个窗口。综上得出结论,mWindowMap就是用来保存WMS中各种窗口的集合。

  • mFinishedStarting:ArrayList

ArrayList类型的变量,元素类型为AppWindowToken,它是WindowToken的子类。
WindowToken主要有两个作用:

  • 可以理解为窗口令牌,当应用程序想要向WMS申请新创建一个窗口,则需要向WMS出示有效的WindowToken。AppWindowToken作为WindowToken的子类,主要用来描述应用程序的WindowToken结构,应用程序中每个Activity都对应一个AppWindowToken(AMS的ActivityRecord)。
  • WindowToken会将相同组件(Acitivity)的窗口(WindowState)集合在一起,方便管理。
    mFinishedStarting就是用于存储已经完成启动的应用程序窗口(Acitivity)的AppWindowToken的列表。
  • mFinishedEarlyAnim和mWindowReplacementTimeouts,其中mFinishedEarlyAnim存储了已经完成窗口绘制并且不需要展示任何已保存surface的应用程序窗口的AppWindowToken。
  • mWindowReplacementTimeout存储了等待更换的应用程序窗口的AppWindowToken,如果更换不及时,旧窗口就需要被处理。

mResizingWindows:ArrayList

  • ArrayList类型的变量,元素类型为WindowState。
  • mResizingWindows是用来存储正在调整大小的窗口的列表。
  • mPendingRemove是在内存耗尽时设置的,里面存有需要强制删除的窗口。
  • mDestroySurface里面存有需要被Destroy的Surface。mDestroyPreservedSurface里面存有窗口需要保存的等待销毁的Surface。

mInputManager:InputManagerService

InputManagerService类型的变量,输入系统的管理者。InputManagerService(IMS)会对触摸事件进行处理,它会寻找一个最合适的窗口来处理触摸反馈信息,WMS是窗口的管理者,因此,WMS“理所应当”的成为了输入系统的中转站。

三、WMS的作用

image.png

frameworks/base/services/core/java/com/android/server/wm/WindowManagerService .java

private WindowManagerService(Context context, InputManagerService inputManager,
         boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore, WindowManagerPolicy policy) {
    ...
    mInputManager = inputManager;
    ...
     mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
     //通过DisplayManager的getDisplays方法得到Display数组,每个显示设备都有一个Display实例
     mDisplays = mDisplayManager.getDisplays();
     for (Display display : mDisplays) {
         createDisplayContentLocked(display);
     }
    ...
      mActivityManager = ActivityManager.getService();
    ...
     //创建了WindowAnimator,它用于管理所有的窗口动画
     mAnimator = new WindowAnimator(this);
     mAllowTheaterModeWakeFromLayout = context.getResources().getBoolean(
             com.android.internal.R.bool.config_allowTheaterModeWakeFromWindowLayout);
     LocalServices.addService(WindowManagerInternal.class, new LocalService());
     initPolicy();//6
     // Add ourself to the Watchdog monitors.
     Watchdog.getInstance().addMonitor(this);
  ...
 }

2、Window的添加过程

frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java

1、对所要添加的窗口进行检查,如果窗口不满足一些条件,就不会再执行下面的代码逻辑。
2、WindowToken相关的处理,比如有的窗口类型需要提供WindowToken,没有提供的话就不会执行下面的代码逻辑,有的窗口类型则需要由WMS隐式创建WindowToken。
3、WindowState的创建和相关处理,将WindowToken和WindowState相关联。
4、创建和配置DisplayContent,完成窗口添加到系统前的准备工作。

 public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            InputChannel outInputChannel) {

        int[] appOp = new int[1];
        //检查相关的权限
        int res = mPolicy.checkAddPermission(attrs, appOp);
        if (res != WindowManagerGlobal.ADD_OKAY) {
            return res;
        }
        ...
        synchronized(mWindowMap) {
            if (!mDisplayReady) {
                throw new IllegalStateException("Display has not been initialialized");
            }
            final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);//2
            if (displayContent == null) {
                Slog.w(TAG_WM, "Attempted to add window to a display that does not exist: "
                        + displayId + ".  Aborting.");
                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
            }
            ...
            //type代表一个窗口的类型,它的数值介于FIRST_SUB_WINDOW和LAST_SUB_WINDOW之间(1000~1999),
            //这个数值定义在WindowManager中,说明这个窗口是一个子窗口
            if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
                //attrs.token是IBinder类型的对象,windowForClientLocked方法内部会根据attrs.token作为key值
                //从mWindowMap中得到该子窗口的父窗口
                parentWindow = windowForClientLocked(null, attrs.token, false);
                if (parentWindow == null) {
                    Slog.w(TAG_WM, "Attempted to add window with token that is not a window: "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                }
                if (parentWindow.mAttrs.type >= FIRST_SUB_WINDOW
                        && parentWindow.mAttrs.type <= LAST_SUB_WINDOW) {
                    Slog.w(TAG_WM, "Attempted to add window with token that is a sub-window: "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                }
            }
      }
         AppWindowToken atoken = null;
         final boolean hasParent = parentWindow != null;
         //得到WindowToken保存窗口相关信息
         WindowToken token = displayContent.getWindowToken(
                 hasParent ? parentWindow.mAttrs.token : attrs.token);
         final int rootType = hasParent ? parentWindow.mAttrs.type : type;
         boolean addToastWindowRequiresToken = false;

         if (token == null) {
             if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
                 Slog.w(TAG_WM, "Attempted to add application window with unknown token "
                       + attrs.token + ".  Aborting.");
                 return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
             }
             if (rootType == TYPE_INPUT_METHOD) {
                 Slog.w(TAG_WM, "Attempted to add input method window with unknown token "
                       + attrs.token + ".  Aborting.");
                 return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
             }
             if (rootType == TYPE_VOICE_INTERACTION) {
                 Slog.w(TAG_WM, "Attempted to add voice interaction window with unknown token "
                       + attrs.token + ".  Aborting.");
                 return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
             }
             if (rootType == TYPE_WALLPAPER) {
                 Slog.w(TAG_WM, "Attempted to add wallpaper window with unknown token "
                       + attrs.token + ".  Aborting.");
                 return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
             }
             ...
             if (type == TYPE_TOAST) {
                 // Apps targeting SDK above N MR1 cannot arbitrary add toast windows.
                 if (doesAddToastWindowRequireToken(attrs.packageName, callingUid,
                         parentWindow)) {
                     Slog.w(TAG_WM, "Attempted to add a toast window with unknown token "
                             + attrs.token + ".  Aborting.");
                     return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                 }
             }
             final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
             //这说明当我们添加窗口时是可以不向WMS提供WindowToken的,前提是rootType和type的值不为前面条件判断筛选的值。
             //WindowToken隐式和显式的创建肯定是要加以区分的,注释3处的第4个参数为false就代表这个WindowToken是隐式创建的
             token = new WindowToken(this, binder, type, false, displayContent,
                     session.mCanAddInternalSystemWindow);
             //处判断如果窗口为应用程序窗口
         } else if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
             //将WindowToken转换为专门针对应用程序窗口的AppWindowToken,AppWindowToken对应ActivityRecord
             atoken = token.asAppWindowToken();
             if (atoken == null) {
                 Slog.w(TAG_WM, "Attempted to add window with non-application token "
                       + token + ".  Aborting.");
                 return WindowManagerGlobal.ADD_NOT_APP_TOKEN;
             } else if (atoken.removed) {
                 Slog.w(TAG_WM, "Attempted to add window with exiting application token "
                       + token + ".  Aborting.");
                 return WindowManagerGlobal.ADD_APP_EXITING;
             }
         } else if (rootType == TYPE_INPUT_METHOD) {
             if (token.windowType != TYPE_INPUT_METHOD) {
                 Slog.w(TAG_WM, "Attempted to add input method window with bad token "
                         + attrs.token + ".  Aborting.");
                   return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
             }
         }
        .....
         //创建了WindowState,它存有窗口的所有的状态信息,在WMS中它代表一个窗口
         final WindowState win = new WindowState(this, session, client, token, parentWindow,
                  appOp[0], seq, attrs, viewVisibility, session.mUid,
                  session.mCanAddInternalSystemWindow);
          //窗口的客户端是否已经死亡
          if (win.mDeathRecipient == null) {
              // Client has apparently died, so there is no reason to
              // continue.
              Slog.w(TAG_WM, "Adding window client " + client.asBinder()
                      + " that is dead, aborting.");
              return WindowManagerGlobal.ADD_APP_EXITING;
          }
          //窗口的DisplayContent是否为null,没有展示内容
          if (win.getDisplayContent() == null) {
              Slog.w(TAG_WM, "Adding window to Display that has been removed.");
              return WindowManagerGlobal.ADD_INVALID_DISPLAY;
          }
          //调用了WindowManagerPolicy的adjustWindowParamsLw方法,该方法的实现在PhoneWindowManager中,
          //会根据窗口的type对窗口的LayoutParams的一些成员变量进行修改
          mPolicy.adjustWindowParamsLw(win.mAttrs);
          win.setShowToOwnerOnlyLocked(mPolicy.checkShowToOwnerOnly(attrs));
          res = mPolicy.prepareAddWindowLw(win, attrs);
          .....
          win.attach();
          mWindowMap.put(client.asBinder(), win);
          if (win.mAppOp != AppOpsManager.OP_NONE) {
              int startOpResult = mAppOps.startOpNoThrow(win.mAppOp, win.getOwningUid(),
                      win.getOwningPackage());
              if ((startOpResult != AppOpsManager.MODE_ALLOWED) &&
                      (startOpResult != AppOpsManager.MODE_DEFAULT)) {
                  win.setAppOpVisibilityLw(false);
              }
          }

          final AppWindowToken aToken = token.asAppWindowToken();
          if (type == TYPE_APPLICATION_STARTING && aToken != null) {
              aToken.startingWindow = win;
              if (DEBUG_STARTING_WINDOW) Slog.v (TAG_WM, "addWindow: " + aToken
                      + " startingWindow=" + win);
          }

          boolean imMayMove = true;
          win.mToken.addWindow(win);
           if (type == TYPE_INPUT_METHOD) {
              win.mGivenInsetsPending = true;
              setInputMethodWindowLocked(win);
              imMayMove = false;
          } else if (type == TYPE_INPUT_METHOD_DIALOG) {
              displayContent.computeImeTarget(true /* updateImeTarget */);
              imMayMove = false;
          } else {
              if (type == TYPE_WALLPAPER) {
                  displayContent.mWallpaperController.clearLastWallpaperTimeoutTime();
                  displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
              } else if ((attrs.flags&FLAG_SHOW_WALLPAPER) != 0) {
                  displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
              } else if (displayContent.mWallpaperController.isBelowWallpaperTarget(win)) {
                  displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
              }
          }
}

3、Window的删除过程

frameworks/base/core/java/android/view/WindowManagerGlobal.java

public void removeView(View view, boolean immediate) {
      if (view == null) {
          throw new IllegalArgumentException("view must not be null");
      }
      synchronized (mLock) {
          //找到view的索引
          int index = findViewLocked(view, true);
          View curView = mRoots.get(index).getView();
          //删除当前的view
          removeViewLocked(index, immediate);
          if (curView == view) {
              return;
          }
          throw new IllegalStateException("Calling with view " + view
                  + " but the ViewAncestor is attached to " + curView);
      }
  }

      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);//4
       if (view != null) {
           view.assignParent(null);
           if (deferred) {
               mDyingViews.add(view);
           }
       }
   }

四、WMS主要类介绍:

1、WindowManagerService类

WindowManagerService服务就可以通过它在内部所创建的WindowState对象的成员变量mClient来 要求运行在应用程序进程这一侧的Activity组件来配合管理窗口的状态,例如:

  • 当一个Activity组件的窗口的大小发生改变后,WindowManagerService服务就会调用这个
    IWindow接口的成员函数resized来通知该Activity组件,它的大小发生改变了。
  • 当一个Activity组件的窗口的可见性之后,WindowManagerService服务就会调用这个Iwindow
    接口的成员函数dispatchAppVisibility来通知该Activity组件,它的可见性发生改变了。
  • 当一个Activity组件的窗口获得或者失去焦点之后,WindowManagerService服务就会调用这个
    IWindow接口的成员函数windowFoucusChanged来通知该Activity组件,它的焦点发生改变了。

2、Window类

  • 定义Callback接口,它包含一系列dispatchXxxx方法和一系列onXxxx方法,用于处理UI事件。
  • 定义了一些接口,如setContentView、findViewById()等。由PhoneWindow来实现。

3、WindowManager类

  • WindowManager继承自ViewManager这个接口,这个接口主要有以下的实现子接口:
    addView()、updateViewLayout()、removeView();
    WindowManager可以添加view到屏幕,也可以从屏幕删除view。它面向的对象一端是屏幕,另一端就是View,通过WindowManager的 addView方法创建View,这样产生出来的View根据WindowManager.LayoutParams属性不同,效果也就不同了,比如创建系统顶级窗口,实现悬浮窗口效果。

4、ViewRootImpl类

ViewRootImpl这个类在android的UI结构中扮演的是一个中间者的角色,连接的是PhoneWindow 和WindowManagerService,也就是窗口管理系统与窗口呈现系统之间的桥梁。
它的主要作用有两个:
1.向DecorView分发收到的用户发起的event事件,如按键,触屏等事件;
2.与WindowManagerService交互,完成整个Activity的GUI的绘制。
3.里面两个重要的变量 mWindowSessoin和mWindow。

  • mWindowSessoin是ViewRootImpl和WindowManagerService之间的一个会话层,它的实体是在WMS中定义,作为ViewRootImpl 向WMS发送请求的的桥梁。
  • mWindow是ViewRootImpl提供给WMS,以便WMS反向通知ViewRootImpl的接口。由于ViewRootImpl处在application端,而WMS处在system_server端,它们处在不同的进程,因此需要添加这个W接口,便于WMS向ViewRootImpl传递信息。

5、WindowState类

  • WMS中最基本的元素,描述WMS中的一个窗口。它既可以是由App添加过来的View,也可以是系统创建的系统窗口。mAttrs为WindowManager.LayoutParams类型,描述布局参数。mClient为IWindow类型,也就是App端的ViewRootImpl::W。为了查找方便,WMS中的mWindowMap保存了IWindow到WindowState的映射,mTokenMap保存了IApplicationToken到WindowToken的映射。

6、Session类

  • 向App提供IWindowSession接口让其可以和WMS通信,每个App在WMS有一个Session对象,App就是通过这个Session来向WMS发出窗口管理申请的,命令dumpsys window sessions可以查看系统中的Session。

7、WindowToken类

  • 描述WM中一组相关的窗口,这些Window对应的WindowState放在其成员变量windows里。其主要继承类AppWindowToken,它是针对App的WindowToken结构。WindowState中的mAppToken指向所属的AppWindowToken,如果是系统窗口,mAppToken为空,mToken指向WindowToken对象。

8、AppWindowToken

每个App的Activity对应一个AppWindowToken。其中的appToken为IApplicationToken类型,连接着对应的AMS中的ActivityRecord::Token对象,有了它就可以顺着AppWindowToken找到AMS中相应的ActivityRecord。其中allAppWindows是一个无序的列表,包含该Activity中所有的窗口。用dumpsys window display可以查看z-ordered的AppWindowToken列表。

8、TaskStack类

  • AppWindowToken保存了属于它的WindowState的有序列表,而它本身也作为一个列表被管理在TaskStack中的mTasks成员中。
  • TaskStack中有个重要的变量mBounds,在相同Task里的AppWindowToken对应的Activity的大小是相同的,所以mBounds代表的就是Activity对应的大小。

9、DisplayContent类

表示一个显示设备上的内容,这个显示设备可以是外接显示屏,也可以是虚拟显示屏。其中 mWindows是一个WindowState的有序(Z-ordered,底部最先)列表。mStackBoxes包含了若干个StackBox,其中 一个为HomeStack,另一个是App的StackBox。所有的StackBox被组织成二叉树,StackBox是其中的节点,其中有三个重要成 员变量,mFirst和mSecond指向左和右子结点(也是StackBox),StackBox的成员mStack才是我们真正关心的东西 -TaskStack。可以看到,为了要把TaskStack存成树的结构,需要一个容器,这个容器就是StackBox。

五、AMS和WMS交互图

1、AMS主要类关系图

image.png

2、WMS主要类关系图

image.png

3、AMS、WMS数据结构关系

image.png

参考链接:http://liuwangshu.cn/tags/Android%E6%A1%86%E6%9E%B6%E5%B1%82/
出版的“Android进阶解密”这本书值得看

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容