Core-Animation-Advanced-Techniques阅读笔记

很棒的翻译!
Git传送门:https://github.com/AttackOnDobby/iOS-Core-Animation-Advanced-Techniques

图层树

摘要:这一章阐述了图层的树状结构,说明了如何在iOS中由UIView的层级关系形成的一种平行的CALayer层级关系,在后面的实验中,我们创建了自己的CALayer,并把它添加到图层树中。
Core Animation是一个复合引擎,它的职责就是尽可能快地组合屏幕上不同的可视内容,这个内容是被分解成独立的图层,存储在一个叫做图层树的体系之中。
UIView<--->CALayer
视图----------图层
视图层级-----图层树
功能不同点:和UIView最大的不同是CALayer不处理用户的交互。
但是为什么iOS要基于UIView和CALayer提供两个平行的层级关系呢?为什么不用一个简单的层级来处理所有事情呢?原因在于要做职责分离,这样也能避免很多重复代码。

寄宿图

摘要:本章介绍了寄宿图和一些相关的属性。你学到了如何显示和放置图片, 使用拼合技术来显示, 以及用CALayerDelegate和Core Graphics来绘制图层内容。

CALayer相关属性

  • contents:id,用来给图层设置图片(__birdge 转换)。
  • contentGravity:对应于UIView的contentMode。
  • contentScale:定义了寄宿图的像素尺寸和视图大小的比例。contentsScale属性其实属于支持高分辨率(又称Hi-DPI或Retina)屏幕机制的一部分。
  • maskToBounds:对应于UIView的clipsToBounds属性,决定是否用来显示超过边界的内容。
  • contentsRect:允许我们在图层边框里显示寄宿图的一个子域。和bounds,frame不同,contentsRect不是按点来计算的,它使用了单位坐标,单位坐标指定在0到1之间,是一个相对值(像素和点就是绝对值)。所以它们是相对与寄宿图的尺寸的。
    PS:iOS使用的坐标系统
  1. 点(逻辑像素)
  2. 像素(物理像素)
  3. 单位(在openGL中会比较常用)
  • contentsCenter:对应于UIView的resizableImageWithCapInsets的效果,但是可以运用在任何寄宿图,甚至包括核心绘图绘制的图形。

  • CustomDrawing:我们也可以直接用Core Graphics直接绘制寄宿图。能够通过继承UIView并实现-drawRect:方法来自定义绘制。
    -drawRect: 方法没有默认的实现,因为对UIView来说,寄宿图并不是必须的,它不在意那到底是单调的颜色还是有一个图片的实例。如果UIView检测到-drawRect: 方法被调用了,它就会为视图分配一个寄宿图,这个寄宿图的像素尺寸等于视图大小乘以 contentsScale的值。

图层几何学

摘要:本章涉及了CALayer的集合结构,包括它的frame,position和bounds,介绍了三维空间内图层的概念,以及如何在独立的图层内响应事件,最后简单说明了在iOS平台,Core Animation对自动调整和自动布局支持的缺乏。

  • 布局
  1. UIView有三个比较重要的布局属性:frame,bounds和center,CALayer对应地叫做frame,bounds和position。
  2. frame代表了图层的外部坐标(也就是在父图层上占据的空间),bounds是内部坐标({0, 0}通常是图层的左上角),center和position都代表了相对于父图层anchorPoint所在的位置。
  3. 视图的frame,bounds和center属性仅仅是存取方法,当操纵视图的frame,实际上是在改变位于视图下方CALayer的frame,不能够独立于图层之外改变视图的frame。
  4. 对于视图或者图层来说,frame并不是一个非常清晰的属性,它其实是一个虚拟属性,是根据bounds,position和transform计算而来,所以当其中任何一个值发生改变,frame都会变化。相反,改变frame的值同样会影响到他们当中的值。
  • 锚点
  • 坐标系
  1. 和视图一样,图层在图层树当中也是相对于父图层按层级关系放置,一个图层的position依赖于它父图层的bounds,如果父图层发生了移动,它的所有子图层也会跟着移动。
  2. CALayer提供一组Api可以方便的转换图层之间的点或矩形。
  3. 翻转的几何结构:常规说来,在iOS上,一个图层的position位于父图层的左上角,但是在Mac OS上,通常是位于左下角。Core Animation可以通过geometryFlipped属性来适配这两种情况,它决定了一个图层的坐标是否相对于父图层垂直翻转。
  4. Z轴:CALayer存在于一个三维空间当中,CALayer还有另外两个属性,zPosition和anchorPointZ。
  5. HitTest:CALayer并不关心任何响应链事件,所以不能直接处理触摸事件或者手势。但是它有一系列的方法帮你处理事件:-containsPoint:和-hitTest:。
  • 自动布局

视觉效果

摘要:这一章介绍了一些可以通过代码应用到图层上的视觉效果,比如圆角,阴影和蒙板。我们也了解了拉伸过滤器和组透明。

  • 圆角
  • 边框
  • 阴影
  • 阴影裁剪
  • 阴影形状ShadowPath
  • 图层蒙版
  • 拉伸过滤:三种,双线性、三线性过滤两者较为相似,还有一种邻近像素过滤。
  • 组透明:CALayer. shouldRasterize栅格化属性可以将整个图层及其子图层整合成一个整体的图片。

变换

摘要:这一章涉及了一些2D和3D的变换。你学习了一些矩阵计算的基础,以及如何用Core Animation创建3D场景。你看到了图层背后到底是如何呈现的,并且知道了不能把扁平的图片做成真实的立体效果,最后我们用demo说明了触摸事件的处理,视图中图层添加的层级顺序会比屏幕上显示的顺序更有意义。

  • 仿射变换:CGAffineTransform中的“仿射”的意思是无论变换矩阵用什么值,图层中平行的两条线在变换之后任然保持平行。
  • 混合变换:这意味着变换的顺序会影响最终的结果,也就是说旋转之后的平移和平移之后的旋转结果可能不同。
  • 剪切变换:未提供相应函数,需要自己设置transform值来配置。
  • 3D变换:CATransform3D。
  • 透视投影:m34参数。
  • 灭点:灭点的概念,SubLayerTransform属性。
  • 背面:图层是双面绘制的,反面显示的是正面的一个镜像图片,CALayer有一个叫做doubleSided的属性来控制图层的背面是否要被绘制。
  • 扁平化图层:如果内部图层相对外部图层做了相反的变换,那么按照逻辑这两个变换将被相互抵消。尽管Core Animation图层存在于3D空间之内,但它们并不都存在同一个3D空间。每个图层的3D场景其实是扁平化的,当你从正面观察一个图层,看到的实际上由子图层创建的想象出来的3D场景,但当你倾斜这个图层,你会发现实际上这个3D场景仅仅是被绘制在图层的表面。
  • 固体对象:尝试做一个🎲
  • 点击事件的响应问题

专用图层

摘要:本章将会介绍其他的一些图层类,进一步扩展使用Core Animation绘图的能力。

  • CAShapeLayer:渲染快速、高效利用内存、不会被图层边界剪裁掉、不会出现像素化。
  • CATextLayer:iOS6及之前的版本UILabel是通过WebKit绘制的,性能差,而CATextLayer使用了CoreText并且渲染快速。
  • CATransformLayer:CATransformLayer并不平面化它的子图层,所以它能够用于构造一个层级的3D结构。
  • CAGradientLayer:颜色平滑渐变。
  • CAReplicatorLayer:CAReplicatorLayer的目的是为了高效生成许多相似的图层。它会绘制一个或多个图层的子图层,并在每个复制体上应用不同的变换。一个比较实用的地方就是镜面反射效果。
  • CAScrollLayer
  • CATiledLayer:CATiledLayer将大图分解成小片然后将他们单独按需载入。
  • CAEmitterLayer:CAEmitterLayer是一个高性能的粒子引擎,被用来创建实时例子动画如:烟雾,火,雨等等这些效果。
  • CAEAGLLayer:OpenGL支持。
  • CAEAGLLayer

隐式动画

摘要:这一章讨论了隐式动画,还有Core Animation对指定属性选择合适的动画行为的机制。同时你知道了UIKit是如何充分利用Core Animation的隐式动画机制来强化它的显示系统,以及动画是如何被默认禁用并且当需要的时候启用的。最后,你了解了呈现和模型图层,以及Core Animation是如何通过它们来判断出图层当前位置以及将要到达的位置。

  • 事务:CATransaction实际上是Core Animation用来包含一系列属性动画集合的机制,任何用指定事务去改变可以做动画的图层属性都不会立刻发生变化,而是当事务一旦提交的时候开始用一个动画过渡到新值。
  • 图层行为:每个UIView对它关联的图层都扮演了一个委托,并且提供了-actionForLayer:forKey的实现方法。当不在一个动画块的实现中,UIView对所有图层行为返回nil,但是在动画block范围之内,它就返回了一个非空值。
  • 呈现图层与模型图层:呈现图层(PresentationLayer)、呈现树。大多数情况下,你不需要直接访问呈现图层,你可以通过和模型图层的交互,来让Core Animation更新显示。两种情况下呈现图层会变得很有用,一个是同步动画,一个是处理用户交互。

显式动画

摘要:这一章中,我们涉及了属性动画(你可以对单独的图层属性动画有更加具体的控制),动画组(把多个属性动画组合成一个独立单元)以及过度(影响整个图层,可以用来对图层的任何内容做任何类型的动画,包括子图层的添加和移除)。

  • 属性动画:属性动画作用于图层的某个单一属性,并指定了它的一个目标值,或者一连串将要做动画的值。属性动画分为两种:基础和关键帧。
  1. 基础动画:fromValue、toValue、byValue(id类型),CAAnimationDelegate
  2. 关键帧动画: rotationMode参数,虚拟属性(transform)。
  3. 动画组
  4. 过渡:属性动画只对图层的可动画属性起作用,所以如果要改变一个不能动画的属性(比如图片),或者从层级关系中添加或者移除图层,属性动画将不起作用。过渡动画首先展示之前的图层外观,然后通过一个交换过渡到新的外观。CATransition的type及subtype属性。隐式过渡、图层树动画。自定义动画。
  • 在动画过程中取消动画

图层时间

摘要:在这一章,我们了解了CAMediaTiming协议,以及Core Animation用来操作时间控制动画的机制。在下一章,我们将要接触缓冲,另一个用来使动画更加真实的操作时间的技术。

  • CAMediaTiming协议:CAMediaTiming协议定义了在一段动画内用来控制逝去时间的属性的集合,CALayer和CAAnimation都实现了这个协议,所以时间可以被任意基于一个图层或者一段动画的类控制。
  1. 持续和重复
  2. 相对时间:beginTime、speed、timeOffSet
  3. fillMode
  • 层级关系时间:每个动画和图层在时间上都有它自己的层级概念,相对于它的父亲来测量。对图层调整时间将会影响到它本身和子图层的动画,但不会影响到父图层。
  1. 全局时间和本地时间:CALayer像hitTest一样提供了方法来转换不同图层间的本地时间。
  2. 暂停、倒回、快进:layer.speed。
  3. 手动动画

缓冲

摘要: 在这一章中,我们了解了有关缓冲和CAMediaTimingFunction类,它可以允许我们创建自定义的缓冲函数来完善我们的动画,同样了解了如何用CAKeyframeAnimation来避开CAMediaTimingFunction的限制,创建完全自定义的缓冲函数。

  • CAMediaTimingFunction
  • 缓冲和关键帧动画
  • 自定义缓冲函数
  1. 三次贝塞尔曲线:CAMediaTimingFunction函数的主要原则在于它把输入的时间转换成起点和终点之间成比例的改变。起点、终点、两个控制点。
  2. 更加复杂的动画曲线:关键帧+ CAMediaTimingFunction分段,关键属性values、timingFunctions、keyTimes。
  3. 流程自动化:上个方法虽然可行,但是笨重而难以修改。动画流程的自动化需要用一个数学函数表示弹性动画。技术点:keyValue插值。

基于定时器的动画

摘要:在这一章中,我们了解了如何通过一个计时器创建一帧帧的实时动画,包括缓冲,物理模拟等等一系列动画技术,以及用户输入(通过加速计)。

  • 定时帧:用Timer实现CA效果。NSTimer / CADisplayLink。解释了不同RunloopMode之间的差异。
  • 物理模拟:第三方Chipmunk。

性能调优

摘要:在这章中,我们学习了Core Animation是如何渲染,以及我们可能出现的瓶颈所在。你同样学习了如何使用Instruments来检测和修复性能问题。

代码应该运行的尽量快,而不是更快。也就是说业务代码不需要无止境的优化,当性能满足要求即可。

CPU/GPU

以下是CA是如何在这两个处理器之间分配工作的。

  • 当运行一段动画的时候,具体分为四个阶段:
  1. 布局:准备视图/图层的层级关系,以及设置图层属性(位置、背景色、边框)的阶段。
  2. 显示:这是图层的寄宿图被绘制的阶段。
  3. 准备:CA准备发送动画数据到渲染服务的阶段,同时也是CA将要执行一些别的事务例如解码动画过程中将要显示图片的时间点。
  4. 提交:CA打包所有图层和动画属性,然后通过IPC(内部处理通信)发送到渲染服务进行显示。
    但是这些仅仅阶段仅仅发生在你的应用程序之内,在动画在屏幕上显示之前仍然有更多的工作。一旦打包的图层和动画到达渲染服务进程,他们会被反序列化来形成另一个叫做渲染树的图层树(在第一章“图层树”中提到过)。使用这个树状结构,渲染服务对动画的每一帧做出如下工作:
  5. 对所有的图层属性计算中间值,设置OpenGL几何形状(纹理化的三角形)来执行渲染。
  6. 在屏幕上渲染可见的三角形。
      所以一共有六个阶段;最后两个阶段在动画过程中不停地重复。前五个阶段都在软件层面处理(通过CPU),只有最后一个被GPU执行。而且,你真正只能控制前两个阶段:布局和显示。Core Animation框架在内部处理剩下的事务,你也控制不了它。

GPU相关的操作

宽泛的说,大多数CALayer的属性都是用GPU来绘制,但是有一些事情会降低(基于GPU)图层绘制,比如:

  • 太多的几何结构:过多图层、栅格化
  • 重绘:重叠的半透明图层、GPU填充比率
  • 离屏绘制:对于特定图层效果的使用,比如圆角,图层遮罩,阴影或者是图层光栅化都会强制Core Animation提前渲染图层的离屏绘制。
  • 过大的图片:如果视图绘制超出GPU支持的2048x2048或者4096x4096尺寸的纹理,就必须要用CPU在图层每次显示之前对图片预处理,同样也会降低性能。

CPU相关的操作

大多数工作在Core Animation的CPU都发生在动画开始之前。这意味着它不会影响到帧率,所以很好,但是他会延迟动画开始的时间,让你的界面看起来会比较迟钝,以下CPU的操作都会延迟动画的开始时间:

  • 布局计算:自动布局。
  • 视图惰性加载:iOS只会当视图控制器的视图显示到屏幕上时才会加载它。
  • Core Graphics绘制:-drawRect:方法或者CALayerDelegate的-drawLayer:inContext:方法。
  • 解压图片:JPG/PNG需要解压才能显示,过大的图片的解压需要过长的时间。
      当图层被成功打包,发送到渲染服务器之后,CPU仍然要做如下工作:为了显示屏幕上的图层,Core Animation必须对渲染树种的每个可见图层通过OpenGL循环转换成纹理三角板。由于GPU并不知晓Core Animation图层的任何结构,所以必须要由CPU做这些事情。这里CPU涉及的工作和图层个数成正比,所以如果在你的层级关系中有太多的图层,就会导致CPU每一帧的渲染,即使这些事情不是你的应用程序可控的。

IO相关工作

上下文中的IO(输入/输出)指的是例如闪存或者网络接口的硬件访问。

如何测试

确保在真实环境下测试你的程序

  1. 模拟器Mac上的GPU和iOS设备的完全不一样,模拟器不得已要在软件层面(CPU)模拟设备的GPU,这意味着GPU相关的操作在模拟器上运行的更慢,尤其是使用CAEAGLLayer来写一些OpenGL的代码时候。这就是说在模拟器上的测试出的性能会高度失真。
  2. 性能测试一定要用发布配置,而不是调试模式。因为当用发布环境打包的时候,编译器会引入一系列提高性能的优化,例如去掉调试符号或者移除并重新组织代码。
  3. 在你支持的设备中性能最差的设备上测试:如果基于iOS6开发,这意味着最好在iPhone 3GS或者iPad2上测试。如果可能的话,测试不同的设备和iOS版本,因为苹果在不同的iOS版本和设备中做了一些改变,这也可能影响到一些性能。例如iPad3明显要在动画渲染上比iPad2慢很多,因为渲染4倍多的像素点(为了支持视网膜显示)。

Instruments

将讨论如下几个工具:

  • 时间分析器:用来测量被方法/函数打断的CPU使用情况。
  • Core Animation:用来调试各种Core Animation性能问题。
  • OpenGL ES驱动 :用来调试GPU性能问题。这个工具在编写Open GL代码的时候很有用,但有时也用来处理Core Animation的工作。

高效绘图

摘要:本章我们主要围绕用Core Graphics软件绘制讨论了一些性能挑战,然后探索了一些改进方法:比如提高绘制性能或者减少需要绘制的数量。

软件绘图

代价高昂的绘图方式,绘制上下文需要的内存计算公式:WH4byte。

矢量图形

  • BezierPath:越画越慢,因为每次drawRect都会重新绘制之前的路径。
  • 专有图层

脏矩形

为了减少不必要的绘制,Mac OS和iOS设备将会把屏幕区分为需要重绘的区域和不需要重绘的区域。那些需要重绘的部分被称作『脏区域』。在实际应用中,鉴于非矩形区域边界裁剪和混合的复杂性,通常会区分出包含指定视图的矩形位置,而这个位置就是『脏矩形』。
调用-setNeedsDisplayInRect:来标记需要重绘的区域。

异步绘制

一些情况下,我们可以推测性地提前在另外一个线程上绘制内容,然后将由此绘出的图片直接设置为图层的内容。这实现起来可能不是很方便,但是在特定情况下是可行的。Core Animation提供了一些选择:CATiledLayer和drawsAsynchronously属性。

  • CATiledLayer:在多个线程中为每个小块同时调用-drawLayer:inContext:方法。这就避免了阻塞用户交互而且能够利用多核来更快地绘制。只有一个小块的CATiledLayer是实现异步更新图片视图的简单方法。
  • drawsAsynchronously:允许CGContext延缓绘制命令的执行以至于不阻塞用户交互,-drawLayer:inContext:方法只会在主线程调用,但是CGContext并不等待每个绘制命令的结束。相反地,它会将命令加入队列,当方法返回时,在后台线程逐个执行真正的绘制。根据苹果的说法。这个特性在需要频繁重绘的视图上效果最好(比如我们的绘图应用,或者诸如UITableViewCell之类的),对那些只绘制一次或很少重绘的图层内容来说没什么太大的帮助。

图像IO

摘要:在这章中,我们研究了和图片加载解压相关的性能问题,并延展了一系列解决方案。

加载和潜伏

在应用运行的时候周期性地加载和卸载图片。只要有可能,就应当设法在程序生命周期中不易察觉的时候加载图片。

  • 异步线程加载:GCD和NSOperationQueue
  • 立即解压:iOS通常会延迟解压图片的时间,[UIImage imageNamed:]可以让图片立即加载,或者将其设置成图层内容(只能在主线程执行,所以不提升性能),使用ImageIO框架强制图片立即解压。最后是使用UIKit的Context的绘制让图片强制解压。(这个又分为只绘制一个像素或者将整张图片绘制在Context中,丢弃原始的图片使用绘制出来的图片带替)。
  • CATiledLayer
    -分辨率交换

缓存

  • imageNamed方法
  • 自定义缓存
  • NSCache:NSCache和NSDictionary类似。你可以通过-setObject:forKey:和-object:forKey:方法分别来插入,检索。和字典不同的是,NSCache在系统低内存的时候自动丢弃存储的对象。可以使用-setCountLimit:方法设置缓存大小,以及-setObject:forKey:cost:来对每个存储的对象指定消耗的值来提供一些暗示。

文件格式——PNG/JPEG?

  • 混合图片
  • JPEG2000:重压缩。
  • PVRTC:重加载速度。

图层性能

摘要:本章学习了使用Core Animation图层可能遇到的性能瓶颈,并讨论了如何避免或减小压力。你学习了如何管理包含上千虚拟图层的场景。同时也学习了一些有用的技巧,选择性地选取光栅化或者绘制图层内容在合适的时候重新分配给CPU和GPU。

隐式绘制

通过以下三种方式创建隐式的寄宿图:1.使用特定的图层属性;2.特定的视图;3.特定的图层子类。

  • 文本:尽可能地避免改变那些包含文本的视图的frame,因为这样做的话文本就需要重绘。例如,如果你想在图层的角落里显示一段静态的文本,但是这个图层经常改动,你就应该把文本放在一个子图层中。
  • 光栅化:避免使用在内容经常变化的图层上。
  • 离屏渲染:圆角+maskToBounds、图层蒙版、阴影将触发离屏渲染。对于那些需要动画而且要在屏幕外渲染的图层来说,你可以用CAShapeLayer,contentsCenter或者shadowPath来获得同样的表现而且较少地影响到性能。
  • CAShapeLayer
  • 可伸缩图片ContentsCenter
  • shadowPath

混合和过度绘制

为了加速处理进程,尽量不要使用透明图层,且将视图的backgroundColor属性设置一个固定的不透明的颜色,设置opaque属性为YES。

减少图层数量

对象回收:UITableView、CollectionView等。

CoreGraphics绘制

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

推荐阅读更多精彩内容