从java层看react-naive通信机制

-2,前言

最近我们公司突然开始涉足react-native开发,老实说,我内心是拒绝的。其一,是因为目前我对于原生开发还不够精通,不想突然转向,其二是因为react-native目前还没有1.0的正式版本,仍然处于探索期不稳定。其三,我特么开发react-native用的是windows啊,人家facebook的工程师开发这玩儿用的都是mac。看在工资的份儿上,我开始探索react-native。

-1,解释说明

  • 说实话,一开始就上这么难的话题,我也是没办法。关于环境集成,跑demo等等遇到的这些坑,虽然积攒了一点经验,但是很多时候,我是懵逼的,所以总结这些经验而不说出问题的根源,不去探究为什么,这不是我的风格。而通信机制则是一整套严肃的设计,知根知底,而且随着对于原理的深究,以后对于问题的根源就会更加明了(不过windows环境下还是不要立这种flag比较好)。
  • 其次,作为react-native的菜鸟,我驾驭这个问题是有一些难度的,但是,这也正好是另一种优势,那就是,我能以一个react-native的菜鸟的角度,去解释Java与js的通信机制,应该是对于刚接触react-native的原生开发的工程师更加友好。
  • 最后,我在探究这个问题的过程中,找到了很多很棒的博文,比如其实没那么复杂!探究react-native通信机制等,对于这些前辈,我只有献上自己的膝盖。 我目前用的RN的最新版本,而他们的版本应该是有点老,因此代码其实跟上面的作者分析有出入。

0,铺垫

首先我们要明白以下几点:

  • 在react-native中的通信,主要是Java与JavaScript之间的通信,而实际上,Java与Js之间是根本没办法直接对话的,别看他们看起来好像是亲戚,实际上他们的关系就相当于雷锋和雷峰塔的关系
  • 那么Java和Js之间想要能听懂对方的话,有两个必备条件:
    • 双方的信息要能够传达到对方那里去,就是,先不管听不听的懂 ,你首先要把话传过去
    • 信息传达前需要经过翻译,才能被接受方正确理解。
  • 第一个条件的解决方案是通过C++来做这个传话筒,Java通过JNI来call到c++层,然后c++层再把信息传到js,反之亦然;第二个条件的解决方案就是通过在初始化的时候构造两本“词典”,约定好以后说话只说对方的“词典”上的单词。

所以我们的问题其实只有两点:那就是集中精力观察“词典”是怎么传递到双方手里的,以及两方是怎么传递数据的

1,开篇

1.1,Java传递“词典”

首先,对于词典还是正确解释一下,它是某种config,某种配置文件,每次Java层收到js层传来的的信息,都会读取这个文件,然后才能理解Java层的意思。Java层也是一样。他们对应RN的代码的类分别是:NativeModuleRegistry和JavaScriptModuleRegistry

初始化的开端源自ReactActivity,这是react-native中的类,它的onCreate()方法中是这么做的:

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    if (getUseDeveloperSupport() && Build.VERSION.SDK_INT >= 23) {
      // Get permission to show redbox in dev builds.
      if (!Settings.canDrawOverlays(this)) {
        Intent serviceIntent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
        startActivity(serviceIntent);
        FLog.w(ReactConstants.TAG, REDBOX_PERMISSION_MESSAGE);
        Toast.makeText(this, REDBOX_PERMISSION_MESSAGE, Toast.LENGTH_LONG).show();
      }
    }

    mReactRootView = createRootView();
    //这是最重要的一步
    mReactRootView.startReactApplication(
      getReactNativeHost().getReactInstanceManager(),
      getMainComponentName(),
      getLaunchOptions());
    setContentView(mReactRootView);
    mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer();
  }

mRootView是一个layout,继承自FrameLayout,一切的js渲染从这个Layout上开始,它的startReactApplication()方法如下:

  public void startReactApplication(
      ReactInstanceManager reactInstanceManager,
      String moduleName,
      @Nullable Bundle launchOptions) {
    UiThreadUtil.assertOnUiThread();

    // TODO(6788889): Use POJO instead of bundle here, apparently we can't just use WritableMap
    // here as it may be deallocated in native after passing via JNI bridge, but we want to reuse
    // it in the case of re-creating the catalyst instance
    Assertions.assertCondition(
        mReactInstanceManager == null,
        "This root view has already been attached to a catalyst instance manager");

    mReactInstanceManager = reactInstanceManager;
    mJSModuleName = moduleName;
    mLaunchOptions = launchOptions;

    //这是关键
    if (!mReactInstanceManager.hasStartedCreatingInitialContext()) {
      mReactInstanceManager.createReactContextInBackground();
    }

    // We need to wait for the initial onMeasure, if this view has not yet been measured, we set which
    // will make this view startReactApplication itself to instance manager once onMeasure is called.
    if (mWasMeasured) {
      attachToReactInstanceManager();
    }
  }

这里有一个ReactInstanceManager,它的作用就是管理CatalystInstance的实例,CatalystInstance是什么?这是一个上层抽象的调用接口),Java和Js都可以通过这个去调用对方,当然,那两个类都是抽象的,实际上都是通过它们的XXXXImpl类来实现具体的功能。

那么我们接着往下,注意我们的目的:了解初始化时如何传递那两本“词典”的,mReactInstanceManager.createReactContextInBackground();这个方法就直接调用到了它的实现类:XReactInstanceManagerImpl中的createReactContextInBackground然后接下来的流程就是:

createReactContextInBackground()------> recreateReactContextInBackgroundInner(); -------> recreateReactContextInBackgroundFromBundleLoader(); ---------> recreateReactContextInBackground() ;

到了recreateReactContextInBackground()这个方法大概是这样的:


  private void recreateReactContextInBackground(
      JavaScriptExecutor.Factory jsExecutorFactory,
      JSBundleLoader jsBundleLoader) {
    UiThreadUtil.assertOnUiThread();

    //构造参数
    ReactContextInitParams initParams =
        new ReactContextInitParams(jsExecutorFactory, jsBundleLoader);
    if (mReactContextInitAsyncTask == null) {
      // No background task to create react context is currently running, create and execute one.
      //执行了一个AsyncTask.......
      mReactContextInitAsyncTask = new ReactContextInitAsyncTask();
      mReactContextInitAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, initParams);
    } else {
      // Background task is currently running, queue up most recent init params to recreate context
      // once task completes.
      mPendingReactContextInitParams = initParams;
    }
  }
  

好我们接下来看这个线程内部的细节,重点看doInBackground()这个方法:

 @Override
    protected Result<ReactApplicationContext> doInBackground(ReactContextInitParams... params) {
      // TODO(t11687218): Look over all threading
      // Default priority is Process.THREAD_PRIORITY_BACKGROUND which means we'll be put in a cgroup
      // that only has access to a small fraction of CPU time. The priority will be reset after
      // this task finishes: https://android.googlesource.com/platform/frameworks/base/+/
      d630f105e8bc0021541aacb4dc6498a49048ecea/core/java/android/os/AsyncTask.java#256
      Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);

      Assertions.assertCondition(params != null && params.length > 0 && params[0] != null);
      try {
        JavaScriptExecutor jsExecutor = params[0].getJsExecutorFactory().create();
        
        //createReactContext()这个方法被执行
        return Result.of(createReactContext(jsExecutor, params[0].getJsBundleLoader()));
      } catch (Exception e) {
        // Pass exception to onPostExecute() so it can be handled on the main thread
        return Result.of(e);
      }
    }

接下去看createReactContext(jsExecutor, params[0].getJsBundleLoader())这个方法:

/**
   * @return instance of {@link ReactContext} configured a {@link CatalystInstance} set
   */
  private ReactApplicationContext createReactContext(
      JavaScriptExecutor jsExecutor,
      JSBundleLoader jsBundleLoader) {
    FLog.i(ReactConstants.TAG, "Creating react context.");
    ReactMarker.logMarker(CREATE_REACT_CONTEXT_START);
    mSourceUrl = jsBundleLoader.getSourceUrl();
    
    //你瞧,之前提到的两本“词典”,他们的Builder已经露面了。
    NativeModuleRegistry.Builder nativeRegistryBuilder = new NativeModuleRegistry.Builder();
    JavaScriptModuleRegistry.Builder jsModulesBuilder = new JavaScriptModuleRegistry.Builder();

    final ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext);
    if (mUseDeveloperSupport) {
      reactContext.setNativeModuleCallExceptionHandler(mDevSupportManager);
    }

    ReactMarker.logMarker(PROCESS_PACKAGES_START);
    Systrace.beginSection(
        TRACE_TAG_REACT_JAVA_BRIDGE,
        "createAndProcessCoreModulesPackage");
    try {
    
    //CoreModulesPackage里面定义了RN框架核心的一些Java和JS的module
    //通过processPackage()方法写入到两本“词典”的Builder中
      CoreModulesPackage coreModulesPackage =
          new CoreModulesPackage(this, mBackBtnHandler, mUIImplementationProvider);
          
      processPackage(coreModulesPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);
    } finally {
      Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
    }

    // TODO(6818138): Solve use-case of native/js modules overriding
    //这里是开发者自己定义或封装的一些组件或者事件的package
    for (ReactPackage reactPackage : mPackages) {
      Systrace.beginSection(
          TRACE_TAG_REACT_JAVA_BRIDGE,
          "createAndProcessCustomReactPackage");
      try {
        processPackage(reactPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);
      } finally {
        Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
      }
    }
    ReactMarker.logMarker(PROCESS_PACKAGES_END);

    ReactMarker.logMarker(BUILD_NATIVE_MODULE_REGISTRY_START);
    Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "buildNativeModuleRegistry");
    NativeModuleRegistry nativeModuleRegistry;
    try {
        //好了,创建了用于翻译Java端的“词典”
       nativeModuleRegistry = nativeRegistryBuilder.build();
    } finally {
      Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
      ReactMarker.logMarker(BUILD_NATIVE_MODULE_REGISTRY_END);
    }

    NativeModuleCallExceptionHandler exceptionHandler = mNativeModuleCallExceptionHandler != null
        ? mNativeModuleCallExceptionHandler
        : mDevSupportManager;
    CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder()
        .setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault())
        .setJSExecutor(jsExecutor)
        .setRegistry(nativeModuleRegistry)
        //创建了用于翻译JS端的“词典”
        .setJSModuleRegistry(jsModulesBuilder.build())
        .setJSBundleLoader(jsBundleLoader)
        .setNativeModuleCallExceptionHandler(exceptionHandler);
    
    //到目前为止。两本“词典”都已经创建完毕,而且全部都在CatalystInstance这个类的**实现类的Builder中**,此时你可以回忆一下整个过程,理清一下思路。
    
    ReactMarker.logMarker(CREATE_CATALYST_INSTANCE_START);
    // CREATE_CATALYST_INSTANCE_END is in JSCExecutor.cpp
    Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "createCatalystInstance");
    final CatalystInstance catalystInstance;
    try {
      //这个Build()很关键,它用实现类的Builder创建了一个CatalystInstance类。
      catalystInstance = catalystInstanceBuilder.build();
    } finally {
      Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
      ReactMarker.logMarker(CREATE_CATALYST_INSTANCE_END);
    }

    if (mBridgeIdleDebugListener != null) {
      catalystInstance.addBridgeIdleDebugListener(mBridgeIdleDebugListener);
    }

    ReactMarker.logMarker(RUN_JS_BUNDLE_START);
    try {
      catalystInstance.getReactQueueConfiguration().getJSQueueThread().callOnQueue(
        new Callable<Void>() {
          @Override
          public Void call() throws Exception {
            reactContext.initializeWithInstance(catalystInstance);

            Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "runJSBundle");
            try {
                //在这里就运行js代码说明至少在这个方法之前,“词典”应该传过去了
                //于是我们刻印回去聚焦到catalystInstance = catalystInstanceBuilder.build();这段代码
              catalystInstance.runJSBundle();
            } finally {
              Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
              ReactMarker.logMarker(RUN_JS_BUNDLE_END);
            }
            return null;
          }
        }).get();
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    } catch (ExecutionException e) {
      if (e.getCause() instanceof RuntimeException) {
        throw (RuntimeException) e.getCause();
      } else {
        throw new RuntimeException(e);
      }
    }

    return reactContext;
  }
  
  //将package中的关于Java和js的东西分别添加到两本“词典”的builder中
   private void processPackage(
      ReactPackage reactPackage,
      ReactApplicationContext reactContext,
      NativeModuleRegistry.Builder nativeRegistryBuilder,
      JavaScriptModuleRegistry.Builder jsModulesBuilder) {
    for (NativeModule nativeModule : reactPackage.createNativeModules(reactContext)) {
      nativeRegistryBuilder.add(nativeModule);
    }
    for (Class<? extends JavaScriptModule> jsModuleClass : reactPackage.createJSModules()) {
      jsModulesBuilder.add(jsModuleClass);
    }
  }

catalystInstanceBuilder.build()这段代码具体实现如下:

    public CatalystInstanceImpl build() {
      return new CatalystInstanceImpl(
          Assertions.assertNotNull(mReactQueueConfigurationSpec),
          Assertions.assertNotNull(mJSExecutor),
          Assertions.assertNotNull(mRegistry),
          Assertions.assertNotNull(mJSModuleRegistry),
          Assertions.assertNotNull(mJSBundleLoader),
          Assertions.assertNotNull(mNativeModuleCallExceptionHandler));
    }

这个方法的作用就是直接new了一个CatalystInstanceImpl类,那么我们接下去看CatalystInstanceImpl类的构造方法:

 private CatalystInstanceImpl(
      final ReactQueueConfigurationSpec ReactQueueConfigurationSpec,
      final JavaScriptExecutor jsExecutor,
      final NativeModuleRegistry registry,
      final JavaScriptModuleRegistry jsModuleRegistry,
      final JSBundleLoader jsBundleLoader,
      NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
    FLog.d(ReactConstants.TAG, "Initializing React Xplat Bridge.");
    mHybridData = initHybrid();

    mReactQueueConfiguration = ReactQueueConfigurationImpl.create(
        ReactQueueConfigurationSpec,
        new NativeExceptionHandler());
    mBridgeIdleListeners = new CopyOnWriteArrayList<>();
    //这个就是Java层要传递给Js层的“词典”
    mJavaRegistry = registry;
    mJSModuleRegistry = jsModuleRegistry;
    mJSBundleLoader = jsBundleLoader;
    mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler;
    mTraceListener = new JSProfilerTraceListener(this);
    
    //在这个方法里,就把Java暴露给Js的词典传了进去
    initializeBridge(
      new BridgeCallback(this),
      jsExecutor,
      mReactQueueConfiguration.getJSQueueThread(),
      mReactQueueConfiguration.getNativeModulesQueueThread(),
      //getModuleRegistryHolder()这个方法不过是一种holder,对“词典”做了一些封装。
      mJavaRegistry.getModuleRegistryHolder(this));
    mMainExecutorToken = getMainExecutorToken();
  }
  
  //你瞧,native方法,直接call到C++层,接下来,由C++层通过各种折腾,然后生成某种配置文件,转发到Js端
   private native void initializeBridge(ReactCallback callback,
                                       JavaScriptExecutor jsExecutor,
                                       MessageQueueThread jsQueue,
                                       MessageQueueThread moduleQueue,
                                       ModuleRegistryHolder registryHolder);

好了,关于“词典”是如何传递过去的,就解释到这里,虽然C++层可能有更多操作,但是目的就是一个,把Java类转化成一个JS读得懂的格式的文件。
那么我们接下来看看C++是如何做好传声筒的

1.2,数据传递过程

初始化完成之后,Java端和Js端都有了“词典”,就可把自己的意图翻译成对方能听得懂的话了,这个时候交流就会畅通了。

1.2.1,Java --> Js

这个问题首先Java层应该做的是找到那本Js的词典,所以我们应该寻找Java层是在哪里调用到了JSModuleRegistry这个类的,

让我们退回到ReactContextInitAsyncTask的doInBackground方法中,在词典传递完毕之后,这个方法基本执行完毕,接下来是

  @Override
    protected void onPostExecute(Result<ReactApplicationContext> result) {
      try {
        setupReactContext(result.get());
      } catch (Exception e) {
        mDevSupportManager.handleException(e);
      } finally {
        mReactContextInitAsyncTask = null;
      }

      // Handle enqueued request to re-initialize react context.
      if (mPendingReactContextInitParams != null) {
        recreateReactContextInBackground(
            mPendingReactContextInitParams.getJsExecutorFactory(),
            mPendingReactContextInitParams.getJsBundleLoader());
        mPendingReactContextInitParams = null;
      }
    }

setupReactContext(result.get())这个方法,然后这个方法又会调用attachMeasuredRootViewToInstance()方法:

 private void attachMeasuredRootViewToInstance(
      ReactRootView rootView,
      CatalystInstance catalystInstance) {
    Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "attachMeasuredRootViewToInstance");
    UiThreadUtil.assertOnUiThread();

    // Reset view content as it's going to be populated by the application content from JS
    rootView.removeAllViews();
    rootView.setId(View.NO_ID);

    UIManagerModule uiManagerModule = catalystInstance.getNativeModule(UIManagerModule.class);
    int rootTag = uiManagerModule.addMeasuredRootView(rootView);
    rootView.setRootViewTag(rootTag);
    @Nullable Bundle launchOptions = rootView.getLaunchOptions();
    WritableMap initialProps = Arguments.makeNativeMap(launchOptions);
    String jsAppModuleName = rootView.getJSModuleName();

    WritableNativeMap appParams = new WritableNativeMap();
    appParams.putDouble("rootTag", rootTag);
    appParams.putMap("initialProps", initialProps);
    
    //在这里,Java找到了那本Js的“词典”,然后runApplication
    catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams);
    Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
  }

其实catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams)这段代码比较复杂,首先getJSModule方法具体实现在catalystInstanceImpl中,然后会调用到mJSModuleRegistry.getJavaScriptModule(this, executorToken, jsInterface)中,代码如下:

  public synchronized <T extends JavaScriptModule> T getJavaScriptModule(
    CatalystInstance instance,
    ExecutorToken executorToken,
    Class<T> moduleInterface) {
    HashMap<Class<? extends JavaScriptModule>, JavaScriptModule> instancesForContext =
        mModuleInstances.get(executorToken);
    if (instancesForContext == null) {
      instancesForContext = new HashMap<>();
      mModuleInstances.put(executorToken, instancesForContext);
    }

    JavaScriptModule module = instancesForContext.get(moduleInterface);
    if (module != null) {
      return (T) module;
    }

    JavaScriptModuleRegistration registration =
        Assertions.assertNotNull(
            mModuleRegistrations.get(moduleInterface),
            "JS module " + moduleInterface.getSimpleName() + " hasn't been registered!");
    //关键
    JavaScriptModule interfaceProxy = (JavaScriptModule) Proxy.newProxyInstance(
        moduleInterface.getClassLoader(),
        new Class[]{moduleInterface},
        new JavaScriptModuleInvocationHandler(executorToken, instance, registration));
    instancesForContext.put(moduleInterface, interfaceProxy);
    return (T) interfaceProxy;
  }

什么动态代理不用理他,看 new JavaScriptModuleInvocationHandler(executorToken, instance, registration)这个类:

  private static class JavaScriptModuleInvocationHandler implements InvocationHandler {

    private final WeakReference<ExecutorToken> mExecutorToken;
    private final CatalystInstance mCatalystInstance;
    private final JavaScriptModuleRegistration mModuleRegistration;

    public JavaScriptModuleInvocationHandler(
        ExecutorToken executorToken,
        CatalystInstance catalystInstance,
        JavaScriptModuleRegistration moduleRegistration) {
      mExecutorToken = new WeakReference<>(executorToken);
      mCatalystInstance = catalystInstance;
      mModuleRegistration = moduleRegistration;
    }

    //关键
    @Override
    public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable {
      ExecutorToken executorToken = mExecutorToken.get();
      if (executorToken == null) {
        FLog.w(ReactConstants.TAG, "Dropping JS call, ExecutorToken went away...");
        return null;
      }
      NativeArray jsArgs = args != null ? Arguments.fromJavaArgs(args) : new WritableNativeArray();
      mCatalystInstance.callFunction(
        executorToken,
        mModuleRegistration.getName(),
        method.getName(),
        jsArgs
      );
      return null;
    }
  }
}

invoke方法通过mCatalystInstance调用了callFunction()方法;不用多想,我们直接在实现类中去找这个方法:

@Override
  public void callFunction(
      ExecutorToken executorToken,
      final String module,
      final String method,
      final NativeArray arguments) {
    if (mDestroyed) {
      FLog.w(ReactConstants.TAG, "Calling JS function after bridge has been destroyed.");
      return;
    }
    if (!mAcceptCalls) {
      throw new RuntimeException("Attempt to call JS function before JS bundle is loaded.");
    }

    callJSFunction(executorToken, module, method, arguments);
  }
  
  
    private native void callJSFunction(ExecutorToken token,String module, String method,NativeArray arguments);

callFunction()方法里面调用了callJSFunction()这个本地方法,然后由C++做转发,这个本地方法传递的参数有token,包名,方法名,和参数,

至此,从Java端调用到Js端的过程,到这里可以宣告结束了。

1.2.2,js-->Java

待续。

2,矫正

  • 暂无

3,总结

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

推荐阅读更多精彩内容