Flutter 開發(fā)之 Native 集成 Flutter 混合開發(fā)

本文先介紹一下現(xiàn)有工程如何集成 Flutter 實現(xiàn)混合開發(fā)荒给,以及混合項目如何打包疏虫,再探索下如何降低原生和 Flutter 之間的依賴屏鳍,使 Flutter 開發(fā)對原生開發(fā)的影響盡量降低栏饮,以及一些我在嘗試中遇到的問題及解決吊说。

介紹 Flutter

Flutter 是 Google 發(fā)布的一個用于創(chuàng)建跨平臺嘁信、高性能移動應用的框架。Flutter 和 QT mobile 一樣疏叨,都沒有使用原生控件潘靖,相反都實現(xiàn)了一個自繪引擎,使用自身的布局蚤蔓、繪制系統(tǒng)卦溢。開發(fā)者可以通過 Dart 語言開發(fā) App,一套代碼同時運行在 iOS 和 Android平臺秀又。Flutter 提供了豐富的組件单寂、接口,開發(fā)者可以很快地為 Flutter 添加 Native 擴展吐辙。

前提工作

開發(fā)者需要安裝好 Flutter 的環(huán)境宣决,執(zhí)行flutter doctor -v驗證。

flutter_doctor_v

驗證通過后即可開始集成 Flutter昏苏。

現(xiàn)有原生工程集成 Flutter

最官方的教程應該是Add Flutter to existing apps了尊沸,按照教程如下一步步操作:

1.創(chuàng)建 flutter module

使用flutter create xxx指令創(chuàng)建的 Flutter 項目包括用于 Flutter/Dart 代碼的非常簡單的工程。你可以修改 main.dart 的內容贤惯,以滿足你的需要洼专,并在此基礎上進行構建。

假設你有一個已經存在 iOS 工程(以 flutterHybridDemo 為例)在some/path/flutterHybridDemo孵构,那么你新建的 flutter_module 和 iOS 工程應該在同一目錄下(即都在 path 下)屁商。

$ cd some/path/
$ flutter create -t module flutter_module
flutter_module目錄結構

通過shift+command+.顯示/隱藏隱藏文件夾

  • lib/main.dart:存放的是 Dart 語言編寫的代碼,這里是核心代碼颈墅;
  • pubspec.yaml:配置依賴項的文件蜡镶,比如配置遠程 pub 倉庫的依賴庫,或者指定本地資源(圖片恤筛、字體官还、音頻、視頻等)叹俏;
  • .ios/:iOS 部分代碼妻枕;
  • .android/:Android 部分代碼僻族;
  • build/:存儲 iOS 和 Android 構建文件粘驰;
  • test/:測試代碼屡谐。

2.將 flutter module 作為依賴添加到工程

假設文件夾結構如下:

some/path/
  flutter_module/
    lib/main.dart
    .ios/
    ...
  flutterHybridDemo/
    flutterHybridDemo.xcodeproj
    flutterHybridDemo/
        AppDelegate.h
        AppDelegate.m
        ...

集成 Flutter 框架需要使用CocoaPods,這是因為 Flutter 框架還需要對 flutter_module 中可能包含的任何 Flutter 插件可用蝌数。

- 如果需要愕掏,請參考cocoapods.org了解如何在您的電腦上安裝 CocoaPods。

創(chuàng)建 Podfile:

$ cd some/path/flutterHybridDemo
$ pod init

此時工程中會出現(xiàn)一個 Podfile 文件顶伞,添加項目依賴的第三方庫就在這個文件中配置饵撑,編輯 Podfile 文件添加最后兩行代碼:

# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'TestOne' do
  # Uncomment the next line if you're using Swift or would like to use dynamic frameworks
  # use_frameworks!

  # Pods for TestOne

  target 'TestOneTests' do
    inherit! :search_paths
    # Pods for testing
  end

  target 'TestOneUITests' do
    inherit! :search_paths
    # Pods for testing
  end

end

#新添加的代碼
flutter_application_path = '../flutter_module'
eval(File.read(File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')), binding)
- 如果你的工程(flutterHybridDemo)已經在使用 Cocoapods ,你只需要做以下幾件事來整合你的 flutter_module 應用程序:

(1)添加如下內容到 Podfile:

flutter_application_path = '../flutter_module'
eval(File.read(File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')), binding)

(2)執(zhí)行pod install

當你在some/path/flutter_module/pubspec.yaml中修改 Flutter 插件依賴時唆貌,需要先執(zhí)行flutter packages get通過 podhelper.rb 腳本來刷新插件列表滑潘,然后再從some/path/flutterHybridDemo執(zhí)行一次pod install

podhelper.rb 腳本將確保你的插件和 Flutter 框架被添加到你的工程中锨咙,以及 bitcode 被禁用语卤。

(3)禁用 bitcode

因為 Flutter 現(xiàn)在不支持 bitcode。需要設置 Build Settings->Build Options->Enable Bitcode 為 NO酪刀。


bitcode 禁用

3.為編譯 Dart 代碼配置 build phase

打開 iOS 工程粹舵,選中項目的 Build Phases 選項,點擊左上角+號按鈕骂倘,選擇 New Run Script Phase眼滤。


配置 build phase

將下面的 shell 腳本添加到輸入框中:

"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed

最后,確保 Run Script 這一行在 "Target dependencies" 或者 "Check Pods Manifest.lock" 后面历涝。


配置 build phase

至此诅需,你可以編譯一下工程確保無誤:?B

4.在 iOS 工程中使用 FlutterViewController

首先聲明你的 AppDelegate 是 FlutterAppDelegate 的子類荧库。然后定義一個 FlutterEngine 屬性诱担,它可以幫助你注冊一個沒有 FlutterViewController 實例的插件。

在 AppDelegate.h:

#import <UIKit/UIKit.h>
#import <Flutter/Flutter.h>

@interface AppDelegate : FlutterAppDelegate
@property (nonatomic,strong) FlutterEngine *flutterEngine;
@end

在AppDelegate.m电爹,修改didFinishLaunchingWithOptions方法如下:

#import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h> // Only if you have Flutter Plugins
#include "AppDelegate.h"

@implementation AppDelegate

// This override can be omitted if you do not have any Flutter Plugins.
- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  self.flutterEngine = [[FlutterEngine alloc] initWithName:@"io.flutter" project:nil];
  [self.flutterEngine runWithEntrypoint:nil];
  [GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

@end

如果 AppDelegate 已經繼承于別的類的時候蔫仙,可以通過讓你的 delegate 實現(xiàn)FlutterAppLifeCycleProvider協(xié)議:

#import <Flutter/Flutter.h>
#import <UIKit/UIKit.h>
#import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h> // Only if you have Flutter Plugins

@interface AppDelegate : UIResponder <UIApplicationDelegate, FlutterAppLifeCycleProvider>
@property (strong, nonatomic) UIWindow *window;
@end

然后生命周期方法應該由 FlutterPluginAppLifeCycleDelegate 來代理:

@implementation AppDelegate
{
    FlutterPluginAppLifeCycleDelegate *_lifeCycleDelegate;
}

- (instancetype)init {
    if (self = [super init]) {
        _lifeCycleDelegate = [[FlutterPluginAppLifeCycleDelegate alloc] init];
    }
    return self;
}

- (BOOL)application:(UIApplication*)application
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
    [GeneratedPluginRegistrant registerWithRegistry:self]; // Only if you are using Flutter plugins.
    return [_lifeCycleDelegate application:application didFinishLaunchingWithOptions:launchOptions];
}

// Returns the key window's rootViewController, if it's a FlutterViewController.
// Otherwise, returns nil.
- (FlutterViewController*)rootFlutterViewController {
    UIViewController* viewController = [UIApplication sharedApplication].keyWindow.rootViewController;
    if ([viewController isKindOfClass:[FlutterViewController class]]) {
        return (FlutterViewController*)viewController;
    }
    return nil;
}

- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
    [super touchesBegan:touches withEvent:event];

    // Pass status bar taps to key window Flutter rootViewController.
    if (self.rootFlutterViewController != nil) {
        [self.rootFlutterViewController handleStatusBarTouches:event];
    }
}

- (void)applicationDidEnterBackground:(UIApplication*)application {
    [_lifeCycleDelegate applicationDidEnterBackground:application];
}

- (void)applicationWillEnterForeground:(UIApplication*)application {
    [_lifeCycleDelegate applicationWillEnterForeground:application];
}

- (void)applicationWillResignActive:(UIApplication*)application {
    [_lifeCycleDelegate applicationWillResignActive:application];
}

- (void)applicationDidBecomeActive:(UIApplication*)application {
    [_lifeCycleDelegate applicationDidBecomeActive:application];
}

- (void)applicationWillTerminate:(UIApplication*)application {
    [_lifeCycleDelegate applicationWillTerminate:application];
}

- (void)application:(UIApplication*)application
didRegisterUserNotificationSettings:(UIUserNotificationSettings*)notificationSettings {
    [_lifeCycleDelegate application:application
didRegisterUserNotificationSettings:notificationSettings];
}

- (void)application:(UIApplication*)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken {
    [_lifeCycleDelegate application:application
didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}

- (void)application:(UIApplication*)application
didReceiveRemoteNotification:(NSDictionary*)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
    [_lifeCycleDelegate application:application
       didReceiveRemoteNotification:userInfo
             fetchCompletionHandler:completionHandler];
}

- (BOOL)application:(UIApplication*)application
            openURL:(NSURL*)url
            options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options {
    return [_lifeCycleDelegate application:application openURL:url options:options];
}

- (BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url {
    return [_lifeCycleDelegate application:application handleOpenURL:url];
}

- (BOOL)application:(UIApplication*)application
            openURL:(NSURL*)url
  sourceApplication:(NSString*)sourceApplication
         annotation:(id)annotation {
    return [_lifeCycleDelegate application:application
                                   openURL:url
                         sourceApplication:sourceApplication
                                annotation:annotation];
}

- (void)application:(UIApplication*)application
performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
  completionHandler:(void (^)(BOOL succeeded))completionHandler NS_AVAILABLE_IOS(9_0) {
    [_lifeCycleDelegate application:application
       performActionForShortcutItem:shortcutItem
                  completionHandler:completionHandler];
}

- (void)application:(UIApplication*)application
handleEventsForBackgroundURLSession:(nonnull NSString*)identifier
  completionHandler:(nonnull void (^)(void))completionHandler {
    [_lifeCycleDelegate application:application
handleEventsForBackgroundURLSession:identifier
                  completionHandler:completionHandler];
}

- (void)application:(UIApplication*)application
performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
    [_lifeCycleDelegate application:application performFetchWithCompletionHandler:completionHandler];
}

- (void)addApplicationLifeCycleDelegate:(NSObject<FlutterPlugin>*)delegate {
    [_lifeCycleDelegate addDelegate:delegate];
}
@end

在 ViewController 中添加跳轉到 FlutterViewController 的測試代碼即可:

#import "ViewController.h"
#import <Flutter/Flutter.h>
#import "AppDelegate.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    [button addTarget:self
               action:@selector(handleButtonAction)
     forControlEvents:UIControlEventTouchUpInside];
    [button setTitle:@"Jump to flutterViewController" forState:UIControlStateNormal];
    [button setBackgroundColor:[UIColor grayColor]];
    button.frame = CGRectMake(80.0, 210.0, 300.0, 40.0);
    button.center = self.view.center;
    [self.view addSubview:button];
}

- (void)handleButtonAction {
    AppDelegate *delegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
    FlutterEngine *flutterEngine = delegate.flutterEngine;
    
    FlutterViewController *flutterVC = [[FlutterViewController alloc]initWithEngine:flutterEngine nibName:nil bundle:nil];
    [self presentViewController:flutterVC animated:YES completion:nil];
}
@end

5.使用熱重載的方式調試 Dart 代碼

熱重載指的是不用重新啟動就看到修改后的效果,類似 web 網頁開發(fā)時保存就看到效果的方式丐箩。
進入 flutter module摇邦,在終端執(zhí)行命令:

$ cd some/path/flutter_module
$ flutter run
flutter run

并且你能在控制臺中看下如下內容:

??  To hot reload changes while running, press "r". To hot restart (and rebuild state), press "R".
An Observatory debugger and profiler on iPhone X is available at: http://127.0.0.1:54741/
For a more detailed help message, press "h". To quit, press "q".

你可以在 flutter_module 中編輯 Dart code,然后在終端輸入 r 來使用熱重載屎勘。你也可以在瀏覽器中輸入上面的 URL 來查看斷點施籍、分析內存和其他的調試任務。

集成 Flutter 后工程打包

1. flutter build ios

執(zhí)行flutter build ios以創(chuàng)建 release 版本(flutter build 默認為--release概漱,如需創(chuàng)建 debug 版本執(zhí)行flutter build ios —debug)丑慎。

2.成功后修改 Xcode 為 release 模式配置

3.最后選擇 Product > Archive 以生成構建版本即可

archive 成功

混合工程改造優(yōu)化

Flutter 的工程結構比較特殊,由 Flutter 目錄、Native 工程的目錄(即 iOS 和 Android 兩個目錄)組成竿裂。默認情況下玉吁,引入了 Flutter 的 Native 工程無法脫離父目錄進行獨立構建和運行,因為它會反向依賴于 Flutter 相關的庫和資源腻异。

實際上进副,在真實的開發(fā)情況下,開發(fā)者很少會創(chuàng)建一個完全 Flutter 的工程重寫項目悔常,更多的情況是原生工程集成 Flutter影斑。

1.問題

這樣就帶來了一系列問題:

(1)構建打包問題:引入 Flutter 后,Native 工程因對其有了依賴和耦合机打,從而無法獨立編譯構建矫户。在 Flutter 環(huán)境下,工程的構建是從 Flutter 的構建命令開始残邀,執(zhí)行過程中包含了 Native 工程的構建吏垮,開發(fā)者要配置完整的 Flutter 運行環(huán)境才能走通整個流程

(2)混合編譯帶來的開發(fā)效率的降低:在轉型 Flutter 的過程中必然有許多業(yè)務仍使用 Native 進行開發(fā)罐旗,工程結構的改動會使開發(fā)無法在純 Native 環(huán)境下進行膳汪,而適配到 Flutter 工程結構對純 Native 開發(fā)來說又會造成不必要的構建步驟,造成開發(fā)效率的降低九秀。

2.目標

希望能將 Flutter 依賴抽取出來遗嗽,作為一個 Flutter 依賴庫,供純 Native 工程引用鼓蜒,無需配置完整的 Flutter 環(huán)境痹换。

3.Flutter 產物

iOS 工程對 Flutter 有如下依賴:

  • Flutter.framework:Flutter 庫和引擎

  • App.framework:dart 業(yè)務源碼相關文件

  • flutter_assets:Flutter依賴的靜態(tài)資源,如字體都弹,圖片等

  • Flutter Plugin:編譯出來的各種 plugin 的 framework

把以上依賴的編譯結果抽取出來娇豫,即是 Flutter 相關代碼的最終產物。

那么我們只需要將這些打包成一個 SDK 依賴的形式提供給 Native 工程畅厢,就可以解除 Native 工程對 Flutter 工程的直接依賴冯痢。

產物的產生:

對 flutter 工程執(zhí)行 flutter build 命令后,生成在.ios/Flutter目錄下框杜,直接手動拷貝 framework 到主工程即可浦楣。

注意事項:

framework 選擇 Create groups 加入文件夾,flutter_assets 選擇 Create folder references 加入文件夾咪辱。

add_in_project

加入完成后的結構:

thirdFramework

framework 加入后振劳,記住一定要確認 framework 已在 TARGETS -> General -> Embedded Binaries 中添加完成。

embedded_binaires

最后改造 APPDelegate 即可:

#import <UIKit/UIKit.h>
#import <Flutter/Flutter.h>

@interface AppDelegate : FlutterAppDelegate <UIApplicationDelegate>

@property (strong, nonatomic) FlutterEngine *flutterEngine;

@end
#import "AppDelegate.h"

@interface AppDelegate ()
@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    self.flutterEngine = [[FlutterEngine alloc]initWithName:@"io.flutter" project:nil];
    [self.flutterEngine runWithEntrypoint:nil];
    return YES;
}

4. 優(yōu)化

為了更方便管理 framework油狂,可以將這些文件上傳到遠程倉庫历恐,通過 CocoaPods 導入寸癌,Native 項目只需及時更新 pod 依賴即可。

我遇到過的一些問題及解決

1.在 Android Studio 上跑設備

More than one device connected; please specify a device with the '-d <deviceId>' flag, or use '-d all' to act on all devices.

選擇模擬器

提示你當前有兩個模擬器設備弱贼,跑設備的時候要選擇運行在哪個設備上蒸苇,flutter run后面拼接上“-d <deviceId>”,deviceId 是第二列的內容哮洽。

flutter run -d emulator-5554
flutter run -d C517D2D4-EAFA-42CA-B260-A18FA0ABFF60

電腦連著真機也同理填渠,改成真機的 deviceId 即可弦聂。

2.flutter build ios 報錯

build 時可能遇到的錯誤:

It appears that your application still contains the default signing identifier.Try replacing 'com.example' with your signing id in Xcode:

open ios/Runner.xcworkspace

build 時可能遇到的錯誤

解決方法:

修改some/flutter_module/.ios/下 Runner 工程的 Bundle Identifier 和原生工程的一致鸟辅,再次運行flutter build ios即可。

3.開發(fā)時打包產物編譯失敗

當你用flutter build ios的產物添加到原生工程中莺葫,跳轉到 Flutter 界面會黑屏并報出如下錯誤:

flutter_build_questions

Failed to find snapshot: …/Library/Developer/CoreSimulator/Devices/…/data/Containers/Bundle/Application/…/FlutterMixDemo.app/Frameworks/App.framework/flutter_assets/kernel_blob.bin

如何解決:

調試模式下用flutter build ios —debug的產物匪凉,再次拖入工程即可。

原因:

首先我們對比下捺檬,執(zhí)行flutter build ios和執(zhí)行flutter build ios --debug.ios/Flutter/App.framework/flutter_assets的文件內容:

flutter_build_ios.png

flutter_build_ios_debug.png

可以發(fā)現(xiàn)再层,差別是在于三個文件:isolate_snapshot_data、kernel_blob.bin堡纬、vm_snapshot_data聂受。

這里涉及 Flutter 的編譯模式知識,具體可以參閱Flutter 的兩種編譯模式烤镐。

Flutter 開發(fā)階段的編譯模式:使用了 Kernel Snapshot 模式編譯蛋济,打包產物中,可以發(fā)現(xiàn)幾樣東西:

  • isolate_snapshot_data:用于加速 isolate 啟動炮叶,業(yè)務無關代碼碗旅,固定,僅和 flutter engine 版本有關镜悉;

  • platform.dill:和 Dart VM 相關的 kernel 代碼祟辟,僅和 Dart 版本以及 engine 編譯版本有關。固定侣肄,業(yè)務無關代碼旧困;

  • vm_snapshot_data:用于加速 Dart VM 啟動的產物,業(yè)務無關代碼稼锅,僅和 flutter engine 版本有關叮喳;

  • kernel_blob.bin:業(yè)務代碼產物 。

Flutter 生產階段的編譯模式:選擇了 AOT 打包缰贝。

4.集成后 Native 工程報錯

Shell Script Invocation Error

line 2:/packages/flutter_tools/bin/xcode_backend.sh: No such file or directory

集成后 Native 工程報錯

解決方法:

修改 TARGETS -> Build Setting -> FLUTTER_ROOT 為電腦安裝的 Flutter 環(huán)境的路徑即可馍悟。


集成后 Native 工程報錯

5.如何在 iOS 工程 Debug 模式下使用 release 模式的 flutter

只需要將 Generated.xcconfig 中的 FLUTTER_BUILD_MODE 修改為 release,F(xiàn)LUTTER_FRAMEWORK_DIR 修改為 release 對應的路徑即可剩晴。

其他

1.說明:

本文僅供用于學習參考锣咒,請勿用于商業(yè)用途侵状。如需轉載,請標明出處毅整,謝謝合作趣兄。

本文系參考網絡公開 Flutter 學習資料以及個人學習體會總結所得,部分內容為網絡公開學習資料悼嫉,如有侵權請聯(lián)系作者刪除艇潭。

2.參考資料:

Flutter 中文網:https://flutterchina.club

咸魚技術-flutter:https://www.yuque.com/xytech/flutter

iOS Native混編Flutter交互實踐:https://juejin.im/post/5bb033515188255c5e66f500#heading-3

Flutter混編之路——開發(fā)集成(iOS篇):http://www.reibang.com/p/48a9083ebe89

作者簡介

就職于甜橙金融(翼支付)信息技術部,負責 iOS 客戶端開發(fā)戏蔑。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末蹋凝,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子总棵,更是在濱河造成了極大的恐慌鳍寂,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件情龄,死亡現(xiàn)場離奇詭異迄汛,居然都是意外死亡,警方通過查閱死者的電腦和手機骤视,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門鞍爱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人专酗,你說我怎么就攤上這事睹逃。” “怎么了笼裳?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵唯卖,是天一觀的道長。 經常有香客問我躬柬,道長拜轨,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任允青,我火速辦了婚禮喜德,結果婚禮上矾踱,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好巾腕,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布仲闽。 她就那樣靜靜地躺著腋腮,像睡著了一般窍侧。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上瓷蛙,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天悼瓮,我揣著相機與錄音戈毒,去河邊找鬼。 笑死横堡,一個胖子當著我的面吹牛埋市,可吹牛的內容都是我干的。 我是一名探鬼主播命贴,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼道宅,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了胸蛛?” 一聲冷哼從身側響起污茵,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎胚泌,沒想到半個月后省咨,有當地人在樹林里發(fā)現(xiàn)了一具尸體肃弟,經...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡玷室,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了笤受。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片穷缤。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖箩兽,靈堂內的尸體忽然破棺而出津肛,到底是詐尸還是另有隱情,我是刑警寧澤汗贫,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布身坐,位于F島的核電站,受9級特大地震影響落包,放射性物質發(fā)生泄漏部蛇。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一咐蝇、第九天 我趴在偏房一處隱蔽的房頂上張望涯鲁。 院中可真熱鬧,春花似錦有序、人聲如沸抹腿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽警绩。三九已至,卻和暖如春盅称,著一層夾襖步出監(jiān)牢的瞬間肩祥,已是汗流浹背僚匆。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留搭幻,地道東北人咧擂。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像檀蹋,于是被迫代替她去往敵國和親松申。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內容