Flutter基础知识

0、Dart是值传递还是引用传递?

Dart是值传递。每次调用函数,传递过去的都是对象的内存地址,不是对象的复制。
知识点扩展:Swift的struct是值类型,class是引用类型;意思是:声明一个新的变量指向这个结构体,改变某个属性,原本的结构体属性不会发生变化;而类会随着改变

1、Flutter的核心树

Widget:

1、使用配置和状态,描述view的样子
2、当一个Widget发生改变时,Widget会重新build它的描述

Element:

1、Element是widget的实例,在树中详细的位置。

RenderObject:

1、渲染树上的一个对象。
2、RenderObject是渲染库的核心。

2、flutter 中Widget的分类

1、组合类
statelesswidget、statefullwidget
2、代理类
inheritedwidget:用于状态共享,Theme 、Localizations 、 MediaQuery 等,都是通过它进行状态共享,通过context获取共享的状态,ThemeData theme = Theme.of(context);
ParentDataWidget
3、绘制类RenderObjectWidget
RenderObject 的布局相关方法调用顺序是 : layout -> performResize -> performLayout -> markNeedsPaint

3、mixin extends implement 之间的关系?

继承extends 混入mixin 接口实现implement,三者可以同时存在,前后顺序是extends,mixin,implement。
Flutter的继承是单继承,子类重写超类的方法用override,子类调用超类的方法用super。
mixin是为了解决继承方面的问题而引入的机制,Dart为了支持多重继承,引入了mixin关键字。mixins的对象是类,mixins绝不是继承,也不是接口,而是一种全新的特性,可以mixins多个类,mixins的使用需要满足一定条件。
mixin的使用条件:
1、mixin类只能继承自object
2、mixin类不能有构造函数
3、一个类可以mixins多个mixins类,但是不破坏flutter的单继承
mixin就是为了解决Dart的多继承问题,但是mixin不能有构造方法,避免继承多个类产生构造方法冲突

4、Dart的语言特性

1、Dart一切都是对象,所有的对象继承自Object。
2、Dart和Swift一样都是强类型语言,可以用var或 dynamic来声明一个变量,Dart会自动推断其数据类型。
3、没有赋值初始值的变量都会默认null
4、Dart支持顶层方法,如main方法,可以在方法内部创建方法
5、Dart支持顶层变量,也支持类变量或对象变量
6、Dart没有public protected private等关键字,如果变量以下划线开头(_button),表示这个变量在库中是私有的。

5、Dart 中的级联操作符 (未使用疑问)

Dart 当中的 「..」意思是 「级联操作符」,为了方便配置而使用。「..」和「.」不同的是 调用「..」后返回的相当于是 this,而「.」返回的则是该方法返回的值 。

6、Dart 的单线程模型是如何运行的?

Dart在单线程中是以消息循环机制来运行的,包含两个任务队列,微任务队列(microtask queue)、事件队列(event queue)。
flutter启动后,消息循环就启动了。按照先进先出原则逐个执行微任务队列中的任务,执行完毕后执行事件队列中的任务,事件队列中的任务执行完毕之后,在执行微任务队列,如此循环往复。

7、await for 与 stream流 (待深入研究)

Stream<String> stream = new Stream<String>.fromIterable(['1', '2', '3', '4']);
main() async{
 print('start');
 await for(String s in stream){
   print(s);
 }
 print('end..');
}
结果
start
1
2
3
4
end..

await for是不断获取stream流中的数据,然后执行循环体中的操作。await for一般用在知道Stream什么时候完成,并且必须等待传递完成后才能使用,不然就会一直阻塞。

8、Stream 与 Future的关系?

1、Future表示稍后获得的一个数据,所有异步操作的返回值都用Future来表示。
2、Future只能表示一次异步获取的数据。
3、Stream表示多次异步获取的数据。例如按钮多次点击,这个点击事件就是Stream。
4、Future只返回一 次值,Stream返回多次值。
5、Dart中统一使用Stream流处理数据。

9、Stream 有哪两种订阅模式?分别是怎么调用的?

Stream单订阅多订阅两种。
Stream 默认处于单订阅模式,所以同一个 stream 上的 listen 和其它大多数方法只能调用一次,调用第二次就会报错。
Stream 可以通过 transform() 方法(返回另一个 Stream)进行连续调用。通过 Stream.asBroadcastStream() 可以将一个单订阅模式的 Stream 转换成一个多订阅模式的 Stream,isBroadcast 属性可以判断当前 Stream 所处的模式。

10、Flutter中的Widget、State、Context 的核心概念?是为了解决什么问题?

主要是为了解决多个部件之间的交互和部件自身状态的维护。
1、Widget: 在Flutter中,几乎所有东西都是Widget。将一个Widget想象为一个可视化的组件(或与应用可视化方面交互的组件),当你需要构建与布局直接或间接相关的任何内容时,你正在使用Widget。
2、Widget树: Widget以树结构进行组织。包含其他Widget的widget被称为父Widget(或widget容器)。包含在父widget中的widget被称为子Widget。
3、Context: 仅仅是已创建的所有Widget树结构中的某个Widget的位置引用。简而言之,将context作为widget树的一部分,其中context所对应的widget被添加到此树中。一个context只从属于一个widget,它和widget一样是链接在一起的,并且会形成一个context树。
4、State: 定义了StatefulWidget实例的行为,它包含了用于”交互/干预“Widget信息的行为和布局。应用于State的任何更改都会强制重建Widget。

11、Dart异步编程中的 Future关键字

Dart中,执行一个异步任务使用Future来处理。
在 Dart 的每一个 Isolate 当中,执行的优先级为 :Main > MicroTask > EventQueue。

12、什么是Isolate?

Dart只有一个主线程,其实在Dart中并不是叫 Thread ,而是有个专门名词叫 「isolate(隔离)。
用官方文档中定义一句话来概括: An isolated Dart execution context .大概的意思就是「isolate实际就是一个隔离的Dart执行的上下文环境(或者容器)」。「isolate是有自己的内存和单线程控制的事件循环」

其实在Dart也会遇到一些耗时计算的任务,不建议把任务放在主isolate中,否则容易造成UI卡顿,需要开辟一个单独isolate来独立执行耗时任务,然后通过消息机制把最终计算结果发送给主isolate实现UI的更新。」 在Dart中异步是并发方案的基础,Dart支持单个和多个isolate中的异步。。
「Dart没有共享内存的并发」,没有竞争的可能性所以不需要锁,也就不存在死锁的问题。
Flutter异步编程-Isolate

13、Flutter 中的生命周期

13.1 statefulWidget
1、initState→didChangeDependence→build→didupdateDependence→deactivate→dispose

2、initState:只调用一次,widget创建执行的第一个方法,这里可以做初始化工作,不如初始化state变量。
3、didChangeDependence:多次调用。
1、initState调用
2、依赖的InheritedWidget rebuild会被调用。
3、build:多次调用
1、initState调用
2、setState触发的时候调用
4、didupdateDependence:多次调用
组件状态改变的时候调用
5、deactivate:调用一次
当State对象从树中被移除,会调用此回调,会在dispose之前调用。
页面销毁的时候会依次执行:deactivate → dispose
6、dispose:调用一次
当State对象从树中被永久移除时调用;通常在此方法中释放资源。

13.2 statelessWidget

1、builde
2、update

14、flutter和RN

flutter与RN的相同点:
1.都是移动开发跨台解决方案
2.界面的编写都采用响应式视图,维护了一个状态机,只更新改变的最小区域界面
3.都支持热重载hot reload,开发调试非常方便
4.调用系统的service仍然需要封装接口,仍然还是需要懂得native开发
flutter与RN 的区别:

  1. 性能方面:
    Flutter由于是基于Dart语言, 所以避免了RN的那种通过桥接器与Javascript通讯导致效率低下的问题,所以在性能方面比RN更高一筹,会更接近原生的体验.
  2. 学习成本方面:
    Flutter是基于Dart语言,相对来说,由于要学习一门新的开发语言所以学习成本比较高, 而RN采用JS语言开发,基于React,对前端工程师更友好.
  3. UI 样式方面:
    flutter实现跨平台采用了更为彻底的方案, 因为它基于canvas自己实现了一套UI框架, 所以兼容性更好, 而 RN 在在样式方面还是会遇到比较多的问题,且解决起来会有点麻烦.
    4.成熟度的方面:
    React Native 是在 2015 年发布的,经过 3 年多的发展,已经比较成熟, 虽然也还不完善, 但是Flutter 是在今年 6 月份才推出发布预览版,社区也刚刚发展, 在github上还有两千多个待解决的问题,所以flutter需要更多时间。

flutter的有一套自己的UI框架,所以兼容性更好,且由于RN需要桥接器和JavaScript通讯,导致效率低下,flutter更接近原生体验。

15、Flutter 线程管理模型 (待加强)

Flutter Engine层会创建一个Isolate,并且Dart代码默认就运行在这个主Isolate上。必要时可以使用spawnUri和spawn两种方式来创建新的Isolate,在Flutter中,新创建的Isolate由Flutter进行统一的管理。
事实上,Flutter Engine自己不创建和管理线程,Flutter Engine线程的创建和管理是Embeder负责的,Embeder指的是将引擎移植到平台的中间层代码。
Flutter 中存在的四大线程:分别为 UI Runner、GPU Runner、IO Runner, Platform Runner (原生主线程) ,在 Flutter 中可以通过 isolate 或者 compute 执行真正的跨线程异步操作。

16、Flutter状态管理

Flutter的状态可以分为全局状态和局部状态两种。
常用的状态管理都是基于InheritedWidget封装的用于Widget树的数据传递与共享的的一套框架。
Provider是继承于InheritProvider,而InheritProvider本质上是一个InheritWidget,所以Provider本质上是依托于InheritProvider的机制来实现的widget树的状态共享。
总结:Provider本质上是依托于InheritProvider的机制来来实现widget树的共享。

17、isolate是怎么进行通信和实例化的?

1、isolate是一个隔离Dart执行的上下文环境。
2、isolate有自己的内容和单线程控制的事件循环。
3、isolate之间的内存逻辑上是隔离的,不共享内存。
4、任何Dart程序的并发都是运行多个isolate的结果。Dart没有共享内存的并发,所以不存在死锁问题。
isolate线程之间的通信主要通过port来进行,这个port消息传递过程是异步的。

18、Future还是isolate场景分析?

1、如果一段代码不会被中断,那么就直接使用正常的同步执行就行。
2、如果代码段可以独立运行而不会影响应用程序的流畅性,建议使用 Future (需要花费几毫秒时间)
3、如果繁重的处理可能要花一些时间才能完成,而且会影响应用程序的流畅性,建议使用 isolate (需要几百毫秒)
使用 isolate 的具体场景:
1、JSON解析: 解码JSON,这是HttpRequest的结果,可能需要一些时间,可以使用封装好的 isolate 的 compute 顶层方法。
2、加解密: 加解密过程比较耗时
3、图片处理: 比如裁剪图片比较耗时
4、从网络中加载大图

19、Flutter 是如何与原生Android、iOS进行通信的?(重点了解)

Flutter 通过 PlatformChannel 与原生进行交互,其中 PlatformChannel 分为三种:
BasicMessageChannel :用于传递字符串和半结构化的信息。
MethodChannel :用于传递方法调用(method invocation)。
EventChannel : 用于数据流(event streams)的通信。

20、Flutter 绘制流程

GPU的VSync信号同步到UI线程,UI线程使用Dart来构建抽象视图结构。然后,这些数据结构在GPU中进行图层合成,视图数据提供给 Skia引擎渲染为 GPU数据,这些数据通过 OpenGL或者 Vulkan提供给 GPU。

21、PlatformView

platform view 就是 AndroidView 和 UIKitView 的总称,允许将 native view 嵌入到了 flutter widget 体系中,完成 Dart 代码对 native view 的控制。
链接:https://www.jianshu.com/p/9306d7ecde35

22、Flutter的理念架构

image.png

1、Framework层则是使用Dart编写的一套基础视图库,包含了动画、图形绘制和手势识别等功能,是使用频率最高的一层。
2、Engine层负责图形绘制、文字排版和提供Dart运行时,Engine层具有独立虚拟机,正是由于它的存在,Flutter程序才能运行在不同的平台上。
3、Embedder是操作系统适配层,实现了渲染。

参考:https://www.jianshu.com/p/9064a68a05ae

23、flutter的key和context

1、key:是flutter用来标记widget的唯一标识。
  • Local Key(局部key):分为Value Key、Object Key和Unique Key,必须要有唯一性。
    Local Key顾名思义,指的是在当前Widget层级下,有唯一的Key属性。可以用int或者string来表示。
1.1 Value Key使用事例如下

使用学生的学号或者身份证号等唯一的标识来标识。


Value Key
1.2 Object Key

通过对比对象的地址来判断是否相同,下同new了两次Student,所以key是不同的,如果使用同一个对象就会报错。


Object Key
1.3 Unique Key

实在找不到什么标识的话我们可以使用UniqueKey来进行标记,自动生成唯一的key。


Unique Key
  • Global Key (全局key):在全局APP中,具有唯一性。Global Key的性能会比Local Key差很多。
    Global Key使用场景,一是让widget在Widget Tree发生大幅改动的时候仍然保留状态,二是像JavaScript里面getElementById那样查找某个元素并得到它的各种信息。
final  _globalKey1 =  GlobalKey ();
final  _globalKey2 =  GlobalKey ();
横竖屏保持状态

获取全局的数据信息:



image.png
2、context:是指widget在Widget树中的具体位置,flutter就是根据context包含的信息,来绘制widget及其对应的位置。

24、Flutter 空安全

空安全(Sound null safety)是Flutter 更新之后 Dart 中新增的一项特性,swift语言也有此特性。有了空安全,Dart 分析器可以进行更好的检查。与空安全相关的新操作符和关键字有 ?、!。
在空安全之前,是可以直接赋值为null,且可以通过编译,这个就说空安全与之前最大的不同,而且是在编译阶段就直接报异常错误。
在使用空安全的情况下,我们想让变量赋值为null时,我们可以这样处理:

 int? count = null;
 String? name = null;
只需在类型后面添加 ? 即可
类型后面跟操作符 ? 表示当前变量可为null。

总结:
操作符 ? :放在类型后面表示当前变量可为null,例如 String a 和 String ? b ,a 不能为null,而 b 可以。
操作符 !:表示此变量值不为null,如果为null则会抛出异常。使用请慎重考虑。

25、flutter和原生交互传参的方式

Flutter和Xcode混编
重点总结:

1、flutter调用OC传参,需要使用MethodChannel

1、flutter内部点击事件传参

//  用于调用原生方法  "hometestmethod"标识符与OC中的监听标识符保持一致
  var homechannelmethod = MethodChannel("hometestmethod");
// 点击
  onTap: (){
        //给原生发送消息并传入参数,原生根据标识homePageCallNativeMethond来做对应的处理
        homechannelmethod.invokeMethod('homePageCallNativeMethond',{"key":"value","key1":"value1"});
      },

2、OC接受传参

//    1.创建方法通道对象,用于监听flutter调用原生时的回调,唯一标识“hometestmethod”与flutter要保持一致
    FlutterMethodChannel *channel = [FlutterMethodChannel methodChannelWithName:@"hometestmethod" binaryMessenger:flutterViewController.binaryMessenger];
    //2. 设置监听回调block,flutter端通过通道调用原生方法时会进入以下回调
    [channel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {
        //call的属性method是flutter调用原生方法的方法名,我们进行字符串判断然后写入不同的逻辑
        if ([call.method isEqualToString:@"homePageCallNativeMethond"]) {
            //flutter传给原生的参数
            id para = call.arguments;
            NSLog(@"flutter传给原生的参数:%@", para);
            //可以做界面跳转
            [self.navigationController pushViewController:[TestViewController new] animated:YES];
            //获取一个字符串
            NSString *nativeFinalStr = @"原生给flutter回传的值";
            if (nativeFinalStr!=nil) {
                //把获取到的字符串传值给flutter
                result(nativeFinalStr);
            }else{
                //异常(比如改方法是调用原生的getString获取一个字符串,但是返回的是nil(空值),这显然是不对的,就可以向flutter抛出异常 进入catch处理)
                result([FlutterError errorWithCode:@"001" message:[NSString stringWithFormat:@"进入异常处理"] details:@"进入flutter的trycatch方法的catch方法"]);
            }
        }else{
            //调用的方法原生没有对应的处理  抛出未实现的异常
            result(FlutterMethodNotImplemented);
        }
    }];
2、OC调用FLuttter传参,需要调用EventChannel,通过代理传参。
//1. 创建事件通道对象,唯一标识 “hometest”,到时flutter是根据该标识来监听原生发送给flutter的参数信息
    FlutterEventChannel *evenChannel = [FlutterEventChannel eventChannelWithName:@"hometest" binaryMessenger:flutterViewController.binaryMessenger];
    //2. 当原生跳往flutter时会触发下面的- (FlutterError *)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)events回调方法,可以在该方法中给flutter传递参数
    [evenChannel setStreamHandler:self];

//原生跳转flutter时,会触发该方法,在该方法中可以传递参数给flutter界面,需要注意的是flutter代码中必须写上对应的监听代码,这里才会被执行
- (FlutterError *)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)events{
    if (events) {
        events(@{@"key":@"value"});
    }
    return nil;
}

26、本地数据存储方式

文件、sharedPreferencessqlite数据库

和原生通讯方式

Flutter定义了三种不同类型的channel,分别是

eventChannel:用于数据流通信,原生向Flutter传参,通过代理eventSink传参。

methodChannelflutter向原生传参。定义好方法名

BaseMessageChannel:未使用

27、键盘高度超出解决

Scaffold(

resizeToAvoidBottomPadding: false, //输入框抵住键盘

)

28、ListViewVertical viewport was given unbounded height错误解决方法

原因:宽高溢出。导致widget不显示

解决:给ListView的shrinkWrap属性设置为ture(收缩)。父视图的大小跟随子组件内容大小。

29、FluttersetState() or markNeedsBuild() called during build.错误解决办法

错误原因:在运行中,控件和响应事件没有构建完,延时加载就可解决问题

解决方法:使用Future延时

void _addIndex() {
 Future.delayed(Duration(milliseconds: 200)).then((e) {
  setState(() {});
});
}
}

30、FluttersetState() called after dispose()错误解决办法

错误解析:防止页面关闭执行setState()方法
解决方法:mountedmounted 表明 State 当前是否正确绑定在View树中。State的生命周期里面,这个 mounted 属性不会改变,直至 framework 调用 State.dispose,当改变之后, State 对象再也不会调用 build 方法 mounted = false。

31、实现水波纹

InkWell

32、安卓和iOS 控件区别

Flutter提供了两种不同风格安卓的MaterialiOSCupertino风格

33、StatelessWidget的生命周期有哪些,请按生命周期顺序说一下?

只有build,update。

34、Flutter是怎么实现热重载的,说说具体实现原理?

Flutter的 Debug 模式支持 JIT(Just In Time),指的是即时编译或运行时编译,

JIT 编译器将 Dart 代码编译成可以运行在 Dart VM 上的 Dart Kernel, Dart Kernel可以动态更新。

35、为什么说Flutter的性能好,与RN的区别?

1、flutter没有桥接层。

2、自带渲染引擎。skia

3、编译执行。JS是解释执行。

4、Flutter Engine虚拟机

36、Flutter中是怎么实现并发操作的?

两种方式:isolate 和 Future

Future:短时间的几毫秒。

isolate:长时间几百毫秒

37、isolate是怎么进行通信和实例化的?

通信:通过port实现通信,sendPort和reservePort,这个port消息传递过程是异步的。

实例化: Isolate isolate =await Isolate.spawn<SendPort>(dataLoader, mainThreadPort.sendPort);

重点,深刻解析: https://www.bbsmax.com/A/kjdwe1NBJN/

38、Future和isolate有什么区别?

Future: 是一个异步执行并且在未来的某一个时刻完成(或失败)的任务。Future本质上 并非并行执行,而是遵循事件循环处理事件的顺序规则执行。

isolate:为了并行运行代码。每个isolate都有自己的事件循环。

总结:

  • 如果一个方法需要几毫秒使用Future
  • 如果一个处理流程需要几百毫秒使用isolate

使用isolate场景:

  • JSON解码
  • 加密
  • 图像处理:裁剪
  • 从web加载图像

39、Stream与Future是什么关系?

Future:只能接收一次返回的数据

Stream:接收多次返回的数据。flutter就是基于Stream流的处理。

40、setState是同步还是异步操作

setState是异步操作。下面代码的打印先输出2然后在输出1.

onChanged:(value) async{
    setState(){
        print('1111111')  ;
  }
        print('222222')  ;
}

41、setState无效

Flutter开发App时,偶尔会遇到SetState()不起作用或界面更新不完全的Bug,是什么原因导致的呢?
比如在弹窗中点击,标记对号。我们是使用系统的弹窗SimpleDialog来展示,但是点击之后标记框状态没有改变,这是为什么呢?因为SimpleDialog是无状态组件,要解决问题,我们需要包裹StateFulBuilder 如下:


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

推荐阅读更多精彩内容