iOS原生与Flutter混合开发文档

目的: 在iOS原生项目中,嵌套Flutter页面,实现原生与Flutter的通信。

实现方案:为了把Flutter项目引入到原生工程,我们需要将Flutter工程改造为原生工程的一个组件依赖,并以组件化的方式管理不同平台的Flutter构建产物,即iOS平台通过pod进行依赖管理,安卓平台使用aar。这样我们就可以在iOS工程中使用FlutterViewController,安卓使用FlutterView。为Flutter搭建应用入口,实现Flutter与原生的混合开发。


最终实现效果如下:

  • 首页是原生页面,顶部定义Label,用于传值到Flutter页面。下面定义了两个按钮,都用于跳转到Flutter页面。
  • 第一个按钮,Push到Flutter页面后,显示原生的导航条,Flutter页面显示原生传过来的内容。
  • 第二个按钮,Push到Flutter页面后,显示Flutter的导航条,Flutter页面显示原生传过来的内容。再从Flutter页面跳转到原生页面,原生页面显示Flutter页面传过来的内容。
实现效果

具体实现如下:

一:创建Flutter module工程

1: 为了让Flutter工程能够通过pod进行管理,首先要创建一个Flutter module项目:

Project location:选择和原生工程同一级目录,
Project type:选择Module,
原生语言和平台选择对应的即可,如下图所示:


创建flutter_module项目

2: Flutter module创建后,应该和iOS原生工程放在同一目录(方便管理),如下图所示:

目录

二:FlutterBoost介绍与引入到工程

1: FlutterBoost是一个Flutter插件,它是新一代Flutter-Native混合解决方案 ,它可以轻松地为现有原生应用程序提供Flutter混合集成方案。FlutterBoost的理念是将Flutter在原生工程中像Webview那样来使用。我们知道在现有应用程序中同时管理Native页面和Flutter页面并非易事, 因此FlutterBoost帮你处理页面的映射和跳转,你只需关心页面的名字和参数即可(通常可以是URL)。

FlutterBoost的基本功能:

  • 可复用的通用型混合开发方案
  • 支持更加复杂的混合模式,比如支持tab切换的场景
  • 无侵入性方案,使用时不再依赖修改Flutter的方案
  • 支持页面生命周期统一的管理。
  • iOS和安卓双端统一,具有统一明确的设计概念。

2:在flutter_module中引入FlutterBoost 插件(版本号:5.0.1

1: 在flutter_module项目中找到pubspec.yaml文件,在dependencies中配置flutter_boost:

version: 1.0.0+1

environment:
   sdk: '>=3.2.3 <4.0.0'
dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.2
  flutter_boost:
    git:
      url: 'https://github.com/alibaba/flutter_boost.git'
      ref: '5.0.1'

2: 终端执行flutter pub get命令,安装插件,安装完成后,就可以使用该插件了。

3:FlutterBoost基础配置

1: 在main.dart文件中,导入flutter_boost库

 import 'package:flutter_boost/flutter_boost.dart';

2: 如果你的工程里已经有一个继承自WidgetsFlutterBinding的自定义Binding,则只需要将其withBoostFlutterBinding ,如果你的工程没有自定义的Binding,则可以参考CustomFlutterBinding的做法 ,BoostFlutterBinding用于接管Flutter页面的生命周期,必须得接入的。

创建一个自定义的Binding,继承和with的关系如下,里面什么都不用写

class CustomFlutterBinding extends WidgetsFlutterBinding with BoostFlutterBinding {}

3: 在void main方法中调用CustomFlutterBinding()

void main() {
  /// 这里的CustomFlutterBinding调用务必不可缺少,用于控制Boost状态的resume和pause
  CustomFlutterBinding();
  runApp(const MyApp());
}

4: 创建一个有状态的MyApp模版类,在类中配置flutterBoost的路由,路由主要是定义页面名称和参数,从而根据路由实现Flutter和原生的通信。

_MyAppState类中声明一个routerMap变量,对每一个页面的路由方式进行配置。如果想用类似iOS平台的跳转动画(默认跳转动画是安卓样式),那么只需要像下面这样写成CupertinoPageRoute即可。

boostRouterMap变量的作用:
boostRouterMap中的string是页面的名称,下面中的homepage就是我们自定义的一个页面,而FlutterBoostRouteFactory是一个方法的重命名。这个方法的传入参数是路由的设置和uniqueID, 返回值是路由Route类。
routerMap中我们看到的homepage字符串右边的模块其实就是该方法的具体实现,包括参数、函数体,返回的是一个路由的CupertinoPageRoute,其为Route的子类。
uniqueId其实在这里并不会用到,我们真正关心的是Settings中的参数arguments,其也为一个Map类型。我们可以从中获取要打开的新页面所必须使用的一些参数,并传入。

class _MyAppState extends State<MyApp> {
    Map<String, FlutterBoostRouteFactory> routerMap = {
      'homepage': (settings, uniqueId) {
        return CupertinoPageRoute( // CupertinoPageRoute:类似iOS页面的Push效果
            settings: settings,
            builder: (_) {
              Map<String, dynamic>? map = settings.arguments as Map<String,
                  dynamic>;
              String data = map['data'] as String;
              return Homepage(
                data: data,
              );
            });
      },
  }
}

routeFactory方法的作用:
要在build方法中创建FlutterBoostApp这样一个Widget,那么就需要传入一个FlutterBoostRouteFactory类型的参数。

// 在重写的build方法中,构建FlutterBoostApp,将routeFactory和appBuilder这两个方法作为参数传入。
Widget build(BuildContext context) {
  return FlutterBoostApp(
    routeFactory,
    appBuilder: appBuilder,
  );
}

我们实现一个routeFactory方法,该方法的参数和返回值均同FlutterBoostRouteFactory类型相同,所以可以直接当作参数值传入到FlutterBoostApp的初始化方法中,routeFactory方法用于通过传入的页面名称从routerMap获取到对应的路由配置方法,并传入所需参数进行调用。

Route<dynamic>? routeFactory(RouteSettings settings, String? uniqueId) {
  FlutterBoostRouteFactory? func = routerMap[settings.name!];
  if (func == null) {
    return null;
  }
  return func(settings, uniqueId);
}

appBuilder方法的作用:
构建FlutterBoostApp还需要FlutterBoostAppBuilder类型的参数,其也是方法的别名。

typedef FlutterBoostAppBuilder = Widget Function(Widget home);

所以这里也实现一个appBuilder方法,参数和返回值同以上类型保持相同,可以直接将此方法当参数传入使用。appBuilder方法构建了一个MaterialApp类型的Widget,注意这里必须加上builder参数,否则showDialog等会出问题。

Widget appBuilder(Widget home) {
  return MaterialApp(
    home: home,
    debugShowCheckedModeBanner: false, // 是否显示debug模式

    /// 必须加上builder参数,否则showDialog等会出问题
    builder: (_, __) {
     // return const Homepage();
      return home;
    },
  );

5: FlutterBoost基础配置完整代码如下:

class _MyAppState extends State<MyApp> {

  // 配置页面路由
  Map<String, FlutterBoostRouteFactory> routerMap {
     'homepage': (settings, uniqueId) {
     return CupertinoPageRoute( // CupertinoPageRoute:类似iOS页面的Push效果
         settings: settings,
         builder: (_) {
           Map<String, dynamic>? map = settings.arguments as Map<String,dynamic>;
            String data = map['data'] as String;
            return Homepage(
              data: data,
            );
         });
      },
  };

  // routeFactory方法用于通过传入的页面名称从routeMap获取到对应的路由配置方法,并传入所需参数进行调用
  Route<dynamic>? routeFactory(RouteSettings settings, String? uniqueId) {
    FlutterBoostRouteFactory? func = routerMap[settings.name!];
    if (func == null) {
      return null;
    }
    return func(settings, uniqueId);
  }

  Widget appBuilder(Widget home) {
    return MaterialApp(
      home: home,
      debugShowCheckedModeBanner: false,

      /// 必须加上builder参数,否则showDialog等会出问题
      builder: (_, __) {
       // return const Homepage();
        return home;
      },
    );
  }

  @override
  // 在重写的build方法中,构建FlutterBoostApp,将routeFactory和appBuilder这两个方法作为参数传入。
  Widget build(BuildContext context) {
    return FlutterBoostApp(
      routeFactory,
      appBuilder: appBuilder,
    );
  }
}


三: 创建自定义的Flutter页面

1: 自定义Flutter的homepage页面,该页面用于原生页面跳转到Flutter页面。

在Flutter Module项目的lib目录下,新建一个homepage.dart文件,在文件中新建一个有状态的Homepage类,并做简单布局。
在类中定义一个String类型的常量datadata用于接收从原生页面传过来的参数。
在布局的Container容器的Text文本中,调用widget.data, 实现数据的渲染。

具体代码如下所示:

import 'package:flutter/material.dart';
import 'package:flutter_boost/flutter_boost.dart';

class Homepage extends StatefulWidget{
  final String? data;
  const Homepage({Key? key, this.data}) : super(key: key);

  @override
  State<Homepage> createState() => _HomepageState();
}

class _HomepageState extends State<Homepage> {
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    print("进入到Flutter页面");
  }
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          Container(
            margin: const EdgeInsets.only(top: 120),
            alignment: Alignment.center,
            child: const Text("这是一个Flutter页面",
              style: TextStyle(
                  fontSize: 22,
                  color: Colors.blue
              ),
            ),
          ),
          SizedBox(height: 30,),
          Container(
            alignment: Alignment.center,
            child: const Text("下面内容是原生传过来的值",
              style: TextStyle(
                  fontSize: 18,
                  color: Colors.black
              ),
            ),
          ),
          const SizedBox(height: 20,),
          Container(
            color: Colors.lime,
            margin:const EdgeInsets.only(left: 60,right: 60),
            child:Text(widget.data ?? "",
              style:const TextStyle(
                  fontSize: 16,
                  color: Colors.black
              ),),
          ),
        ],
      ),
    );
  }
}

2: 自定义Flutter的simple_page页面,该页面用于Flutter页面跳转原生页面。

新建一个simple_page.dart文件,在文件中新建一个有状态的SimplePage类,并做简单布局。
在类中定义一个String类型的textStrtextStr用于将该字符串从Flutter页面传到原生页面。
在ElevatedButton的点击事件中,调用flutterBoost提供的路由进行跳转并传参:
定义要跳转到原生页面的路由名称为NewsVC,在arguments中传递要传入的参数,代码如下所示:

BoostNavigator.instance.push(
       "NewsVC",
       arguments: {"data": textStr},
 );

SimplePage类的具体代码如下所示:

class _SimplePageState extends State<SimplePage> with PageVisibilityObserver{
  String textStr = "转朱阁,低绮户,照无眠。不应有恨,何事长向别时圆?人有悲欢离合,月有阴晴圆缺,此事古难全。但愿人长久,千里共婵娟。";

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
  }

  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.blue,
        title: const Text(
          "这是一个Flutter页面",
          style: TextStyle(color: Colors.white),
        ),
        leading: IconButton(
            onPressed: () {
              Map<String, Object> result = {'data': '返回页面时传参'};
              BoostNavigator.instance.pop(result);
            },
          icon: const Icon(Icons.arrow_back_ios),
          color: Colors.white,
        ),
      ),
      body: Center(
        child: Column(
          children: [
            Container(
              color: Colors.lime,
              margin: const EdgeInsets.only(left: 60, right: 60, top: 80),
              child: Text(textStr,
                  style: const TextStyle(
                    fontSize: 16,
                    color: Colors.black,
                  )),
            ),
            const SizedBox(
              height: 30,
            ),
            ElevatedButton(
              onPressed: () {
                BoostNavigator.instance.push(
                  "NewsVC",
                  arguments: {"data": textStr},
                );
              },

              style: ButtonStyle(
                backgroundColor: const MaterialStatePropertyAll(Colors.blue),
                foregroundColor: const MaterialStatePropertyAll(Colors.white),
                shape: MaterialStatePropertyAll(RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(12),
                )),
              ),
              child: const Text(
                "跳转到原生页面",
                style: TextStyle(
                  fontSize: 18,
                ),
              ),
            )
          ],
        ),
      ),
    );
  }
}


四: 配置iOS工程中的pod

1: 如果iOS工程未配置pod,需要先进入pod配置,配置过程可自行查找资料,这里不进行细诉。如果工程已经配置pod,则打开podfile文件,在文件中添加如下Flutter的相关配置。

  target 'iOSDemo' do
  use_frameworks!
  
  # 配置flutter相关path
  flutter_application_path = '../flutter-demo'
  
  load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')

  # 集成flutter的PodFile target,执行如下命令
  install_all_flutter_pods(flutter_application_path)

2: 在 Podfile 的 post_install 部分,调用 flutter_post_install(installer)

post_install do |installer|
  flutter_post_install(installer) if defined?(flutter_post_install)
end

3: 最后在终端执行 pod install


五:在iOS原生项目中,配置FlutterBoost

1: 进行准备工作创建 BoostDelegate单例类:

import UIKit
import flutter_boost

class BoostDelegate: NSObject, FlutterBoostDelegate {
    /// 单例
    public static let shared = BoostDelegate()  
  }

2: 实现 FlutterBoostDelegate 委托方法

FlutterBoostDelegate委托包括三个必须实现的方法:

  • pushNativeRoute:如果框架发现您输入的路由表在flutter里面注册的路由表中找不到,那么就会调用此方法来push一个纯原生页面。
  • pushFlutterRoute:当框架的withContainer为true的时候,会调用此方法来做原生的push。
  • popRoute:当pop调用涉及到原生容器的时候,此方法将会被调用。
@protocol  FlutterBoostDelegate <NSObject>

// 如果框架发现您输入的路由表在flutter里面注册的路由表中找不到,那么就会调用此方法来push一个纯原生页面
- (void) pushNativeRoute:(NSString *) pageName arguments:(NSDictionary *) arguments;

// 当框架的withContainer为true的时候,会调用此方法来做原生的push
- (void) pushFlutterRoute:(FlutterBoostRouteOptions *)options;

// 当pop调用涉及到原生容器的时候,此方法将会被调用
- (void) popRoute:(FlutterBoostRouteOptions *)options;

@end

3: BoostDelegate单例类的具体实现:

  • 通过实现pushNativeRoute方法,实现从 Flutter 页面跳转到原生页面。
  • 通过实现pushFlutterRoute方法,实现从原生页面跳转到 Flutter 页面。
  • 通过实现popRoute方法,完成从Flutter页面返回到原生页面。
  • 引入CustomFlutterController类,用于替换FBFlutterViewController,CustomFlutterController的作用后面会说明。
    具体代码实现如下:
class BoostDelegate: NSObject, FlutterBoostDelegate {
    /// 单例
    public static let shared = BoostDelegate()
    
    /// 用来 push 的导航栏
    var navigationController:UINavigationController?
    
    /// 用来存放 Flutter 页面返回原生页面时所执行的回调闭包
    var resultTable:Dictionary<String,([AnyHashable:Any]?)->Void> = [:]
    
    /// 从 Flutter 页面跳转到 iOS 原生页面
    func pushNativeRoute(_ pageName: String!, arguments: [AnyHashable : Any]!) {
        let isPresent = arguments["isPresent"] as? Bool ?? false
        let isAnimated = arguments["isAnimated"] as? Bool ?? true
        
        let targetViewController = openNativePage(pageName, arguments: arguments)
         
        // 可以用参数来控制跳转方式
        if (isPresent) {
            self.navigationController?.present(targetViewController, animated: isAnimated, completion: nil)
        } else {
            self.navigationController?.pushViewController(targetViewController, animated: isAnimated)
        }
    }
    
    /// 打开原生页面
    /// 根据 pageName 来判断生成哪个 VC
    private func openNativePage(_ pageName: String, arguments: [AnyHashable : Any]) -> UIViewController {
        // 根据定义的路由名称跳转到相应的界面
        if (pageName == "NewsVC") {
            let newsVC = NewsViewController()
            newsVC.arguments = arguments as NSDictionary
            return newsVC
        }
        return UIViewController()
    }
    
    /// 从 iOS 原生页面跳转到 Flutter 页面
    func pushFlutterRoute(_ options: FlutterBoostRouteOptions!) {
        let vc = CustomFlutterController()
        
        // 跳转时,是否隐藏原生导航
        let isBarHidden = (options.arguments?["isBarHidden"] as? Bool)  ?? false
        vc.isBarHidden = isBarHidden

        vc.configFlutter(name: options.pageName, uniqueId: options.uniqueId, params: options.arguments, opaque: options.opaque)

        let isPresent = (options.arguments?["isPresent"] as? Bool)  ?? false
        let isAnimated = (options.arguments?["isAnimated"] as? Bool) ?? true

        // 对这个页面设置结果
        resultTable[options.pageName] = options.onPageFinished;

        if (isPresent || !options.opaque) {
            self.navigationController?.present(vc, animated: isAnimated, completion: nil)
        } else {
            self.navigationController?.pushViewController(vc, animated: isAnimated)
        }
    }
    
    /// 退出页面
    func popRoute(_ options: FlutterBoostRouteOptions!) {
        if let vc = self.navigationController?.presentedViewController as? CustomFlutterController, vc.flutterVC.uniqueIDString() == options.uniqueId {// dismiss
            if vc.modalPresentationStyle == .overFullScreen {// 手动调用
                self.navigationController?.topViewController?.beginAppearanceTransition(true, animated: false)
                vc.dismiss(animated: true) {
                    self.navigationController?.topViewController?.endAppearanceTransition()
                }
            } else {
                vc.dismiss(animated: true, completion: nil)
            }
        } else {
            /*
               从Flutter页面返回到原生页面,如果Flutter页面并不是当前的顶级控制器,这时候就不能通过导航栏来返回到原生页面,而是应该移除掉容器Controller。
             */
            guard let viewControllers = self.navigationController?.viewControllers else { return }
                
            var containerToRemove: CustomFlutterController?
            for item in viewControllers.reversed() {
                if let container = item as? CustomFlutterController, container.flutterVC.uniqueIDString() == options.uniqueId {
                    containerToRemove = container
                    break
                }
            }
            
            if (containerToRemove == nil) {
                fatalError("uniqueId is wrong!!!")
            }
            
            if self.navigationController?.topViewController == containerToRemove {
                self.navigationController?.popViewController(animated: true)
            } else {
                containerToRemove?.removeFromParent()
            }
        }

         if let onPageFinshed = resultTable[options.pageName] {
             onPageFinshed(options.arguments)
             resultTable.removeValue(forKey: options.pageName)
         }
     }
}

4: 解决 Flutter 页面在销毁后内存不会被销毁的问题

Flutter 在内存方面最严重的两个点,一个是页面在销毁后内存不会销毁,另外一个是图片内存。关于页面销毁后内存不销毁的问题,主要原因在于引擎为了实现加载过的页面二次进入能达到秒加载,所以造成了内存不销毁。
FlutterBoost没有解决单引擎的通病——页面销毁内存不销毁。在 iOS 端上的解决方案是通过原生Controller,嵌入FBFlutterViewContainer.view,继而由原生端的 Controller实现 dealloc 时主动调用内存释放。
FBFlutterViewContainer继承自FlutterViewController,最终继承自ViewController

@interface FBFlutterViewContainer: FlutterViewController<FBFlutterContainer>\
@interface FlutterViewController: UIViewController

为了解决上面所说的问题,需要新建一个CustomFlutterController类,由其作为FBFlutterViewContainer的容器类,接管FBFlutterViewContainer相关操作。我们将FBFlutterViewContainer的View视图嵌入到这个新创建的类上,作为其子控制器而存在。

import UIKit
import flutter_boost

class CustomFlutterController: UIViewController {
    lazy var flutterVC: FBFlutterViewContainer = FBFlutterViewContainer()
    var isBarHidden : Bool?

    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
    }

    func setupUI() {
        addChild(flutterVC)
        view.addSubview(flutterVC.view)
        flutterVC.view.frame = view.bounds
        
        BoostDelegate.shared.navigationController = self.navigationController
    }
 }

这样我们就可以由新创建的容器Controller实现在dealloc时,主动调用FBFlutterViewContainer的内存释放。

 deinit {
   flutterVC.removeFromParent()
 }

为了能在BoostDelegate中调用configFlutter方法,需要在CustomFlutterController中新增一个桥接方法。

func configFlutter(name: String, uniqueId: String?, params: [AnyHashable : Any]?, opaque: Bool) {
     flutterVC.setName(name, uniqueId: uniqueId, params: params, opaque: opaque)
 }

至止,BoostDelegate单例类已配置完成。

六: 在AppDelegate中初始化FlutterBoost

AppDelegatedidFinishLaunchingWithOptions方法中创建代理,做初始化操作

 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        let boostDelegate = BoostDelegate.shared;
        FlutterBoost.instance().setup(application, delegate: boostDelegate) { engine in
            print("")
        }
        return true
    }

七: 创建iOS原生页面,初始化UI

1: 在控制器的viewDidLoad方法中设置当前的导航控制器为BoostDelegate用来push的导航栏。

override func viewDidLoad() {
    super.viewDidLoad()
    BoostDelegate.shared.navigationController = self.navigationController;
}

2: 创建一个按钮,用于跳转到Flutter页面

let nativePushFlutterButton = UIButton()
nativePushFlutterButton.setTitle("原生页面跳转Flutter页面", for: UIControl.State.normal)
nativePushFlutterButton.setTitleColor(.white, for: UIControl.State.normal)
nativePushFlutterButton.titleLabel?.textAlignment = .center
nativePushFlutterButton.titleLabel?.numberOfLines = 0;
nativePushFlutterButton.backgroundColor = .systemOrange
nativePushFlutterButton.addTarget(self, action: #selector(onClickNativePushFlutterButton), for: UIControl.Event.touchUpInside)
self.view.addSubview(nativePushFlutterButton)

八:实现从iOS原生页面跳转到Flutter页面

在点击按钮跳转到Flutter页面之前,我们需要对路由参数进行相关配置。

 @objc func onClickNativePushFlutterButton(){
        // 路由参数配置
        let options = FlutterBoostRouteOptions()
        
        // 路由的名称
        options.pageName = "homepage"
        
        // 传递的参数
        options.arguments = ["data" : textLabel.text as Any]
        // 页面是否透明
        options.opaque = true
        options.completion = {completion in
            print("打开Flutter页面的操作完成")
        }
        options.onPageFinished = { dict in
            print("Flutter 页面关闭返回到原生页面时,参数值:\(String(describing: dict))")
        }

        // 执行open()会调用"BoostDelegate"中的"pushFlutterRoute"方法
        FlutterBoost.instance().open(options)
    }

九:实现从Flutter页面跳转到原生页面

flutter页面跳转到原生页面,在flutter_module中的SimplePage类中已经说明:

  • 定义要跳转到原生的路由名称。
  • arguments:传递到原生页面的参数。
BoostNavigator.instance.push(
       "NewsVC",
       arguments: {"data": textStr},
 );

十:实现从Flutter页面返回到原生页面

调用flutter_boost提供的:BoostNavigator.instance.pop()方法即可。

onpressed: () {  Map<String, Object> result = {'data': '%E8%BF%94%E5%9B%9E%E9%A1%B5%E9%9D%A2%E6%97%B6%E4%BC%A0%E5%8F%82'};  BoostNavigator.instance.pop(result);}
参考文档:

flutter_boost:https://github.com/alibaba/flutter_boost
flutter_boost和iOS原生的通信:https://www.jianshu.com/p/086d0ad44261

Demo下载:

https://github.com/zhwIdea/iOSAndFlutterExample/tree/main

如果此文档对你有用,请点赞关注支持一下,谢谢。

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

推荐阅读更多精彩内容