Flutter为什么使用Dart?

老孟导读:关于Flutter为什么使用Dart?这个话题,就像PHP是世界上最好的语言一样,争论从来没有停止过,有很多说法,比如:

  • Google是为了推广Dart,Dart是亲儿子。
  • Flutter团队和Dart团队相邻,沟通起来更方便。

或许存在这样的考虑,但即使是亲儿子,烂泥也要能扶上墙才行啊,难道你真的觉得如此重大的决定是靠这些主观色彩决定的?不可能的,所以Dart一定是有其非常大的优势。

前言

在Flutter成立之初,Flutter团队评估了10多种语言,Flutter团队从四个主要维度进行评估,并考虑了框架作者,开发人员和最终用户的需求。最终Dart在所有评估维度上得分都很高,并且符合所有要求和标准。

Dart在以下主要标准上赢得了高分:

  • 开发人员的生产力。Flutter的主要价值主张之一是,它可以让开发人员使用相同的代码库为iOS和Android创建应用程序,从而节省了工程资源。使用高效的语言可以进一步加速开发人员,并使Flutter更具吸引力。这对我们的框架团队和开发人员都非常重要。Flutter的大多数语言都是用与我们提供给用户的语言相同的语言构建的,因此我们需要在不牺牲开发人员的框架和小部件的可访问性或可读性的情况下,保持100k的代码行的生产力。
  • 面向对象。对于Flutter,我们需要一种适合Flutter问题领域的语言:创建视觉用户体验。该行业拥有数十年使用面向对象语言构建用户界面框架的经验。尽管我们可以使用非面向对象的语言,但这将意味着重新发明轮子来解决一些难题。另外,绝大多数开发人员都有面向对象开发的经验,这使得学习Flutter开发方法变得更加容易。
  • 可预测的高性能。借助Flutter,我们希望使开发人员能够创建快速,流畅的用户体验。为了实现这一点,我们需要能够在每个动画帧中运行大量的最终开发人员代码。这意味着我们需要一种既能提供高性能又能提供可预测的性能的语言,而又不会导致帧丢失的定期暂停。
  • 快速分配。Flutter框架使用一种功能样式的流程,该流程在很大程度上依赖于基础内存分配器,可以有效地处理短暂的短期分配。此样式是使用具有此属性的语言开发的,在缺少此功能的语言中无法有效工作。

Dart

以下是Dart功能的列表,这些功能对于Flutter来说是必不可少:

  • Dart是AOT(Ahead Of Time)编译的,编译成快速,可预测的本机代码,几乎所有Flutter都可以用Dart编写。这不仅使Flutter更快,而且几乎所有内容(包括所有小部件)都可以自定义。
  • Dart还可以通过JIT(Just In Time)进行编译,以实现异常快速的开发周期和改变游戏规则的工作流(包括Flutter亚秒级有状态热重装)。
  • Dart使创建以60fps运行的平滑动画和过渡更加容易。Dart可以在没有锁的情况下进行对象分配和垃圾回收。与JavaScript一样,Dart避免了抢先式调度和共享内存(从而避免了锁定)。因为Flutter应用程序已编译为本机代码,所以它们不需要在领域之间建立缓慢的桥梁(例如,JavaScript与本机之间)。它们的启动速度也快得多。
  • Dart允许Flutter避免使用像JSX或XML这样的声明式布局语言,也不需要单独的可视化界面构建器,因为Dart的声明式程序化布局易于阅读和可视化。而且,由于所有布局都以一种语言和一种位置显示,因此Flutter可以轻松地提供使布局变得轻松的高级工具。
  • 开发人员发现Dart特别易于学习,因为它具有静态和动态语言用户都熟悉的功能。

并非所有这些功能都是Dart独有的,但是它们的结合达到了一个甜蜜点,这使得Dart在实施Flutter方面具有独特的功能。如此之多,很难想象Flutter会像没有Dart一样强大。

编译与执行

如果您已经了解静态和动态语言,AOT和JIT编译以及虚拟机等主题,则可以跳过本节。

从历史上看,计算机语言已分为两类:静态语言(例如,Fortran或C,其中在编译时静态键入变量)和动态语言(例如,Smalltalk或JavaScript,其中,变量的类型可以在运行时更改)时间)。通常会编译静态语言,以生成目标计算机的本机代码(或汇编代码)程序,这些程序在运行时由硬件直接执行。动态语言由解释器执行,而不会产生机器语言代码。

当然,后来事情变得更加复杂。虚拟机(VM)的概念变得很流行,它实际上只是一种模仿软件中的硬件机器的高级解释器。虚拟机可以更轻松地将语言移植到新的硬件平台。在这种情况下,VM的输入语言通常是中间语言。例如,一种编程语言(例如Java)被编译成一种中间语言(字节码),然后在VM(JVM)上执行。

另外,现在有即时(JIT)编译器。JIT编译器会在程序执行期间运行,并进行即时编译。在程序创建期间(运行时之前)执行的原始编译器现在称为提前(AOT)编译器。

通常,只有静态语言才适合将AOT编译为本机机器代码,因为机器语言通常需要知道数据的类型,而在动态语言中,类型不是预先固定的。因此,通常会解释动态语言或编译JIT。

在开发过程中 AOT 编译,开发周期(从更改程序到能够执行程序以查看更改结果的时间)总是很慢。但是 AOT 编译产生的程序可以更可预测地执行,并且运行时不需要停下来分析和编译。AOT 编译的程序也更快地开始执行(因为它们已经被编译)。

相反,JIT编译可提供更快的开发周期,但会导致执行速度较慢或更加不稳定。特别是,JIT编译器的启动时间较慢,因为在程序开始运行时,JIT编译器必须在执行代码之前进行分析和编译。研究表明,如果开始执行需要花费几秒钟的时间,那么很多人就会放弃该应用程序。

以上就是 AOT 和 JIT 相关知识,将 AOT 和 JIT 编译的优点结合起来不是很棒吗?而Dart就是通过支持AOT 和 JIT 两种类型的编译而带来显著的优势。

编译并执行Dart

在研究Dart之前,Dart团队的成员已经在高级编译器和虚拟机上进行了开创性的工作,既针对动态语言(如JavaScript 的V8引擎,针对Smalltalk的Strongtalk)又针对静态语言(如Java 的Hotspot编译器)。他们利用这种经验使Dart在如何进行编译和执行方面异常灵活。

Dart是极少数适合于同时编译AOT和JIT的语言之一。支持这两种编译为Dart和(尤其是Flutter)提供了明显的优势。

在开发过程中使用JIT编译,使用的编译器特别快。然后,当应用程序准备发布时,将对其进行AOT编译。因此,借助先进的工具和编译器的帮助下,提供两全其美的方案:极快的开发周期,并快速执行和启动时间。

Dart在编译和执行方面的灵活性不止于此。例如,Dart可以编译为JavaScript以便浏览器可以执行。这允许在移动应用程序和Web应用程序之间重复使用代码。开发人员报告说,他们的移动和Web应用程序之间的代码复用率高达70%。通过将Dart编译成本机代码,或编译成JavaScript并将其与node.js结合使用,也可以在服务器上使用Dart 。

最后,Dart还提供了一个独立的VM,该VM使用Dart语言本身作为其中间语言(本质上像解释器一样工作)。

Dart可以有效地编译AOT或JIT,解释或转换为其他语言。Dart的编译和执行不仅异常灵活,而且速度特别

有状态热重载

Flutter最受欢迎的功能之一是其极快的热重载。在开发期间,Flutter使用JIT编译器,该编译器通常可以在一秒钟内重新加载并继续执行代码。只要有可能,应用状态就会在每次重新加载时保持不变,因此该应用可以从中断处继续运行。

除非您亲自体验过,否则很难理解真正快速(可靠)的热重载在开发过程中有多重要。Flutter的热加载功能使尝试新想法或尝试替代方法变得容易得多,极大地促进了创造力。

以下是一位移动应用程序开发人员对Flutter 热重载的评价:

我想测试热重载,所以我改变了颜色,保存修改,结果……就喜欢上它了❤!

这个功能真的很棒。我曾认为 Visual Studio 中编辑和继续(Edit & Continue)很好用,但这简直令人惊叹。有了这个功能,我认为移动开发者的生产力可以提高两倍。

这对我来说真的是翻天覆地的变化。当我部署代码并花费很长时间时,我分心了,做了其他事情,当我回到模拟器 / 设备时,我就忘了想测试的内容。有什么比花 5 分钟将控件移动 2px 更令人沮丧?有了 Flutter,这不再存在。

避免卡顿

一个快速的应用程序是伟大的,但流畅的应用程序,甚至更好。即使是超级快的动画,如果看起来很生涩,也会看起来很糟糕。但是,防止卡顿可能很困难,因为因素太多。Dart 有许多功能可以避免许多常见的导致卡顿的因素。

当然,(像任何语言一样)仍然可以在Flutter中编写一个简陋的应用程序。Dart更具可预测性,可帮助开发人员更好地控制其应用的平滑度,从而更轻松地提供最佳的用户体验,无所不能。

Flutter创建的用户界面以60 fps的速度运行,其性能远优于其他跨平台开发框架创建的用户界面。

不仅比跨平台应用程序更好,而且与最佳本机应用程序一样好:

用户界面非常流畅……我从未见过如此流畅的Android应用。

AOT汇编和“桥梁”

我们讨论过一个有助于保持顺畅的特性,那就是 Dart 能 AOT 编译为本地机器码。预编译的 AOT 代码比 JIT 更具可预测性,因为在运行时不需要暂停执行 JIT 分析或编译。

但是,AOT编译代码有一个更大的优势,那就是避免了“ JavaScript桥梁”。当动态语言(例如JavaScript)需要与平台上的本机代码进行互操作时,它们必须通过网桥进行通信,这会导致上下文切换必须保存大量的状态(可能保存到辅助存储)。这些上下文切换是双重打击,因为它们不仅使速度变慢,而且可能导致严重的问题。

注意:即使编译的代码也可能需要与平台代码进行对话的接口,这也可以称为桥接,但是通常比动态语言所需的桥接要快几个数量级。此外,由于Dart允许将小部件之类的内容移入应用程序,因此减少了跨过桥梁的需要。

抢先式调度,时间分片和共享资源

大多数支持多个并发执行线程的计算机语言(包括Java,Kotlin,Objective-C和Swift)都使用抢占线程之间进行切换。每个线程都分配了一个“执行时间”,如果超过了分配的时间,则使用上下文切换来抢占该线程。但是,如果在更新线程(例如内存)之间共享的资源时发生了抢占,则这会导致争用条件。

竞争状况是双重打击,因为它们可能导致严重的错误,包括使应用程序崩溃并导致数据丢失,而且由于依赖于独立线程的相对时间,因此特别难以查找和修复它们。在调试器中运行应用程序时,竞争条件会停止表现出来,这很常见。

解决争用条件的典型方法是使用防止其他线程执行的锁来保护共享资源,但是锁本身可能会导致棘手甚至更严重的问题(包括死锁饥饿)。

Dart 采取了不同的方法来解决这个问题。Dart 中的线程称为 isolate,不共享内存,从而避免了大多数锁。isolate 通过在通道上传递消息来通信,这与 Erlang 中的 actor 或 JavaScript 中的 Web Worker 相似。

Dart和JavaScript一样,都是单线程的,这意味着它根本不允许抢占。相反,线程显式地产生(使用async / await,FuturesStreams)。这使开发人员可以更好地控制执行。单线程可帮助开发人员确保关键功能(包括动画和过渡)在没有抢占的情况下得以执行。这不仅对用户界面,而且对于其他客户端-服务器代码,通常都是一个很大的优势。

当然,如果开发人员忘记放弃控制,这可能会延迟其他代码的执行。但是,我们发现,忘记屈服通常比忘记锁定容易得多(因为很难找到竞争条件)。

对象分配和垃圾回收

造成垃圾的另一个严重原因是垃圾收集。确实,这只是访问共享资源(内存)的一种特殊情况,在许多语言中,这都需要使用锁。但是在收集可用内存时,锁可能会阻止整个应用程序运行。但是,Dart几乎可以在没有锁的情况下几乎始终执行垃圾回收。

Dart 使用先进的分代垃圾回收和对象分配方案,该方案对于分配许多短期对象(对于Flutter 这样的反应式用户界面来说非常完美,Flutter 为每帧重建不可变视图树)而言非常快。Dart 可以用一个指针凹凸分配一个对象(无需锁定)。这将导致平滑的滚动和动画效果,而不会产生现卡顿。

统一布局

Dart的另一个好处是Flutter不会在您的程序和其他模板化或布局语言(例如JSX或XML)之间拆分布局,也不需要单独的可视化布局工具。这是用Dart编写的简单Flutter视图:

Center(child:
  Column(children: [
    Text('Hello, World!'),
    Icon(Icons.star, color: Colors.green),
  ])
)

但是,我知道您可能在想什么- 缺少专门的布局语言如何被称为优势?但它确实是颠覆性的。这是开发人员在题为“ 为什么本机应用程序开发人员应认真看待Flutter ” 的文章中写的。

在 Flutter 里,界面布局直接通过 Dart 编码来定义,不需要使用 XML 或模板语言,也不需要使用可视化设计器之类的工具。

我的预感是,听到这个消息后,你们中的一些人甚至会畏缩一点。表面看来,这也是我的反应。使用视觉工具进行布局难道不是一件容易的事。用代码编写各种约束逻辑会不会使事情变得过于复杂?

结果不然。天啊,它简直让我大开眼界。

答案的第一部分是上面提到的热重载。

这比 Android 的 Instant Run 和任何类似解决方案不知道要领先多少年。对于大型的应用同样适用。如此快的速度,正是 Dart 的优势所在。

实际上,可视化编辑器就变得多余了。我一点都不怀恋 XCode 的自动重布局。

Dart 创建的布局简洁且易于理解,而“超快”的热重载可立即看到结果。这包括布局的非静态部分。

结果,在 Flutter 中进行布局要比在 Android/XCode 中快得多。一旦你掌握了它(我花了几个星期),由于很少发生上下文切换,因此会节省大量的开销。不必切换到设计模式,选择鼠标并开始点击,然后想是否有些东西必须通过编程来完成,如何实现等等。因为一切都是程序化的。而且这些 API 设计得非常好。它很直观,并且比自动布局 XML 更强大。

例如,下面是一个简单的列表布局,该列表布局以编程方式定义的所有其他项目之间添加了分隔线(水平线):

return new ListView.builder(itemBuilder: (context, i) {
  if (i.isOdd) return new Divider(); 
  // rest of function
});

在 Flutter 中,无论是静态布局还是编程布局,所有布局都存在于同一个位置。新的Dart 工具,包括Flutter Inspector 和大纲视图(利用所有的布局定义都在代码里)使复杂而美观的布局更加容易。

Dart是专有语言吗?

不,Dart(如 Flutter)是完全开源的,具备清楚的许可证,同时也是 ECMA 标准的。Dart 在 Google 内外很受欢迎。在谷歌内部,它是增长最快的语言之一,并被 Adwords、Flutter、 Fuchsia 和其他产品使用;在谷歌外部,Dart 代码库有超过 100 个外部提交者。

Dart开放性的更好指标是Google以外社区的发展。例如,我们看到来自第三方的Dart文章和视频源源不断(包括Flutter和AngularDart),我在本文中引用了其中的一些。

除了Dart本身的外部提交者之外,公共Dart软件包存储库中还有3000多个软件包,包括Firebase,Redux,RxDart,国际化,加密,数据库,路由,集合等的库。

Dart程序员会容易找到吗?

如果没有很多程序员知道Dart,找到合格的程序员会更困难吗?当然不是,Dart使它更容易找到程序员,因为它是一种非常快速且易于学习的语言。Java,JavaScript,Kotlin,C#或Swift等语言的程序员几乎可以立即在Dart中开始编程。最重要的是,热重载鼓励用户玩Dart并尝试新事物,这使得学习Dart更快,更愉快。

这是一个程序员在题为“ 为什么Flutter将在2018年腾飞 ”的文章中所说的:

Dart是用于开发Flutter应用程序的语言,学习起来很容易。Google拥有创建简单,记录良好的语言(例如Go)的经验。到目前为止,对我而言,Dart使我想起了Ruby,这是一种学习的乐趣。它不仅适用于移动设备,也适用于Web 开发

另一篇关于Flutter和Dart的文章,标题为“ 为什么要Flutter?而不是框架X?甚至更好,为什么我要全力以赴。

Flutter使用由Google创建的Dart语言,说实话,我不喜欢C#或JAVA之类的强类型语言,但我不知道Dart编写代码的方式为什么看起来有所不同。我写起来很舒服。也许是因为它非常简单易学。

通过广泛的UX研究和测试,Dart专门设计为熟悉且易于学习。例如,在2017年上半年,Flutter团队与八名开发人员进行了UX研究。我们向他们简要介绍了Flutter,然后将它们松散了一个小时左右,以创建一个简单的视图。所有参与者都能够立即开始编程,即使他们以前从未使用过Dart。他们专注于编写响应式视图,而不是语言。Dart 直接就能上手用了。

最后,一位参与者(在任务中取得了特别大的进步)没有提及该语言,因此我们询问他们是否意识到自己使用的是哪种语言。他们不知道。语言没关系 ; 他们在几分钟内就可以在Dart中编程。

学习新系统的难点通常不是学习语言,而是学习所有编写好的代码的库,框架,工具,模式和最佳实践。而且Dart库和工具都非常出色,并且文档齐全。一篇文章宣称:“作为奖励,他们还非常注意自己的代码库,并且拥有我见过的最好的文档。” 学习Dart只需花很少的精力就可以通过节省学习其余时间的时间来弥补。

作为直接的证据,Google内部的一个大型项目希望将其移动应用程序移植到iOS。他们打算雇用一些iOS程序员,但决定尝试Flutter。他们监控了使开发人员快速掌握Flutter所花费的时间。他们的结果表明,程序员可以在三周内学习Dart Flutter并提高工作效率。相比之下,他们之前观察到的五个星期可以使程序员仅在Android上就可以上手(更不用说他们必须雇用和培训iOS的开发人员)。

最后,文章“ 我们为什么选择Flutter以及它如何使我们的公司变得更好 ”来自一家将大型企业应用程序迁移到所有三个平台(iOS,Android和Web)上的Dart的公司。他们的结论:

招人变得容易多了。无论他们是来自 Web、iOS 还是 Android,我们现在都希望接受最佳人选。

现在我们拥有 3 倍的工作效率,因为我们所有的团队都集中在一个代码库上。

知识共享达到前所未有的高度。

通过使用Dart和Flutter,他们能够将生产力提高三倍。鉴于他们之前所做的事情,这不足为奇。与许多公司一样,他们正在使用独立的语言,工具和程序员为每个平台(Web,iOS和Android)构建独立的应用程序。改用Dart意味着他们不再需要雇用三种不同的程序员。对于他们来说,将现有的程序员转移到Dart上很容易。

他们和其他人发现,一旦程序员开始使用Flutter,他们通常会爱上Dart。他们喜欢语言的简洁性和缺乏仪式感。他们喜欢语言功能,例如级联,命名参数,异步/等待和流。最重要的是,他们喜欢Dart所提供的Flutter功能(例如热重载),而Dart帮助他们构建的漂亮,高性能的应用程序。

秘诀就是专注

Dart 2中的改进集中在优化客户端开发上。但是Dart仍然是构建服务器端,台式机,嵌入式系统或其他程序的绝佳语言。

专注是一件好事。专注于几乎可以使所有持久的流行语言受益。例如:

  • C是用于编写操作系统和编译器的系统编程语言。它变得更多了。
  • Java是为嵌入式系统设计的语言。
  • JavaScript是Web浏览器的脚本语言(!)。
  • 甚至备受反对的PHP也成功了,因为它专注于编写“个人主页”(并以此为名)。

另一方面,许多语言已经明确尝试(但失败)成为完全通用的语言,例如PL / 1和Ada等。最常见的问题是,没有集中注意力,这些语言就成了众所周知的厨房水槽。

使Dart成为出色的客户端语言的许多功能也使它成为服务器端使用的更好语言。例如,Dart避免了抢先式多任务这一事实使它具有与服务器上的Node相同的优势,但打字效果更好,更安全。

为嵌入式系统编写软件也是如此。Dart可靠地处理多个并发输入的能力是关键。

最后,Dart在客户端上的成功将不可避免地引起更多在服务器上使用它的兴趣-就像JavaScript和Node发生的情况一样。为什么要强迫人们使用两种不同的语言来构建客户端服务器软件?

结论

这对于 Dart 来说是一个激动人心的时刻。使用 Dart 的人喜欢它,而 Dart 2 中的新特性使其成为你工具库中更有价值的补充。如果您没有使用Dart,希望本文为您提供有关Dart的新功能或不同之处的有价值的信息,并且您可以尝试使用它和Flutter。

查看英文原文: https://hackernoon.com/why-flutter-uses-dart-dd635a054ebf

交流

老孟Flutter博客地址(近200个控件用法):http://laomengit.com

欢迎加入Flutter交流群(微信:laomengit)、关注公众号【老孟Flutter】:

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