【原创】Weex 源码探析 —— Module 的注册和调用

整个 Weex 的工作原理大致可以用一张图来表述:



这次分享的重点在 Native Module 如何注册并切可以被 JS 调用的。

初始化

首先我们通过 [WXSDKEngine initSDKEnvironment] 初始化 SDK 环境

+ (void)initSDKEnvironment
{
    ……   
    // 获取 main.js 文件路径
    NSString *filePath = [[NSBundle bundleForClass:self] pathForResource:@"main" ofType:@"js"];
    // 读出 main.js 文件内容
    NSString *script = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
    [WXSDKEngine initSDKEnvironment:script];
    ……
}
      
+ (void)initSDKEnvironment:(NSString *)script
{
    ……
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
     // 注册默认内容 这个稍后会说
        [self registerDefaults];
        // BridgeManager 执行 JS
        [[WXSDKManager bridgeMgr] executeJsFramework:script];
    });
}

让我们忽略掉一些边界处理、监控、日志的代码,只提取其中的核心代码(以下同)。Weex 将 Bundle 中的 main.js 加载,交给 Bridge Manager 执行。我们可以查看 main.js 是一个压缩后的 js 文件,其实它就是 Weex JS 的 Framework,我们调用的 Weex JS API 就是在这里定义的。那么刚刚执行它的 Bridge Manager 又是做什么的呢?

Bridge 模块

Bridge 模块就是负责 Native 与 JS 通讯的,有下面这些类组成。


WXBridgeManager

Bridge 模块还有一个非常重要的类,是在 Manager 路径下的 WXBridgeManager,它是整个模块的对外API,单例。它维护了一个线程来 Loop 处理事件,还关联了 Bridge Context,比如 register module、execute JS 等 JS 的交互操作实际都是在 Bridge Context 中执行的。下面来介绍这个 Bridge Context。

WXBridgeContext

Bridge Context 的主要功能如上文说,是给 Bridge Manager 提供与 JS 交互的能力,所以除了注册 Module、Service 等之外,就是调用 JS 方法和服务,所以它维护了 sendQueue、serviceQueue 两个队列来分别管理方法和服务的调用消息,另外还有一个 methodQueue,是来存储在 Framework 加载之前就被发送的消息,在 Framework 加载完成之后再执行。它还维持了一个 weex instance id stack,在 WXSDKManager 中有一个 id -> WXInstance 的哈希表,可以通过 id 获取到对应的 WXInstance。

WXBridgeProtocol

WXBridgeContext 真正的 JS Native 交互能力依赖于它关联的 id<WXBridgeProtocol>,这个 Protocol 的声明如下:

@protocol WXBridgeProtocol <NSObject>
@property (nonatomic, readonly) JSValue* exception;
/**
  * Executes the js framework code in javascript engine
  * You can do some setup in this method
  */
- (void)executeJSFramework:(NSString *)frameworkScript;
/**
  * Executes the js code in javascript engine
  * You can do some setup in this method
  */
- (void)executeJavascript:(NSString *)script;
/**
 * Executes global js method with specific arguments
 */
- (JSValue *)callJSMethod:(NSString *)method args:(NSArray*)args;
/**
 * Register callback when call native tasks occur
 */
- (void)registerCallNative:(WXJSCallNative)callNative;
/**
 * Reset js engine environment, called when any environment variable is changed.
 */
- (void)resetEnvironment;
@optional
/**
 * Called when garbage collection is wanted by sdk.
 */
- (void)garbageCollect;
/**
 * Register callback when addElement tasks occur
 */
- (void)registerCallAddElement:(WXJSCallAddElement)callAddElement;
/**
 * Register callback for global js function `callNativeModule`
 */
- (void)registerCallNativeModule:(WXJSCallNativeModule)callNativeModuleBlock;
/**
 * Register callback for global js function `callNativeComponent`
 */
- (void)registerCallNativeComponent:(WXJSCallNativeComponent)callNativeComponentBlock;
      
@end

很显然,它提供了 Native 和 JS 交互的最基本功能,包括执行 JS 代码,调用方法,注册 Native 回调等。这个协议有两个实现类:WXJSCoreBridge 和 WXDebugLoggerBridge。其中 WXJSCoreBridge 是使用苹果的 JavaScriptCore 这个引擎来实现的。而 WXDebugLoggerBridge 则是用于调试,将 JS Bridge 对接到一个远程的 websocket 服务器,而不是本地的 JS 引擎。本地准备一个网页,运行了完整的 JS 引擎的代码,并且也可以链接到一个远程的 websocket 服务器,这样客户端的 native 层和本地网页里面的 JS 引擎就串联起来了,原本通过 JS Bridge 的双向通信内容就可以被 websocket 连接记录下来,JS 引擎中里的所有代码都可以通过本地浏览器的开发者工具进行 debug 和 console 控制。

其他

WXBridgeMethod 是其他各种 Method 的基类,他们是封装的方法模型,包括调用对象的 Ref、方法名、参数数组、weex instance 等,可以通过获取 NSInvocation 调用相应的 Native 方法。
JSValue Category 是个工具,可以将 NSInvocation 的返回值转换成 JS 对应的类型。
WXPolyfillSet 是一个 JSExport,封装了一个 NSMutableSet,供 JS 调用。

总结

整个 Weex Bridge 模块的主要类图如下所示。


Native Module 的注册和调用

以 Native Module 为例,学习一下 Weex 是如何通过 JS 调用 Native 代码的。

注册

我们通过[WXSDKEngin registerModule:withClass:]方法来在 Weex 中注册 Module。

+ (void)registerModule:(NSString *)name withClass:(Class)clazz
{
    NSString *moduleName = [WXModuleFactory registerModule:name withClass:clazz];
    NSDictionary *dict = [WXModuleFactory moduleMethodMapsWithName:moduleName];
     
    [[WXSDKManager bridgeMgr] registerModules:dict];
}

代码中先在 ModuleFactory 中注册 Module 并获取 moduleName,再从在 ModuleFactory 中获取到 moduleMethod 的哈希表,并将这个哈希表注册在 Bridge Manager 中。
下面来具体看看每一步注册具体是如何做的。

WXModuleFactory 中的注册

- (NSString *)_registerModule:(NSString *)name withClass:(Class)clazz
{        
    [_moduleLock lock];
    //allow to register module with the same name;
    WXModuleConfig *config = [[WXModuleConfig alloc] init];
    config.name = name;
    config.clazz = NSStringFromClass(clazz);
    [config registerMethods];
    [_moduleMap setValue:config forKey:name];
    [_moduleLock unlock];
     
    return name;
}

在 ModuleFactory 的 moduleMap 中,加入了一个 WXModuleConfig,WXModuleConfig 记录了 name 和 class,调用 [WXModuleConfig registerMethods] 注册了方法。

- (void)registerMethods
{
    Class currentClass = NSClassFromString(_clazz);
     
    if (!currentClass) {
        WXLogWarning(@"The module class [%@] doesn't exit!", _clazz);
        return;
    }
     
    while (currentClass != [NSObject class]) {
        unsigned int methodCount = 0;
        Method *methodList = class_copyMethodList(object_getClass(currentClass), &methodCount);
        for (unsigned int i = 0; i < methodCount; i++) {
            NSString *selStr = [NSString stringWithCString:sel_getName(method_getName(methodList[i])) encoding:NSUTF8StringEncoding];
            BOOL isSyncMethod = NO;
            if ([selStr hasPrefix:@"wx_export_method_sync_"]) {
                isSyncMethod = YES;
            } else if ([selStr hasPrefix:@"wx_export_method_"]) {
                isSyncMethod = NO;
            } else {
                continue;
            }
             
            NSString *name = nil, *method = nil;
            SEL selector = NSSelectorFromString(selStr);
            if ([currentClass respondsToSelector:selector]) {
                method = ((NSString* (*)(id, SEL))[currentClass methodForSelector:selector])(currentClass, selector);
            }
             
            if (method.length <= 0) {
                WXLogWarning(@"The module class [%@] doesn't has any method!", _clazz);
                continue;
            }
             
            NSRange range = [method rangeOfString:@":"];
            if (range.location != NSNotFound) {
                name = [method substringToIndex:range.location];
            } else {
                name = method;
            }
             
            NSMutableDictionary *methods = isSyncMethod ? _syncMethods : _asyncMethods;
            [methods setObject:method forKey:name];
        }
         
        free(methodList);
        currentClass = class_getSuperclass(currentClass);
    }
     
}

[WXModuleConfig registerMethods]方法非常长,简单来说就是:先通过 while 循环遍历自己和父类的方法列表中的每一个方法,如果方法名以 wx_export_method_sync_ 或 wx_export_method_ 开头,则通过 OC runtime 获取到方法所对应的函数指针,并调用这个函数,得到 method 字符串,最后将 method 字符串放入同步或异步的方法表里。
这里面有几个疑问:wx_export_method_sync、wx_export_method 开头的方法是在哪里声明的?调用它得到的 method 字符串又是什么?
在 Weex Module 中,我们需要用 WX_EXPORT_METHOD、WX_EXPORT_METHOD_SYNC 这两个宏来声名 JS 异步和同步方法,问题应该就是在这里,所以看看这两个宏的定义。

/*
 * Concatenate preprocessor tokens a and b without expanding macro definitions
 * (however, if invoked from a macro, macro arguments are expanded).
 */
#define WX_CONCAT(a, b)   a ## b
/*
 * Concatenate preprocessor tokens a and b after macro-expanding them.
 */
#define WX_CONCAT_WRAPPER(a, b)    WX_CONCAT(a, b)
  
#define WX_EXPORT_METHOD_INTERNAL(method, token) \
+ (NSString *)WX_CONCAT_WRAPPER(token, __LINE__) { \
    return NSStringFromSelector(method); \
}
  
 
/**
 *  @abstract export public method
 */
#define WX_EXPORT_METHOD(method) WX_EXPORT_METHOD_INTERNAL(method,wx_export_method_)
/**
 *  @abstract export public method, support sync return value
 *  @warning the method can only be called on js thread
 */
#define WX_EXPORT_METHOD_SYNC(method) WX_EXPORT_METHOD_INTERNAL(method,wx_export_method_sync_)

这两个宏的作用就是定义了一个类方法,以 wx_export_method、wx_export_method_sync_ 加上所在代码的行数组成方法名,返回 selector 的字符串,上面的疑问得以解决。
回到刚才的步骤, 从 ModuleFactory 中获取到 moduleMethod 的哈希表,它是将 config 中的同步和异步方法都装入一个数组中,以 moduleName 为 key,数组为 value 生成一个单项哈希表。
至此,在 WXModuleFactory 中的注册已经基本完成了:moduleMap 哈希表中用 moduleName 作为 key,moduleConfig 作为 value;moduleConfig 中记录着 className 和 moduleName,同时还有两个哈希表,存储同步和异步方法。

WXBridgeManager 中的注册

Bridge Manager 是调用 Bridge Context 执行的注册。

- (void)registerModules:(NSDictionary *)modules
{
    WXAssertBridgeThread();
     
    if(!modules) return;
     
    [self callJSMethod:@"registerModules" args:@[modules]];
}
  
- (void)callJSMethod:(NSString *)method args:(NSArray *)args
{
    if (self.frameworkLoadFinished) {
        [self.jsBridge callJSMethod:method args:args];
    } else {
        [_methodQueue addObject:@{@"method":method, @"args":args}];
    }
}

它是调用了 JSBridge 的 callJSMethod 方法,method 为 "registerModules",args 参数是这个 Module 提供给 JS 调用的所有的同步、异步方法列表。
之前介绍了 Bridge 模块下的 JSBridgeProtocol 的两个实现类,那么以 WXJSCoreBridge 为例,callJSMethod 的方法实现如下:

- (JSValue *)callJSMethod:(NSString *)method args:(NSArray *)args
{
    return [[_jsContext globalObject] invokeMethod:method withArguments:args];
}

直接调用了 JS 全局对象的registerModules方法。那么我们就看看 JS 中的 registerModules 方法是如何定义的。

/**
 * Register the name and methods of each module.
 * @param  {object} modules a object of modules
 */
export function registerModules (modules) {
  /* istanbul ignore else */
  if (typeof modules === 'object') {
    initModules(modules)
  }
}
 
 
/**
 * init modules for an app instance
 * the second param determines whether to replace an existed method
 */
export function initModules (modules, ifReplace) {
  for (const moduleName in modules) {
    // init `modules[moduleName][]`
    let methods = nativeModules[moduleName]
    if (!methods) {
      methods = {}
      nativeModules[moduleName] = methods
    }
 
    // push each non-existed new method
    modules[moduleName].forEach(function (method) {
      if (typeof method === 'string') {
        method = {
          name: method
        }
      }
 
      if (!methods[method.name] || ifReplace) {
        methods[method.name] = method
      }
    })
  }
}

简而言之就是将 name 和方法哈希中的方法都写入了 nativeModules 这个对象中。

调用

我们在 JS 中使用weex.requireModule('xxx')来获取 module,再调用它的方法。那么 weex 这个全局变量是哪里定义的呢?它是怎么能够被获取到的?requireModule()又做了什么?

weex 对象

每个 weex 页面都是一个 WXSDKInstance,weex instance 在加载到 JS bundle 的代码之后,会调用renderWithMainBundleString:方法。

- (void)_renderWithMainBundleString:(NSString *)mainBundleString
{                
    NSMutableDictionary *dictionary = [_options mutableCopy];        
    WXPerformBlockOnMainThread(^{
        _rootView = [[WXRootView alloc] initWithFrame:self.frame];
        _rootView.instance = self;
        if(self.onCreate) {
            self.onCreate(_rootView);
        }
    });
     
    // ensure default modules/components/handlers are ready before create instance
    [WXSDKEngine registerDefaults];
     
    [[WXSDKManager bridgeMgr] createInstance:self.instanceId template:mainBundleString options:dictionary data:_jsData];
}

先在主线程中创建了 WXRootView,并调用了 onCreate 回调,最后调用了[WXBridgeManager createInstance:template:options:data],将 mainBundleString 传给了 BridgeManager。
在 BridgeManager 中切换了执行线程,将任务交给 Bridge Context 来执行。

- (void)createInstance:(NSString *)instance
              template:(NSString *)temp
               options:(NSDictionary *)options
                  data:(id)data
{        
    ……
    [self callJSMethod:@"createInstance" args:args];
}

最后 Bridge Context 调用了createInstance()这个 JS 方法,将 instanceId、mainBundleString 等参数都传了过去。
我们看看 JS 中createInstance()这个方法的实现。


/*
 * @param  {string} id
 * @param  {string} code
 * @param  {object} options
 *         option `HAS_LOG` enable print log
 * @param  {object} data
 * @param  {object} info { created, ... services }
 */
export function createInstance (id, code, options, data, info) {
  const { services } = info || {}
  resetTarget()
  let instance = instanceMap[id]
  /* istanbul ignore else */
  options = options || {}
  let result
  /* istanbul ignore else */
  if (!instance) {
    instance = new App(id, options)
    instanceMap[id] = instance
    result = initApp(instance, code, data, services)
  }
  else {
    result = new Error(`invalid instance id "${id}"`)
  }
  return result
}
  
export function init (app, code, data, services) {
  //……
  //……
  /* istanbul ignore next */
  const bundleRequireModule = name => app.requireModule(removeWeexPrefix(name))
 
 
  const weexGlobalObject = {
    config: app.options,
    define: bundleDefine,
    bootstrap: bundleBootstrap,
    requireModule: bundleRequireModule,
    document: bundleDocument,
    Vm: bundleVm
  }
 
  Object.freeze(weexGlobalObject)
 
  // prepare code
  let functionBody
  /* istanbul ignore if */
  if (typeof code === 'function') {
    // `function () {...}` -> `{...}`
    // not very strict
    functionBody = code.toString().substr(12)
  }
  /* istanbul ignore next */
  else if (code) {
    functionBody = code.toString()
  }
  // wrap IFFE and use strict mode
  functionBody = `(function(global){\n\n"use strict";\n\n ${functionBody} \n\n})(Object.create(this))`
   
  // ……
  
// run code and get result
  const globalObjects = Object.assign({
    define: bundleDefine,
    require: bundleRequire,
    bootstrap: bundleBootstrap,
    register: bundleRegister,
    render: bundleRender,
    __weex_define__: bundleDefine, // alias for define
    __weex_bootstrap__: bundleBootstrap, // alias for bootstrap
    __weex_document__: bundleDocument,
    __weex_require__: bundleRequireModule,
    __weex_viewmodel__: bundleVm,
    weex: weexGlobalObject
  }, timerAPIs, services)
  callFunction(globalObjects, functionBody)
 
  return result
}
  
/**
 * Call a new function body with some global objects.
 * @param  {object} globalObjects
 * @param  {string} code
 * @return {any}
 */
function callFunction (globalObjects, body) {
  const globalKeys = []
  const globalValues = []
  for (const key in globalObjects) {
    globalKeys.push(key)
    globalValues.push(globalObjects[key])
  }
  globalKeys.push(body)
 
  const result = new Function(...globalKeys)
  return result(...globalValues)
}

代码非常多,所以只截取了我们今天要关注的部分。
首先在createInstance()函数中,创建了 App 对象,并将它存储在 instanceMap 中。
init()函数中,先定义了 weexGlobalObject,这也就是我们 JS bundle 代码中要使用的 weex 对象,其中也声名了我们需要的requireModule()方法。而后又将传入的 mainBundleString 封装在了 IIFE 中。然后又定义了 globalObjects 对象,里面除了包含了 weex、require、bootstrap、render 等多个成员外,也包含了 timerAPIs 和 services 中的成员。
最后在callFunction()函数中,将 globalObjects 中的所有成员都作为参数,调用了之前封装的 IIFE。至此我们的 JS bundle 代码中直接使用的 weex 实例就被这样传递过来了。

requireModule()

后面的问题就简单多了,在 App 中查看requireModule()的实现。

/**
 * @deprecated
 */
App.prototype.requireModule = function (name) {
  return requireModule(this, name)
}
 
 
  
/**
 * get a module of methods for an app instance
 */
export function requireModule (app, name) {
  const methods = nativeModules[name]
  const target = {}
  for (const methodName in methods) {
    Object.defineProperty(target, methodName, {
      configurable: true,
      enumerable: true,
      get: function moduleGetter () {
        return (...args) => app.callTasks({
          module: name,
          method: methodName,
          args: args
        })
      },
      set: function moduleSetter (value) {
        if (typeof value === 'function') {
          return app.callTasks({
            module: name,
            method: methodName,
            args: [value]
          })
        }
      }
    })
  }
  return target
}

从 nativeModules 中取到之前注册时候存入的 methods 遍历,将方法的相关信息设为 target 的成员,最后将 target 返回。调用的话是直接将 module name、方法名和参数封装,传递给app.callTasks()

调用 native

先看看app.callTasks()的实现。

/**
 * @deprecated
 */
App.prototype.callTasks = function (tasks) {
  return callTasks(this, tasks)
}
 
 
/**
 * Call all tasks from an app to renderer (native).
 * @param  {object} app
 * @param  {array}  tasks
 */
export function callTasks (app, tasks) {
  let result
 
  /* istanbul ignore next */
  if (typof(tasks) !== 'array') {
    tasks = [tasks]
  }
 
  tasks.forEach(task => {
    result = app.doc.taskCenter.send(
      'module',
      {
        module: task.module,
        method: task.method
      },
      task.args
    )
  })
 
  return result
}

对 taskCenter 发送消息,将 module、method 和 args 都传递过去。

export class TaskCenter {
  ……
  send (type, options, args) {
    const { action, component, ref, module, method } = options
 
    args = args.map(arg => this.normalize(arg))
 
    switch (type) {
      case 'dom':
        return this[action](this.instanceId, args)
      case 'component':
        return this.componentHandler(this.instanceId, ref, method, args, { component })
      default:
        return this.moduleHandler(this.instanceId, module, method, args, {})
    }
  }
  ……
}
  
export function init () {
  const DOM_METHODS = {
    createFinish: global.callCreateFinish,
    updateFinish: global.callUpdateFinish,
    refreshFinish: global.callRefreshFinish,
 
    createBody: global.callCreateBody,
 
    addElement: global.callAddElement,
    removeElement: global.callRemoveElement,
    moveElement: global.callMoveElement,
    updateAttrs: global.callUpdateAttrs,
    updateStyle: global.callUpdateStyle,
 
    addEvent: global.callAddEvent,
    removeEvent: global.callRemoveEvent
  }
  const proto = TaskCenter.prototype
 
  for (const name in DOM_METHODS) {
    const method = DOM_METHODS[name]
    proto[name] = method ?
      (id, args) => method(id, ...args) :
      (id, args) => fallback(id, [{ module: 'dom', method: name, args }], '-1')
  }
 
  proto.componentHandler = global.callNativeComponent ||
    ((id, ref, method, args, options) =>
      fallback(id, [{ component: options.component, ref, method, args }]))
 
  proto.moduleHandler = global.callNativeModule ||
    ((id, module, method, args) =>
      fallback(id, [{ module, method, args }]))
}

taskCenter 这个类是 JS 到 native 的桥梁,其中的 send 方法,会根据 type 对发送来的消息做不同的处理,现在的参数是 'module' 则调用moduleHandler()。在 init() 函数中定义了moduleHandler = global.callNativeModule,感觉就快找到目标了。
最后终于到callNativeModule函数定义在了这里:

@implementation WXJSCoreBridge
  
……
  
- (void)registerCallNativeModule:(WXJSCallNativeModule)callNativeModuleBlock
{
    _jsContext[@"callNativeModule"] = ^JSValue *(JSValue *instanceId, JSValue *moduleName, JSValue *methodName, JSValue *args, JSValue *options) {
        NSString *instanceIdString = [instanceId toString];
        NSString *moduleNameString = [moduleName toString];
        NSString *methodNameString = [methodName toString];
        NSArray *argsArray = [args toArray];
        NSDictionary *optionsDic = [options toDictionary];
         
        NSInvocation *invocation = callNativeModuleBlock(instanceIdString, moduleNameString, methodNameString, argsArray, optionsDic);
        JSValue *returnValue = [JSValue wx_valueWithReturnValueFromInvocation:invocation inContext:[JSContext currentContext]];
        return returnValue;
    };
}
  
……
  
@end
  
@implementation WXBridgeContext
……
- (void)registerGlobalFunctions
{
    ……
    [_jsBridge registerCallNativeModule:^NSInvocation*(NSString *instanceId, NSString *moduleName, NSString *methodName, NSArray *arguments, NSDictionary *options) {
         
        WXSDKInstance *instance = [WXSDKManager instanceForID:instanceId];
         
        WXModuleMethod *method = [[WXModuleMethod alloc] initWithModuleName:moduleName methodName:methodName arguments:arguments instance:instance];
        return [method invoke];
    }];
    ……
}
……
@end

在 WXJSCoreBridge 中定义了注册调用原生模块的方法,在 WXBridgeContext 中的注册全局函数中,对这个函数进行了注册。被调用的处理很简单,根据 moduleName、methodName 和 arguments 创建出 WXModuleMethod 并进行调用。
最后看看 WXModuleMethod 中 invoke 的具体实现。

- (NSInvocation *)invoke
{
    Class moduleClass =  [WXModuleFactory classWithModuleName:_moduleName];
             
    id<WXModuleProtocol> moduleInstance = [self.instance moduleForClass:moduleClass];
    
    BOOL isSync = NO;
    SEL selector = [WXModuleFactory selectorWithModuleName:self.moduleName methodName:self.methodName isSync:&isSync];
    
    if (![moduleInstance respondsToSelector:selector]) {
        // if not implement the selector, then dispatch default module method
        if ([self.methodName isEqualToString:@"addEventListener"]) {
            [self.instance _addModuleEventObserversWithModuleMethod:self];
        } else if ([self.methodName isEqualToString:@"removeAllEventListeners"]) {
            [self.instance _removeModuleEventObserverWithModuleMethod:self];
        }
        return nil;
    }
     
    NSInvocation *invocation = [self invocationWithTarget:moduleInstance selector:selector];
     
    if (isSync) {
        [invocation invoke];
        return invocation;
    } else {
        [self _dispatchInvocation:invocation moduleInstance:moduleInstance];
        return nil;
    }
}

先从 WXModuleFactory 中获取到 moduleClass,再在 weex instance 中获取到 moduleInstance,最后得到 invocation 对象并根据同步异步来做不同的调用,整个 module 的注册和调用流程就总算完成了。

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

推荐阅读更多精彩内容