Android之旅 -- ARouter 源码分析(二)

美景总是好的

Android之旅 -- ARouter 源码分析(一) 主要介绍了 ARouter 启动 activity 的基本原理.
这篇我们来看下在编译期 arouter-compiler 都做了哪些事情

介绍

大概梳理一下 arouter-compiler, 先查看 build.gradle

// build.gradle

// 这里要注意, 不能用 com.android.library, 因为对 annotation 的处理需要用的javax 包下的类, android sdk是没有的.
apply plugin: 'java'

...

dependencies {
    // 这里依赖的不是源码的module
    compile 'com.alibaba:arouter-annotation:1.0.3'
    
    // 这个是自动生成 META-INF 配置 Processor 内容的
    compile 'com.google.auto.service:auto-service:1.0-rc2'

    // 用来生成代码的, 比拼字符串生成源码要高大上一些
    compile 'com.squareup:javapoet:1.7.0'

...

可以看到 arouter-compiler 通过 自动配置的 Processor, 查找声明了 arouter-annotation 里定义的 annotation, 然后根据 annotation 的配置使用 javapoet 生成辅助注入的代码, 达到在开发中可以不用实际依赖一些类, 达到模块或者组件之间的解耦.


arouter-compiler 源码分析

arouter-compiler 有三个 Processor
AutowiredProcessor -- 处理 @Autowired
InterceptorProcessor -- 处理 @Interceptor
RouteProcessor -- 处理 @Autowired, Route

  1. 先分析 RouteProcessor 的工作原理, 先看看 init 方法里初始化做了什么
// RouteProcessor.java
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);

        // 用来生成代码的 Filer 对象
        mFiler = processingEnv.getFiler(); 
        
        // 数据类型和类的描述
        types = processingEnv.getTypeUtils();            // Get type utils.
        elements = processingEnv.getElementUtils();      // Get class meta.
        typeUtils = new TypeUtils(types, elements);

        ...
        // module name, 使用 arouter-compiler 的module 都要配置在build.gradle, 否则抛出 exception
        moduleName = options.get(KEY_MODULE_NAME);

        // 获取 com.alibaba.android.arouter.facade.template.IProvider 的 TypeMirror 对象
        iProvider = elements.getTypeElement(Consts.IPROVIDER).asType();
    }

接下来我们进入核心的 process 方法, init, process 这两个方法都是 Processor 自动调用的

// RouteProcessor.java
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        ...
        Set<? extends Element> routeElements = roundEnv.getElementsAnnotatedWith(Route.class);
        this.parseRoutes(routeElements);
        ...
    }

主要逻辑都在 parseRoutes 这个方法中, 继续查看下去, 这个方法写的有点长...

    private void parseRoutes(Set<? extends Element> routeElements) throws IOException {
            // 创建参数类型 Map<String, Class<? extends IRouteGroup>>
            ParameterizedTypeName inputMapTypeOfRoot = ParameterizedTypeName.get(
                    ClassName.get(Map.class),
                    ClassName.get(String.class),
                    ParameterizedTypeName.get(
                            ClassName.get(Class.class),
                            WildcardTypeName.subtypeOf(ClassName.get(type_IRouteGroup))
                    )
            );

            // 创建参数类型 Map<String, RouteMeta>
            ParameterizedTypeName inputMapTypeOfGroup = ParameterizedTypeName.get(
                    ClassName.get(Map.class),
                    ClassName.get(String.class),
                    ClassName.get(RouteMeta.class)
            );

            // 创建方法参数 routes, atlas, providers
            ParameterSpec rootParamSpec = ParameterSpec.builder(inputMapTypeOfRoot, "routes").build();
            ParameterSpec groupParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "atlas").build();
            ParameterSpec providerParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "providers").build();

            // 声明 public void loadInto(Map<String, Class<? extends IRouteGroup>> routes)
            MethodSpec.Builder loadIntoMethodOfRootBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                    .addAnnotation(Override.class)
                    .addModifiers(PUBLIC)
                    .addParameter(rootParamSpec);

            // 遍历所有申明的 @Route 
            for (Element element : routeElements) {
                // 取得 class 的类型描述
                TypeMirror tm = element.asType();
                
                // 取得@Route注解的实例
                Route route = element.getAnnotation(Route.class);
                RouteMeta routeMete = null;

                // 判断class 是否继承自 Activity
                if (types.isSubtype(tm, type_Activity)) {
                    Map<String, Integer> paramsType = new HashMap<>();
                    
                    // 遍历所有声明 @Autowired 的属性
                    for (Element field : element.getEnclosedElements()) {
                        // 满足条件是属性, 声明了 @Autowired, 并且没有实现 iProvider
                        if (field.getKind().isField() && field.getAnnotation(Autowired.class) != null && !types.isSubtype(field.asType(), iProvider)) {

                            Autowired paramConfig = field.getAnnotation(Autowired.class);

                            // 根据 @Autowired 声明的名字做key, 缓存数据类型的 index 值
                            paramsType.put(StringUtils.isEmpty(paramConfig.name()) ? field.getSimpleName().toString() : paramConfig.name(), typeUtils.typeExchange(field));
                        }
                    }

                    // 创建 RouteMeta 对象
                    routeMete = new RouteMeta(route, element, RouteType.ACTIVITY, paramsType);
                } else if (types.isSubtype(tm, iProvider)) {         // IProvider
                    routeMete = new RouteMeta(route, element, RouteType.PROVIDER, null);
                } else if (types.isSubtype(tm, type_Service)) {           // Service
                    routeMete = new RouteMeta(route, element, RouteType.parse(SERVICE), null);
                } else if (types.isSubtype(tm, fragmentTm) || types.isSubtype(tm, fragmentTmV4)) {
                    routeMete = new RouteMeta(route, element, RouteType.parse(FRAGMENT), null);
                }
              
                // 这个方法就是把 route 按照 group 分组保存到 groupMap( Map<String, Set<RouteMeta>> )
                categories(routeMete);
            }
            

            // 声明这个方法 public void loadInto(Map<String, RouteMeta> providers)
            MethodSpec.Builder loadIntoMethodOfProviderBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                    .addAnnotation(Override.class)
                    .addModifiers(PUBLIC)
                    .addParameter(providerParamSpec);

            // 开始生成代码, 遍历刚才生成的 groupMap
            for (Map.Entry<String, Set<RouteMeta>> entry : groupMap.entrySet()) {
                String groupName = entry.getKey();
              
                // 声明方法 public void loadInto(Map<String, RouteMeta> atlas)
                MethodSpec.Builder loadIntoMethodOfGroupBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                        .addAnnotation(Override.class)
                        .addModifiers(PUBLIC)
                        .addParameter(groupParamSpec);

                // 遍历 group 内保存的 routes
                Set<RouteMeta> groupData = entry.getValue();
                for (RouteMeta routeMeta : groupData) {
                    switch (routeMeta.getType()) {
                        case PROVIDER: 
                            List<? extends TypeMirror> interfaces = ((TypeElement) routeMeta.getRawType()).getInterfaces();

                            // 生成方法内代码, 例如下面
                            // providers.put("com.alibaba.android.arouter.demo.testservice.HelloService", RouteMeta.build(RouteType.PROVIDER, HelloServiceImpl.class, "/service/hello", "service", null, -1, -2147483648));
                            for (TypeMirror tm : interfaces) {
                                if (types.isSameType(tm, iProvider)) {
                                    loadIntoMethodOfProviderBuilder.addStatement(
                                            "providers.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, null, " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))",
                                            (routeMeta.getRawType()).toString(),
                                            routeMetaCn,
                                            routeTypeCn,
                                            ClassName.get((TypeElement) routeMeta.getRawType()),
                                            routeMeta.getPath(),
                                            routeMeta.getGroup());
                                } else if (types.isSubtype(tm, iProvider)) {
                                    loadIntoMethodOfProviderBuilder.addStatement(
                                            "providers.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, null, " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))",
                                            tm.toString(), 
                                            routeMetaCn,
                                            routeTypeCn,
                                            ClassName.get((TypeElement) routeMeta.getRawType()),
                                            routeMeta.getPath(),
                                            routeMeta.getGroup());
                                }
                            }
                            break;
                        default:
                            break;
                    }

                    // 生成配置的参数类型 map
                    // 例如 : put("pac", 9); put("obj", 10); put("name", 8); put("boy", 0);
                    StringBuilder mapBodyBuilder = new StringBuilder();
                    Map<String, Integer> paramsType = routeMeta.getParamsType();
                    if (MapUtils.isNotEmpty(paramsType)) {
                        for (Map.Entry<String, Integer> types : paramsType.entrySet()) {
                            mapBodyBuilder.append("put(\"").append(types.getKey()).append("\", ").append(types.getValue()).append("); ");
                        }
                    }
                    String mapBody = mapBodyBuilder.toString();

                    // 生成方法内代码
                    // atlas.put("/test/activity2", RouteMeta.build(RouteType.ACTIVITY, Test2Activity.class, "/test/activity2", "test", new java.util.HashMap<String, Integer>(){{put("key1", 8); }}, -1, -2147483648));
                    loadIntoMethodOfGroupBuilder.addStatement(
                            "atlas.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, " + (StringUtils.isEmpty(mapBody) ? null : ("new java.util.HashMap<String, Integer>(){{" + mapBodyBuilder.toString() + "}}")) + ", " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))",
                            routeMeta.getPath(),
                            routeMetaCn,
                            routeTypeCn,
                            ClassName.get((TypeElement) routeMeta.getRawType()),
                            routeMeta.getPath().toLowerCase(),
                            routeMeta.getGroup().toLowerCase());
                }

                // 生成 java 文件, 例如 ARouter$$Group$$test.java, ARouter$$Group$$service.java
                String groupFileName = NAME_OF_GROUP + groupName;
                JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
                        TypeSpec.classBuilder(groupFileName)
                                .addJavadoc(WARNING_TIPS)
                                .addSuperinterface(ClassName.get(type_IRouteGroup))
                                .addModifiers(PUBLIC)
                                .addMethod(loadIntoMethodOfGroupBuilder.build())
                                .build()
                ).build().writeTo(mFiler);

                // 缓存文件名
                rootMap.put(groupName, groupFileName);
            }

            // 生成 ARouter$$Root$$app.java 里的代码
            if (MapUtils.isNotEmpty(rootMap)) {
                for (Map.Entry<String, String> entry : rootMap.entrySet()) {

                    // 生成代码 : routes.put("service", ARouter$$Group$$service.class);
                    loadIntoMethodOfRootBuilder.addStatement("routes.put($S, $T.class)", entry.getKey(), ClassName.get(PACKAGE_OF_GENERATE_FILE, entry.getValue()));
                }
            }

            // 生成 providers java 文件. 例如 ARouter$$Providers$$app.java
            String providerMapFileName = NAME_OF_PROVIDER + SEPARATOR + moduleName;
            JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
                    TypeSpec.classBuilder(providerMapFileName)
                            .addJavadoc(WARNING_TIPS)
                            .addSuperinterface(ClassName.get(type_IProviderGroup))
                            .addModifiers(PUBLIC)
                            .addMethod(loadIntoMethodOfProviderBuilder.build())
                            .build()
            ).build().writeTo(mFiler);

            // 生成 root java 文件. 例如 ARouter$$Root$$app.java
            String rootFileName = NAME_OF_ROOT + SEPARATOR + moduleName;
            JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
                    TypeSpec.classBuilder(rootFileName)
                            .addJavadoc(WARNING_TIPS)
                            .addSuperinterface(ClassName.get(elements.getTypeElement(ITROUTE_ROOT)))
                            .addModifiers(PUBLIC)
                            .addMethod(loadIntoMethodOfRootBuilder.build())
                            .build()
            ).build().writeTo(mFiler);
    }

总结一下RouteProcessor 做了什么事情

  • 根据 @Route 配置 path, group 生成 ARouter$$Root$$"moduleName"
  • ARouter$$Root$$xxxx.java 以 group 为 key, 注入了生成的 ARouter$$Group$$xxxx 类
  • 根据 @Route 配置 生成 ARouter$$Providers$$"groupName" 和 ARouter$$Group$$"groupName"
  • ARouter$$Providers$$xxxx.java 以类为 key, 注入了 IProvider 接口的实现类
  • ARouter$$Group$$xxxx.java 以 path 为 key, 注入 RouteMate实例, 描述实现类和 @Autowired 参数描述

RouteProcessor 完成 ARouter 大部分工作, 除了拦截器和自动注入

  1. InterceptorProcessor init 方法和 RouteProcessor 类似, 直接跳过, 直接看核心方法
// InterceptorProcessor.java
    private void parseInterceptors(Set<? extends Element> elements) throws IOException {
            for (Element element : elements) {
                // 验证必需实现了 IInterceptor 接口
                if (verify(element)) {
                    Interceptor interceptor = element.getAnnotation(Interceptor.class);
                    
                    // 根据配置 @Interceptor 的 priority, 返回是否有重复配置的拦截器, 如果有抛出错误
                    Element lastInterceptor = interceptors.get(interceptor.priority());
                    if (null != lastInterceptor) {
                        throw new IllegalArgumentException...
                    }

                    interceptors.put(interceptor.priority(), element);
                }
            }

            // 声明类型 Map<Integer, Class<? extends ITollgate>>
            ParameterizedTypeName inputMapTypeOfTollgate = ParameterizedTypeName.get(
                    ClassName.get(Map.class),
                    ClassName.get(Integer.class),
                    ParameterizedTypeName.get(
                            ClassName.get(Class.class),
                            WildcardTypeName.subtypeOf(ClassName.get(type_ITollgate))
                    )
            );

            // 方法参数 interceptors
            ParameterSpec tollgateParamSpec = ParameterSpec.builder(inputMapTypeOfTollgate, "interceptors").build();

            // 声明方法 loadInto
            MethodSpec.Builder loadIntoMethodOfTollgateBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                    .addAnnotation(Override.class)
                    .addModifiers(PUBLIC)
                    .addParameter(tollgateParamSpec);

                // 生成方法内代码, 例如 : interceptors.put(7, Test1Interceptor.class);
                for (Map.Entry<Integer, Element> entry : interceptors.entrySet()) {
                    loadIntoMethodOfTollgateBuilder.addStatement("interceptors.put(" + entry.getKey() + ", $T.class)", ClassName.get((TypeElement) entry.getValue()));
                }

            // // 生成 Interceptor 的注入代码, 继承了 IInterceptorGroup. 例如 ARouter$$Interceptors$$app.java
            JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
                    TypeSpec.classBuilder(NAME_OF_INTERCEPTOR + SEPARATOR + moduleName)
                            .addModifiers(PUBLIC)
                            .addJavadoc(WARNING_TIPS)
                            .addMethod(loadIntoMethodOfTollgateBuilder.build())
                            .addSuperinterface(ClassName.get(type_ITollgateGroup))
                            .build()
            ).build().writeTo(mFiler);
    }

InterceptorProcessor 就是生成注入拦截器的代码, 拦截器有优先级, 并且一个优先级只能一个拦截器

  1. AutowiredProcessor 负责生成自动注入的代码, 这点 butterknife 原理差不多
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        categories(roundEnvironment.getElementsAnnotatedWith(Autowired.class));
        generateHelper();
    }
    private void categories(Set<? extends Element> elements) throws IllegalAccessException {
            for (Element element : elements) {
                TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
                
                // @Autowired不能修饰 private
                if (element.getModifiers().contains(Modifier.PRIVATE)) {
                    throw new IllegalAccessException("The autowired fields CAN NOT BE 'private'!!! please check field ["
                            + element.getSimpleName() + "] in class [" + enclosingElement.getQualifiedName() + "]");
                }

                // 根据声明的类分组缓存
                if (parentAndChild.containsKey(enclosingElement)) {
                    parentAndChild.get(enclosingElement).add(element);
                } else {
                    List<Element> childs = new ArrayList<>();
                    childs.add(element);
                    parentAndChild.put(enclosingElement, childs);
                }
            }
    }
    private void generateHelper() throws IOException, IllegalAccessException {
        // 声明方法参数 Object target
        ParameterSpec objectParamSpec = ParameterSpec.builder(TypeName.OBJECT, "target").build();

            // 遍历前面分析后的 parentAndChild
            for (Map.Entry<TypeElement, List<Element>> entry : parentAndChild.entrySet()) {

                // 声明方法 public void inject(Object target)
                MethodSpec.Builder injectMethodBuilder = MethodSpec.methodBuilder(METHOD_INJECT)
                        .addAnnotation(Override.class)
                        .addModifiers(PUBLIC)
                        .addParameter(objectParamSpec);

                TypeElement parent = entry.getKey();
                List<Element> childs = entry.getValue();

                String qualifiedName = parent.getQualifiedName().toString();
                String packageName = qualifiedName.substring(0, qualifiedName.lastIndexOf("."));

                // 类名 : com.alibaba.android.arouter.demo.testactivity.Test1Activity
                // 生成 : com.alibaba.android.arouter.demo.testactivity.Test1Activity$$ARouter$$Autowired
                String fileName = parent.getSimpleName() + NAME_OF_AUTOWIRED;

                // 声明 public class Test1Activity$$ARouter$$Autowired implements ISyringe
                TypeSpec.Builder helper = TypeSpec.classBuilder(fileName)
                        .addJavadoc(WARNING_TIPS)
                        .addSuperinterface(ClassName.get(type_ISyringe))
                        .addModifiers(PUBLIC);

                // 声明变量 private SerializationService serializationService;
                FieldSpec jsonServiceField = FieldSpec.builder(TypeName.get(type_JsonService.asType()), "serializationService", Modifier.PRIVATE).build();
                helper.addField(jsonServiceField);
  
                // 生成代码 : serializationService = (SerializationService)ARouter.getInstance().navigation(SerializationService.class);
                injectMethodBuilder.addStatement("serializationService = $T.getInstance().navigation($T.class);", ARouterClass, ClassName.get(type_JsonService));

                // 生成代码 : Test1Activity substitute = (Test1Activity)target;
                injectMethodBuilder.addStatement("$T substitute = ($T)target", ClassName.get(parent), ClassName.get(parent));

                // 生成自动注入的代码
                for (Element element : childs) {
                    Autowired fieldConfig = element.getAnnotation(Autowired.class);
                    String fieldName = element.getSimpleName().toString();
                    if (types.isSubtype(element.asType(), iProvider)) { 
                        if ("".equals(fieldConfig.name())) {

                            // 生成代码 : substitute.helloService = (HelloService)ARouter.getInstance().navigation(HelloService.class);
                            injectMethodBuilder.addStatement(
                                    "substitute." + fieldName + " = $T.getInstance().navigation($T.class)",
                                    ARouterClass,
                                    ClassName.get(element.asType())
                            );
                        } else {
                            // 生成代码 : substitute.helloService = (HelloService)ARouter.getInstance().build("/test/hello").navigation();
                            injectMethodBuilder.addStatement(
                                    "substitute." + fieldName + " = ($T)$T.getInstance().build($S).navigation();",
                                    ClassName.get(element.asType()),
                                    ARouterClass,
                                    fieldConfig.name()
                            );
                        }

                    } else {  
                        String statment = "substitute." + fieldName + " = substitute.";
                        boolean isActivity = false;
                        if (types.isSubtype(parent.asType(), activityTm)) {
                            isActivity = true;
                            // activity  直接用getIntent()
                            statment += "getIntent().";
                        } else if (types.isSubtype(parent.asType(), fragmentTm) || types.isSubtype(parent.asType(), fragmentTmV4)) {
                            // fragment  直接用getArguments()
                            statment += "getArguments().";
                        } else {
                            throw new IllegalAccessException("The field [" + fieldName + "] need autowired from intent, its parent must be activity or fragment!");
                        }
        
                        // 生成注入代码
                        statment = buildStatement(statment, typeUtils.typeExchange(element), isActivity);

                        // 判断serializationService注入
                        if (statment.startsWith("serializationService.")) { 
                            injectMethodBuilder.beginControlFlow("if (null != serializationService)");
                            injectMethodBuilder.addStatement(
                                    "substitute." + fieldName + " = " + statment,
                                    (StringUtils.isEmpty(fieldConfig.name()) ? fieldName : fieldConfig.name()),
                                    ClassName.get(element.asType())
                            );
                            injectMethodBuilder.nextControlFlow("else");
                            injectMethodBuilder.addStatement(
                                    "$T.e(\"" + Consts.TAG +  "\", \"You want automatic inject the field '" + fieldName + "' in class '$T' , then you should implement 'SerializationService' to support object auto inject!\")", AndroidLog, ClassName.get(parent));
                            injectMethodBuilder.endControlFlow();
                        } else {
                            injectMethodBuilder.addStatement(statment, StringUtils.isEmpty(fieldConfig.name()) ? fieldName : fieldConfig.name());
                        }
                    }
                }


                helper.addMethod(injectMethodBuilder.build());

                // 生成 Autowired 的注入代码, 继承了 ISyringe. 例如 Test1Activity$$ARouter$$Autowired.java
                JavaFile.builder(packageName, helper.build()).build().writeTo(mFiler);
            }
    }

ARouter 总结

我们已经底 ARouter 大部分代码都进行了一遍分析, 接下来总结一下 ARouter 工作原理.

编译期 arouter-compiler 负责生成了注入的的一些代码:

  • ARouter$$Group$$group-name 以 group-name 为文件名注入该 group 下声明了 @Route 的信息
  • ARouter$$Root$$app 按照 group 分组注入了 ARouter$$Group$$group-name
  • ARouter$$Providers$$app 注入实现 IProvider 接口的信息
  • ARouter$$Interceptors$$app 注入实现 IInterceptor 接口信息
  • Test1Activity$$ARouter$$Autowired @Autowired 自动注入的实现

ARouter 初始化的时候会把注入的信息进行缓存

在进行 navigation 的时候, 根据缓存进行懒加载, 然后获取实际对象或者跳转 activity.

自动注入就是调用 对应的 Test1Activity$$ARouter$$Autowired 实例, 对声明 @Autowired 的字段进行复制操作.

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

推荐阅读更多精彩内容