Flutter 與 Native 混合開發(fā)

前言

本文主要介紹 Flutter 與 Android 的混合開發(fā),關(guān)于 Flutter 與 iOS 的混合開發(fā)后面再詳細(xì)介紹饿序。

一团赏、Flutter 和 Android 混合開發(fā)

開發(fā)步驟:

1. 創(chuàng)建 Flutter module

在做混合開發(fā)之前首先需要創(chuàng)建一個 Flutter module府框。

假如你的 Android項(xiàng)目路徑是這樣的:xxx/flutter_hybrid/Android項(xiàng)目,那么 flutter module 就要創(chuàng)建在 Android項(xiàng)目的上一級目錄下:xxx/flutter_hybrid/。

$ cd xxx/flutter_hybrid/
$ flutter create -t module flutter_module

flutter module 創(chuàng)建成功之后迅皇,看一下flutter_module 的文件結(jié)構(gòu)从铲,

它里面包含兩個隱藏的文件 .android 與 .ios,這兩個文件是 flutter_module 的宿主工程:

  • .android:flutter_module 的 Android 宿主工程
  • .ios:flutter_module 的 iOS 宿主工程
  • lib:flutter_module 的 Dart 部分的代碼
  • pubspec.yaml:flutter_module 的項(xiàng)目依賴配置文件

因?yàn)樗拗鞴こ痰拇嬖谛牵覀冞@個 flutter_module 在不加額外的配置的情況下是可以獨(dú)立運(yùn)行的珠插,通過安裝了 Flutter 與 Dart 插件的AndroidStudio 打開這個 flutter_module 項(xiàng)目,就可以直接運(yùn)行颖对。

2. 在Android項(xiàng)目中添加 Flutter module 依賴

首先在 Android項(xiàng)目根目錄下的 settings.gradle 文件中添加下面的代碼捻撑,構(gòu)建 flutter 工程 module:

include ':app'
setBinding(new Binding([gradle: this]))
evaluate(new File(
  settingsDir.parentFile,
  'flutter_module/.android/include_flutter.groovy'
))

然后在 Android項(xiàng)目主工程app下的 build.gradle 中添加 flutter_module 的依賴:

dependencies {

    ...

    implementation project(':flutter')
}

需要注意兩點(diǎn):
(1)這里需要的 minSdkVersion 最小為16。
(2)Android項(xiàng)目編譯的時候需要設(shè)置 Java8

補(bǔ)充:怎樣設(shè)置Java8缤底,在主工程 app下的build.gradle中添加如下代碼:

android {
    
    ...

    compileOptions{
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

3. 在 Android 中調(diào)用 Flutter module

在 Android 中調(diào)用 Flutter模塊 有兩種方式:

  • 使用 Flutter.createView API的方式
  • 使用 FlutterFragment 的方式

(1)使用 Flutter.createView API 的方式

MainActivity.java 代碼:

findViewById(R.id.test).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                View flutterView = Flutter.createView(
                        MainActivity.this,
                        getLifecycle(),
                        "route2"
                );
                FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(600, 800);
                layoutParams.gravity = Gravity.CENTER;
                addContentView(flutterView, layoutParams);
            }
        });

(2)使用 FlutterFragment 的方式

xml布局中代碼:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/test"
        android:layout_width="wrap_content"
        android:layout_height="50dp"
        android:background="@color/colorAccent"
        android:gravity="center"
        android:text="Hello World!" />

    <FrameLayout
        android:id="@+id/someContainer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

MainActivity.java 代碼:

findViewById(R.id.test).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                FragmentTransaction tx = getSupportFragmentManager().beginTransaction();
                tx.replace(R.id.someContainer, Flutter.createFragment("route1"));
                tx.commit();
            }
        });

上面代碼中的“route1”用來告訴 Dart 代碼在 Flutter 視圖中顯示哪個小部件顾患。Flutter 模塊項(xiàng)目的 lib/main.dart文件需要通過window.defaultRouteName 來獲取 Native指定要顯示的路由名,以確定要創(chuàng)建哪個窗口小部件并傳遞給runApp,
main.dart 代碼

void main() => runApp(_widgetForRoute(window.defaultRouteName));

Widget _widgetForRoute(String route){
  switch(route){
    case 'route1':
      return SomeWidget(...);
    case 'route2':
      return SomeOtherWidget(...);
    default:
      return MyApp(initParams: window.defaultRouteName,);
  }
}

“route1”也可以換成需要傳遞的數(shù)據(jù)个唧。

4. 調(diào)試與發(fā)布

前面在介紹 Flutter 開發(fā)的時候江解,我們知道它帶有熱重啟/重新加載的功能。但是在本文混合開發(fā)的示例中會發(fā)現(xiàn)徙歼,在 Android 項(xiàng)目中集成了 Flutter 項(xiàng)目犁河,F(xiàn)lutter 的熱重啟/重新加載功能好像失效了,我們通過下面的方法來啟動混合開發(fā)中的熱重啟/重新加載:

  • 連接一個設(shè)備到電腦上魄梯;
  • 關(guān)閉我們的APP桨螺,然后運(yùn)行 flutter attach:

(1)調(diào)試 Dart 代碼

混合開發(fā)模式下高效調(diào)試代碼的方式:

  • 關(guān)閉APP
  • 點(diǎn)擊 AndroidStudio 的 Flutter Attach 按鈕
  • 啟動APP

(2)發(fā)布

第一步:生成 Android 簽名證書
簽名 APK 需要一個證書用于為 APP 簽名,生成簽名證書可以用 AndroidStudio 可視化的方式生成酿秸,也可以使用命令行的方式生成灭翔,這里就不在詳細(xì)介紹了。

第二步:設(shè)置 gradle 變量
① 將簽名證書放到 android/app 目錄下辣苏。
② 編輯~/.gradle/gradle.properties 或 ../android/gradle.properties肝箱,加入一下代碼:

MYAPP_RELEASE_STORE_FILE = your keystone filename
MYAPP_RELEASE_KEY_ALIAS = your keystone alias
MYAPP_RELEASE_STORE_PASSWORD = *****
MYAPP_RELEASE_KEY_PASSWORD = *****

③ 在 gradle 配置文件中添加簽名配置
編輯 android/app/build.gradle 文件添加如下代碼:

...
android{
    ...
    defaultConfig{...}
    signingConfigs{
        release{
            storeFile file(MYAPP_RELEASE_STORE_FILE)
            storePassword MYAPP_RELEASE_STORE_PASSWORD
            keyAlias MYAPP_RELEASE_KEY_ALIAS
            keyPassword MYAPP_RELEASE_KEY_PASSWORD
        }
    }

    buildTypes{
        release{
            ...
            signingConfig signingConfigs.release
        }
    }
}
...

④ 簽名打包APK

二、Flutter 通信機(jī)制 & Dart 端講解

Flutter 和 Native 的通信是通過 PlatformChannel 來完成的稀蟋。



消息使用 PlatformChannel 在客戶端和主機(jī)之間傳遞煌张。

PlatformChannel 的三種不同類型的種類:

  • BasicMessageChannel:用于傳遞字符串和半結(jié)構(gòu)化的信息,持續(xù)通信退客,收到消息后可以回復(fù)本次消息骏融。
  • MethodChannel:用于傳遞方法調(diào)用(method invocation)一次性通信,如 Flutter 調(diào)用 Native 拍照。
  • EventChannel:用于數(shù)據(jù)流(event streams)的通信绎谦,持續(xù)通信管闷,收到消息后無法回復(fù)此次消息,通過長用于Native向Dart的通信窃肠,如:手機(jī)電量變化包个、網(wǎng)絡(luò)連接變化、陀螺儀冤留、傳感器等碧囊。

這三種類型的 PlatformChannel 都是全雙工通信,即A<=>B纤怒,F(xiàn)lutter 可以主動發(fā)送消息給 platform 端糯而,并且 platform 接收到消息后可以做出回應(yīng)。同樣泊窘,platform 端可以主動發(fā)送消息給 Dart 端熄驼,F(xiàn)lutter 端接收消息后返回給 platform 端。

  1. BasicMessageChannel
    Flutter端:

(1) 構(gòu)造方法原型

const BasicMessageChannel(this.name,this.codec);

① String name:Channel 的名字烘豹,要和 Native端保持一致瓜贾;
② MessageCodec<T> codec:消息的編解碼器,要和 Native端保持一致;

(2) setMessageHandler 方法原型

void setMessageHandler(Future<T> handler(T message))

① Future<T> handler(T message):消息處理器携悯,配合BinaryMessager 完成消息的處理祭芦;
在創(chuàng)建好BasicMessageChannel 后,如果要讓其接收 Native 發(fā)來的消息憔鬼,則需要調(diào)用它的 setMessageHandler 方法為其設(shè)置一個消息處理器龟劲。

(3)send 方法原型

Future<T> send(T message)
  • T message:要傳遞給Native的具體信息;
  • Future<T>:消息發(fā)出去后轴或,收到Native回復(fù)的回調(diào)函數(shù)昌跌;
    在創(chuàng)建好 BasicMessageChannel 后,如果要向 Native 發(fā)送消息侮叮,可以調(diào)用它的 send 方法向 Native 傳遞數(shù)據(jù)避矢。
import 'package:flutter/services.dart';
...
static const BasicMessageChannel _basicMessageChannel = 
      const BasicMessageChannel('BasicMessageChannelPlugin', StringCodec());

...

//使用BasicMessageChannel接收來自Native的消息悼瘾,并向Native回復(fù)
_basicMessageChannel.setMessageHandler((String message) => Future<String>((){
  setState((){
    showMessage = message;
  });
  return '收到Native的消息:'+ messgae;
}));

//使用BasicMessageChannel向Native發(fā)送消息囊榜,并接收到Native的回復(fù)
String response;
try{
  response = await _basicMessageChannel.send(value);
} on PlatformException catch (e){
  print(e);
}
...
  1. MethodChannel
    Flutter端:

(1)構(gòu)造方法原型

const MethodChannel(this.name,[this.codec = const StandardMethodCodec()])
  • String name:Channel 的名字,要和 Native端保持一致亥宿;
  • MethodCodec codec:消息的編解碼器卸勺,默認(rèn)是 StandardMethodCodec,要和 Native端保持一致烫扼;

(2)invokeMethod 方法原型

Future<T> invokeMethod<T>(String method,[dynamic arguments])
  • String method:要調(diào)用 Native的方法名曙求;
  • [dynamic arguments]:調(diào)用 Native方法傳遞的參數(shù),可不傳;
import 'package:flutter/services.dart';
...
static const MethodChannel _methodChannelPlugin = 
    const MethodChannel('MethodChannelPlugin');
...
String response;
try{
  response = await _methodChannelPlugin.invokeMethod('send',value);
}on PlatformException catch (e){
print(e);
}
...
  1. EventChannel
    Flutter端:

(1)構(gòu)造方法原型

const EventChannel(this.name,[this.codec = const StandardMethodCodec()]);
  • String name:Channel的名字悟狱,要和 Native端保持一致静浴;
  • MethodCodec codec:消息的編解碼器,默認(rèn)是 StandardMethodCodec挤渐,要和 Native端保持一致苹享;

(2)receiveBroadcastStream 方法原型

Stream<dynamic> receiveBroadcastStream([dynamic arguments])
  • dynamic arguments:監(jiān)聽事件時向 Native傳遞的數(shù)據(jù);
    初始化一個廣播流用于從 channel 中接收數(shù)據(jù)浴麻,它返回一個 Stream 接下來需要調(diào)用 Stream的 listen 方法來完成注冊得问,另外需要在頁面銷毀時調(diào)用 Stream 的 cancel 方法來取消監(jiān)聽;
import 'package:flutter/services.dart';

...

static const EventChannel _eventChannelPlugin = 
    EventChannel('EventChannelPlugin');

StreamSubscription _streamSubscription;

@override
void initState(){
  _streamSubscription = _eventChannelPlugin
      .receiveBroadcastStream()
      .listen(_onToDart,onError: _onToDartError);
  super.initState();
}

@override
void dispose(){
  if(_streamSubscription != null){
    _streamSubscription.cancel();
    _streamSubscription = null;
  }
  super.dispose();
}

void _onToDart(message){
  setState((){
    showMessage = message;
  });
}

void _onToDartError(error){
  print(error);
}

三软免、Flutter 與 Android 通信

  1. BasicMessageChannel用法
    Android端:

(1)構(gòu)造方法原型

BasicMessageChannel(BinaryMessenger messenger,String name,MessageCodec<T> codec)
  • BinaryMessenger messenger:消息信使宫纬,是消息的發(fā)送與接收的工具;
  • String name:Channel的名字膏萧,也是其唯一標(biāo)識符漓骚;
  • MessageCodec<T> codec:消息的編解碼器,它有幾種不同類型的實(shí)現(xiàn):
    BinaryCodec:最為簡單的一種 Codec榛泛,因?yàn)槠浞祷刂殿愋秃腿雲(yún)⒌念愋拖嗤暇常鶠槎M(jìn)制格式(Android 中為 ByteBuffer)。實(shí)際上挟鸠,BinaryCodec 在編解碼過程中什么都沒做叉信,只是原封不動將二進(jìn)制數(shù)據(jù)消息返回而已∷蚁#或許你會因此覺得 BinaryCodec 沒有意義硼身,但是在某些情況下它非常有用,比如使用 BinaryCodec 可以使傳遞內(nèi)存數(shù)據(jù)塊時在編解碼階段免于內(nèi)存拷貝覆享;
    StringCodec:用于字符串與二進(jìn)制數(shù)據(jù)之間的編解碼佳遂,其編碼格式為 UTF-8;
    JSONMessageCodec:用于基礎(chǔ)數(shù)據(jù)與二進(jìn)制數(shù)據(jù)之間的編解碼撒顿,其支持基礎(chǔ)數(shù)據(jù)類型以及列表丑罪、字典。
    StandardMessageCodec:是 BasicMessageChannel 的默認(rèn)編解碼器凤壁,其支持基礎(chǔ)數(shù)據(jù)類型吩屹、二進(jìn)制數(shù)據(jù)、列表拧抖、字典煤搜;

(2)setMessageHandler 方法原型

void setMessageHandler(BasicMessageChannel.MessageHandler<T> handler)
  • BasicMessageChannel.MessageHandler<T> handler:消息處理器,配合 BinaryMessenger 完成消息的處理唧席;

在創(chuàng)建好 BasicMessageChannel 后擦盾,如果要讓其接收 Flutter 發(fā)來的消息嘲驾,則需要調(diào)用它的setMessageHandler 方法為其設(shè)置一個消息處理器。

(3)BasicMessageChannel.MessageHandler 原型

public interface MessageHandler<T>{
    void onMessage(T var1,BasicMessageChannel.Reply<T> var2);
}
  • onMessage(T var1,BasicMessageChannel.Reply<T> var2):用于接收消息迹卢,var1 是消息內(nèi)容辽故,var2 是回復(fù)此消息的回調(diào)函數(shù);

(4)send方法原型

void send(T message)
void send(T message,BasicMessageChannel.Reply<T> callback)
  • T message:要傳遞給 Flutter 的具體信息腐碱;
  • BasicMessageChannel.Reply<T> callback:消息發(fā)出去后榕暇,收到 Flutter 的回復(fù)的回調(diào)函數(shù);

在創(chuàng)建好BasicMessageChannel后喻杈,如果要向 Flutter 發(fā)送消息彤枢,可以調(diào)用它的send方法向 Flutter 傳遞數(shù)據(jù)。

public class BasicMessageChannelPlugin implements BasicMessageChannel.MessageHandler<String>,
        BasicMessageChannel.Reply<String> {

    private final Activity activity;
    private final BasicMessageChannel<String> messageChannel;

    public BasicMessageChannelPlugin(FlutterView flutterView) {
        this.activity = (Activity) flutterView.getContext();
        this.messageChannel = new BasicMessageChannel<>(flutterView,
                "BasicMessageChannelPlugin", StringCodec.INSTANCE);
        //設(shè)置消息處理器筒饰,處理來自Flutter的消息
        messageChannel.setMessageHandler(this);
    }

    static BasicMessageChannelPlugin registerWith(FlutterView flutterView) {
        return new BasicMessageChannelPlugin(flutterView);
    }

    @Override
    public void onMessage(String s, BasicMessageChannel.Reply<String> reply) {//處理Flutter發(fā)來的消息
        reply.reply("BasicMessageChannelPlugin收到:" + s);//可以通過reply進(jìn)行回復(fù)
        if (activity instanceof IShowMessage) {
            ((IShowMessage) activity).onShowMessage(s);
        }
        Toast.makeText(activity, s, Toast.LENGTH_SHORT).show();
    }

    /**
     * 向Flutter發(fā)送消息缴啡,并接受Flutter的反饋
     *
     * @param message
     * @param callback
     */
    void send(String message, BasicMessageChannel.Reply<String> callback) {
        messageChannel.send(message, callback);
    }

    @Override
    public void reply(String s) {

    }
}
  1. MethodChannel用法
    Android端:

(1)構(gòu)造方法原型

//會構(gòu)造一個StandardMethodCodec.INSTANCE類型的MethodCodec
MethodChannel(BinaryMessager messenger,String name)
//或
MethodChannel(BinaryMessager messenger,String name,MethodCodec codec)
  • BinaryMessenger messenger:消息信使瓷们,是消息的發(fā)送與接收的工具业栅;
  • String name:Channel的名字,也是其唯一標(biāo)識符谬晕;
  • MethodCodec codec:用作 MethodChannel 的解編碼器碘裕;

(2)setMethodCallHandler 方法原型

setMethodCallHandler(@Nullable MethodChannel.MethodCallHandler handler)
  • @Nullable MethodChannel.MethodCallHandler handler:消息處理器,配合 BinaryMessenger 完成消息的處理攒钳;

在創(chuàng)建好 MethodChannel 后帮孔,需要調(diào)用它的 setMethodCallHandler方法為其設(shè)置一個消息處理器,以便能接收來自 Flutter 的消息不撑。

(3)MethodChannel.MethodCallHandler原型

public interface MethodCallHandler{
    void onMethodCall(MethodCall var1,MethodChannel.Result var2);
}
  • onMethodCall(MethodCall call,MethodChannel.Result result):用于接收消息文兢,call 是消息內(nèi)容,它有兩個成員變量 String 類型的 call.method 表示調(diào)用的方法名焕檬,Object 類型的 call.arguments 表示調(diào)用方法所傳遞的入?yún)⒛芳幔籑ethodChannel.Result result 是回復(fù)此消息的回調(diào)函數(shù)提供了result.success,result.error实愚,result.notImplemented方法調(diào)用兼呵;
/**
 * MethodChannelPlugin
 * 用于傳遞方法調(diào)用(method invocation),一次性通信腊敲,通常用于 Flutter 調(diào)用 native 的方法:如拍照
 */
public class MethodChannelPlugin implements MethodChannel.MethodCallHandler {

    private final Activity activity;

    public MethodChannelPlugin(Activity activity) {
        this.activity = activity;
    }

    public static void registerWith(FlutterView flutterView) {
        MethodChannel channel = new MethodChannel(flutterView, "MethodChannelPlugin");
        MethodChannelPlugin instance = new MethodChannelPlugin((Activity) flutterView.getContext());
        channel.setMethodCallHandler(instance);
    }

    @Override
    public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
        switch (methodCall.method) {//處理來自Flutter的方法調(diào)用
            case "showMessage":
                showMessage(methodCall.arguments());
                result.success("MethodChannelPlugin收到:" + methodCall.arguments);//返回結(jié)果給Flutter
                break;
            default:
                result.notImplemented();
        }
    }

    /**
     * 展示來自Flutter的數(shù)據(jù)
     *
     * @param arguments
     */
    private void showMessage(String arguments) {
        if (activity instanceof IShowMessage) {
            ((IShowMessage) activity).onShowMessage(arguments);
        }
        Toast.makeText(activity, arguments, Toast.LENGTH_SHORT).show();
    }
}
  1. EventChannel用法
    Android端:

(1)構(gòu)造方法原型

//會構(gòu)造一個StandardMethodCodec.INSTANCE類型的MethodCodec
EventChannel(BinaryMessenger messenger,String name)
//或
EventChannel(BinaryMessenger messenger,String name,MethodCodec codec)
  • BinaryMessenger messenger:消息信使击喂,是消息的發(fā)送與接收的工具;
  • String name:Channel的名字兔仰,也是其唯一標(biāo)識符茫负;
  • MethodCodec:用作 EventChannel 的編解碼器;

(2)setStreamHandler 方法原型

void setStreamHandler(EventChannel.StreamHandler handler)
  • EventChannel.StreamHandler handler:消息處理器乎赴,配合 BinaryMessenger 完成消息的處理忍法;在創(chuàng)建好 EventChannel 后,如果要讓其接收 Flutter 發(fā)來的消息榕吼,則需要調(diào)用它的 setStreamHandler 方法為其設(shè)置一個消息處理器饿序。

(3)EventChannel.StreamHandler原型

public interface StreamHandler{
    void onListen(Object args,EventChannel.EventSink eventSink);

    void onCancel(Object 0);
}
  • void onListen(Object args,EventChannel.EventSink eventSink):Flutter Native監(jiān)聽事件時調(diào)用,Object args 是傳遞的參數(shù)羹蚣,EventChannel.EventSink eventSink 是 Native 回調(diào) Flutter 時的回調(diào)函數(shù)原探,eventSink 提供success、error 與 endOfStream 三個回調(diào)方法分別對應(yīng)事件的不同狀態(tài)顽素;
  • void onCancel(Object 0):Flutter取消監(jiān)聽時調(diào)用咽弦;
public class EventChannelPlugin implements EventChannel.StreamHandler {
    
    private List<EventChannel.EventSink> eventSinks = new ArrayList<>();
    
    static EventChannelPlugin registerWith(FlutterView flutterView){
        EventChannelPlugin plugin = new EventChannelPlugin();
        new EventChannel(flutterView,"EventChannelPlugin").setStreamHandler(plugin);
        return plugin;
    }
    
    void sendEvent(Object params){
        for (EventChannel.EventSink eventSink : eventSinks){
            eventSink.success(params);
        }
    }
    
    @Override
    public void onListen(Object o, EventChannel.EventSink eventSink) {
        eventSinks.add(eventSink);
    }

    @Override
    public void onCancel(Object o) {

    }
}

總結(jié)

本文主要介紹了 Flutter 與 Native 的混合開發(fā),它們之間的消息通信主要是通過 Channel 實(shí)現(xiàn)的胁出。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末型型,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子全蝶,更是在濱河造成了極大的恐慌闹蒜,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抑淫,死亡現(xiàn)場離奇詭異绷落,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)始苇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門砌烁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人催式,你說我怎么就攤上這事往弓。” “怎么了蓄氧?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵函似,是天一觀的道長。 經(jīng)常有香客問我喉童,道長撇寞,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任堂氯,我火速辦了婚禮蔑担,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘咽白。我一直安慰自己啤握,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布晶框。 她就那樣靜靜地躺著排抬,像睡著了一般懂从。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蹲蒲,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天番甩,我揣著相機(jī)與錄音,去河邊找鬼届搁。 笑死缘薛,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的卡睦。 我是一名探鬼主播宴胧,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼表锻!你這毒婦竟也來了恕齐?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤浩嫌,失蹤者是張志新(化名)和其女友劉穎檐迟,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體码耐,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡追迟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了骚腥。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片敦间。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖束铭,靈堂內(nèi)的尸體忽然破棺而出廓块,到底是詐尸還是另有隱情,我是刑警寧澤契沫,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布带猴,位于F島的核電站,受9級特大地震影響懈万,放射性物質(zhì)發(fā)生泄漏拴清。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一会通、第九天 我趴在偏房一處隱蔽的房頂上張望口予。 院中可真熱鬧,春花似錦涕侈、人聲如沸沪停。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽木张。三九已至众辨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間窟哺,已是汗流浹背泻轰。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工技肩, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留且轨,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓虚婿,卻偏偏與公主長得像旋奢,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子然痊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容