ARouter原理解析

ARouter 是一个用于帮助 Android App 进行组件化改造的框架 —— 支持模块间的路由、通信、解耦
https://github.com/alibaba/ARouter/blob/master/README_CN.md

说到路由便不得不提一下Android中的组件化开发思想,组件化是最近比较流行的架构设计方案,它能对代码进行高度的解耦、模块分离等,
极大地提高开发效率。路由和组件化本身没有什么联系,因为路由的责任是负责页面跳转,但是组件化中两个单向依赖的module之间需要互相启动对方的Activity,
因为没有相互引用,startActivity()是实现不了的,必须需要一个协定的通信方式,此时类似ARouter的路由框架就派上用场了。

ARouter实现的核心方式有三个,不了解的查一下资料

  • 自定义注解
  • 注解处理器(APT,用来在编译时扫描和处理注解)
  • JavaPoet(用来生成代码)

不只是ARouter,像如ButterKnife、Dagger、JsBridge等大型框架使用的也是这个方式。

创建注解

Route注解用来声明路由路径,路径至少是两级:

//元注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Route {
    //路由的路径,标识一个路由节点
    String path();
    //将路由节点进行分组,可以实现按组动态加载
    String group() default "";
}

Extra注解用来声明额外信息,实现数据在模块间的传递:

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.CLASS)
public @interface Extra {
    String name() default "";
}

RouteMeta用来保存路由信息,通过解析Route注解获取。路由页面有两种类型:1.Activity,代表需要路由的Activity;2.ISERVICE,代表可以传递的ISERVICE对象,实现了ISERVICE的接口,都可以通过路由来传递。

public class RouteMeta {
    //路由页面的类型:Activity或者IService
    public enum Type {
        ACTIVITY,
        ISERVICE
    }

    private Type type;
    //节点 (Activity)
    private Element element;
    //注解使用的类对象
    private Class<?> destination;
    //路由地址
    private String path;
    //路由分组
    private String group;
    ...
}

注解处理

compiler模块使用Javax的Processor来处理注解,使用google的AutoService来自动编译Processor类,使用javapoet来生成Java类。Javax是Java的库,Android不支持,所以compiler模块是Java的library模块。

apply plugin: 'java-library'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
    compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
    implementation 'com.squareup:javapoet:1.11.1'

    implementation project(':router_annotation')
}

// java控制台输出中文乱码
tasks.withType(JavaCompile) {
    options.encoding = "UTF-8"
}
//Android对Java1.8的兼容性不好,所以指定1.7版本
sourceCompatibility = "7"
targetCompatibility = "7"

声明一个RouteProcessor继承Processor,用来处理Route注解。

/**
 * 在这个类上添加了@AutoService注解,它的作用是用来生成
 * META-INF/services/javax.annotation.processing.Processor文件的,
 * 也就是我们在使用注解处理器的时候需要手动添加
 * META-INF/services/javax.annotation.processing.Processor,
 * 而有了@AutoService后它会自动帮我们生成。
 * AutoService是Google开发的一个库,使用时需要在
 * factory-compiler中添加依赖
 */
@AutoService(Processor.class)   //注册注解处理器
/**
 * 处理器接收的参数 替代 {@link AbstractProcessor#getSupportedOptions()} 函数
 */
@SupportedOptions(Consts.ARGUMENTS_NAME)
/**
 * 指定使用的Java版本 替代 {@link AbstractProcessor#getSupportedSourceVersion()} 函数
 * 声明我们注解支持的JDK的版本
 */
@SupportedSourceVersion(SourceVersion.RELEASE_7)
/**
 * 注册给哪些注解的  替代 {@link AbstractProcessor#getSupportedAnnotationTypes()} 函数
 * 声明我们要处理哪一些注解 该方法返回字符串的集合表示该处理器用于处理哪些注解
 */
@SupportedAnnotationTypes({Consts.ANN_TYPE_ROUTE})
public class RouteProcessor extends AbstractProcessor {
    /**
     * key:组名 value:类名
     */
    private Map<String, String> rootMap = new TreeMap<>();
    /**
     * 分组 key:组名 value:对应组的路由信息
     */
    private Map<String, List<RouteMeta>> groupMap = new HashMap<>();
    /**
     * 节点工具类 (类、函数、属性都是节点)
     */
    private Elements elementUtils;

    /**
     * type(类信息)工具类
     */
    private Types typeUtils;
    /**
     * 文件生成器 类/资源
     */
    private Filer filerUtils;
    /**
     * 参数
     */
    private String moduleName;

    private Log log;

    /**
     * 初始化 从 {@link ProcessingEnvironment} 中获得一系列处理器工具
     *
     * @param processingEnvironment
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        //获得apt的日志输出
        log = Log.newLog(processingEnvironment.getMessager());
        log.i("init()");
        elementUtils = processingEnv.getElementUtils();
        typeUtils = processingEnvironment.getTypeUtils();
        filerUtils = processingEnv.getFiler();
        //参数是模块名 为了防止多模块/组件化开发的时候 生成相同的 xx$$ROOT$$文件
        Map<String, String> options = processingEnv.getOptions();
        if (!Utils.isEmpty(options)) {
            moduleName = options.get(Consts.ARGUMENTS_NAME);
        }
        log.i("RouteProcessor Parmaters:" + moduleName);
        if (Utils.isEmpty(moduleName)) {
            throw new RuntimeException("Not set Processor Parmaters.");
        }
    }

    /**
     * 相当于main函数,正式处理注解
     *
     * @param set 使用了支持处理注解  的节点集合
     * @param roundEnvironment 表示当前或是之前的运行环境,可以通过该对象查找找到的注解。
     * @return true 表示后续处理器不会再处理(已经处理)
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {

        //使用了需要处理的注解
        if (!Utils.isEmpty(set)) {
            //获取所有被 Route 注解的元素集合
            Set<? extends Element> routeElements = roundEnvironment.getElementsAnnotatedWith
                    (Route.class);
            //处理 Route 注解
            if (!Utils.isEmpty(routeElements)) {
                try {
                    log.i("Route Class: ===" + routeElements.size());
                    parseRoutes(routeElements);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return true;
        }
        return false;
    }

    private void parseRoutes(Set<? extends Element> routeElements) throws IOException {
        //支持配置路由类的类型
        TypeElement activity = elementUtils.getTypeElement(Consts.ACTIVITY);
        //节点自描述 Mirror
        TypeMirror type_Activity = activity.asType();
        log.i("Route Class: ===" + type_Activity);
        TypeElement iService = elementUtils.getTypeElement(Consts.ISERVICE);
        TypeMirror type_IService = iService.asType();

        /**
         * groupMap(组名:路由信息)集合
         */
        //声明 Route 注解的节点 (需要处理的节点 Activity/IService)
        for (Element element : routeElements) {
            //路由信息
            RouteMeta routeMeta;
            // 使用Route注解的类信息
            TypeMirror tm = element.asType();
            log.i("Route Class: " + tm.toString());
            Route route = element.getAnnotation(Route.class);
            //是否是 Activity 使用了Route注解
            if (typeUtils.isSubtype(tm, type_Activity)) {
                routeMeta = new RouteMeta(RouteMeta.Type.ACTIVITY, route, element);
            } else if (typeUtils.isSubtype(tm, type_IService)) {
                routeMeta = new RouteMeta(RouteMeta.Type.ISERVICE, route, element);
            } else {
                throw new RuntimeException("[Just Support Activity/IService Route] :" + element);
            }
            //分组信息记录  groupMap <Group分组,RouteMeta路由信息> 集合
            categories(routeMeta);
        }

       //生成类需要实现的接口
        TypeElement iRouteGroup = elementUtils.getTypeElement(Consts.IROUTE_GROUP);
        log.i("---------" + iRouteGroup.getSimpleName());
        TypeElement iRouteRoot = elementUtils.getTypeElement(Consts.IROUTE_ROOT);

        /**
         *  生成Group类 作用:记录 <地址,RouteMeta路由信息(Class文件等信息)>
         */
        generatedGroup(iRouteGroup);
        /**
         * 生成Root类 作用:记录 <分组,对应的Group类>
         */
        generatedRoot(iRouteRoot, iRouteGroup);
    }

    private void generatedGroup(TypeElement iRouteGroup) throws IOException {
        //参数  Map<String,RouteMeta>
        ParameterizedTypeName atlas = ParameterizedTypeName.get(
                ClassName.get(Map.class),
                ClassName.get(String.class),
                ClassName.get(RouteMeta.class)
        );
        //参数 Map<String,RouteMeta> atlas
        ParameterSpec groupParamSpec = ParameterSpec.builder(atlas, "atlas")
                .build();

        //遍历分组,每一个分组创建一个 $$Group$$ 类
        for (Map.Entry<String, List<RouteMeta>> entry : groupMap.entrySet()) {
            /**
             * 类成员函数loadInfo声明构建
             */
            //函数 public void loadInfo(Map<String,RouteMeta> atlas)
            MethodSpec.Builder loadIntoMethodOfGroupBuilder = MethodSpec.methodBuilder
                    (Consts.METHOD_LOAD_INTO)
                    .addAnnotation(Override.class)
                    .addModifiers(PUBLIC)
                    .addParameter(groupParamSpec);

            //分组名 与 对应分组中的信息
            String groupName = entry.getKey();
            List<RouteMeta> groupData = entry.getValue();
            //遍历分组中的条目 数据
            for (RouteMeta routeMeta : groupData) {
                // 组装函数体:
                // atlas.put(地址,RouteMeta.build(Class,path,group))
                // $S https://github.com/square/javapoet#s-for-strings
                // $T https://github.com/square/javapoet#t-for-types
                loadIntoMethodOfGroupBuilder.addStatement(
                        "atlas.put($S, $T.build($T.$L,$T.class, $S, $S))",
                        routeMeta.getPath(),
                        ClassName.get(RouteMeta.class),
                        ClassName.get(RouteMeta.Type.class),
                        routeMeta.getType(),
                        ClassName.get((TypeElement) routeMeta.getElement()),
                        routeMeta.getPath().toLowerCase(),
                        routeMeta.getGroup().toLowerCase());
            }
            // 创建java文件($$Group$$)  组
            String groupClassName = Consts.NAME_OF_GROUP + groupName;
            JavaFile.builder(Consts.PACKAGE_OF_GENERATE_FILE,
                    TypeSpec.classBuilder(groupClassName)
                            .addSuperinterface(ClassName.get(iRouteGroup))
                            .addModifiers(PUBLIC)
                            .addMethod(loadIntoMethodOfGroupBuilder.build())
                            .build()
            ).build().writeTo(filerUtils);
            log.i("Generated RouteGroup: " + Consts.PACKAGE_OF_GENERATE_FILE + "." +
                    groupClassName);
            //分组名和生成的对应的Group类类名
            rootMap.put(groupName, groupClassName);
        }
    }

    private void generatedRoot(TypeElement iRouteRoot, TypeElement iRouteGroup) throws IOException {
        //类型 Map<String,Class<? extends IRouteGroup>> routes>
        //Wildcard 通配符
        ParameterizedTypeName routes = ParameterizedTypeName.get(
                ClassName.get(Map.class),
                ClassName.get(String.class),
                ParameterizedTypeName.get(
                        ClassName.get(Class.class),
                        WildcardTypeName.subtypeOf(ClassName.get(iRouteGroup))
                )
        );

        //参数 Map<String,Class<? extends IRouteGroup>> routes> routes
        ParameterSpec rootParamSpec = ParameterSpec.builder(routes, "routes")
                .build();
        //函数 public void loadInfo(Map<String,Class<? extends IRouteGroup>> routes> routes)
        MethodSpec.Builder loadIntoMethodOfRootBuilder = MethodSpec.methodBuilder
                (Consts.METHOD_LOAD_INTO)
                .addAnnotation(Override.class)
                .addModifiers(PUBLIC)
                .addParameter(rootParamSpec);

        //函数体
        for (Map.Entry<String, String> entry : rootMap.entrySet()) {
            loadIntoMethodOfRootBuilder.addStatement("routes.put($S, $T.class)", entry
                    .getKey(), ClassName.get(Consts.PACKAGE_OF_GENERATE_FILE, entry.getValue
                    ()));
        }
        //生成 $Root$类
        String rootClassName = Consts.NAME_OF_ROOT + moduleName;
        JavaFile.builder(Consts.PACKAGE_OF_GENERATE_FILE,
                TypeSpec.classBuilder(rootClassName)
                        .addSuperinterface(ClassName.get(iRouteRoot))
                        .addModifiers(PUBLIC)
                        .addMethod(loadIntoMethodOfRootBuilder.build())
                        .build()
        ).build().writeTo(filerUtils);
        log.i("Generated RouteRoot: " + Consts.PACKAGE_OF_GENERATE_FILE + "." + rootClassName);
    }

    private void categories(RouteMeta routeMeta) {
        if (routeVerify(routeMeta)) {
            log.i("Group Info, Group Name = " + routeMeta.getGroup() + ", Path = " +
                    routeMeta.getPath());
            List<RouteMeta> routeMetas = groupMap.get(routeMeta.getGroup());
            //如果未记录分组则创建
            if (Utils.isEmpty(routeMetas)) {
                List<RouteMeta> routeMetaSet = new ArrayList<>();
                routeMetaSet.add(routeMeta);
                groupMap.put(routeMeta.getGroup(), routeMetaSet);
            } else {
                routeMetas.add(routeMeta);
            }
        } else {
            log.i("Group Info Error: " + routeMeta.getPath());
        }
    }

    /**
     * 验证路由信息必须存在path(并且设置分组)
     *
     * @param meta raw meta
     */
    private boolean routeVerify(RouteMeta meta) {
        String path = meta.getPath();
        String group = meta.getGroup();
        //路由地址必须以 / 开头
        if (Utils.isEmpty(path) || !path.startsWith("/")) {
            return false;
        }
        //如果没有设置分组,以第一个 / 后的节点为分组(所以必须path两个/)
        if (Utils.isEmpty(group)) {
            String defaultGroup = path.substring(1, path.indexOf("/", 1));
            if (Utils.isEmpty(defaultGroup)) {
                return false;
            }
            meta.setGroup(defaultGroup);
            return true;
        }
        return true;
    }
}

在process方法(注解的处理方法)中遍历被Route注解的节点,先判断节点的类型,再使用RouteMeta保存节点信息,然后验证节点的路由地址是否符合规则,然后使用categories() 根据分组名来保存节点信息到groupMap中;在process方法(注解的处理方法)中遍历被Route注解的节点,先判断节点的类型,再使用RouteMeta保存节点信息,然后验证节点的路由地址是否符合规则,然后使用categories() 根据分组名来保存节点信息到groupMap中;

然后,遍历groupMap中的节点信息,使用javapoet工具,根据分组名生成一个继承IRouteGroup接口的Java类,叫做分组信息类,用来保存每个分组的路由信息,比如:

public class ToolRouter$$Group$$demo1 implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/demo1/activityToolRouterDemo1", RouteMeta.build(RouteMeta.Type.ACTIVITY,ActivityToolRouterDemo1.class, "/demo1/activitytoolrouterdemo1", "demo1"));
    atlas.put("/demo1/activityToolRouterDemo2", RouteMeta.build(RouteMeta.Type.ACTIVITY,ActivityToolRouterDemo2.class, "/demo1/activitytoolrouterdemo2", "demo1"));
  }
}

根据模块名生成一个继承IRouteRoot接口的Java类,叫做表信息类,用来保存所有的分组信息类,比如:

public class ToolRouter$$Root$$DemoComponent1 implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("demo1", ToolRouter$$Group$$demo1.class);
    routes.put("main", ToolRouter$$Group$$main.class);
  }
}

Extra注解同理,生成下面类:

public class ActivityToolRouterDemo1$$Extra implements IExtra {
    @Override
    public void loadExtra(Object target) {
        ActivityToolRouterDemo1 t = (ActivityToolRouterDemo1) target;
        t.testService1 = (TestService) ToolRouter.getInstance().build("/main/service1").navigation();
        t.testService2 = (TestService) ToolRouter.getInstance().build("/main2/service2").navigation();
    }
}

初始化路由表

定义一个路由表Warehouse,来保存所有的路由信息。

public class Warehouse {
    // root 映射表 保存分组信信息类
    static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();
    // group 映射表 保存所有的路由页面
    static Map<String, RouteMeta> routes = new HashMap<>();
    // group 映射表 保存所有的IService对象
    static Map<Class, IService> services = new HashMap<>();
}

然后,遍历所有的dex文件,查找所有的使用javapoet生成的分组信息类和表信息类。查找是一个耗时操作,使用ThreadPoolExecutor维护一个线程池,来查找所有包名匹配的类,并使用使用同步计数器来判断查找是否完成。

   public static Set<String> getFileNameByPackageName(Application context, final String
            packageName) throws PackageManager.NameNotFoundException, IOException,
            InterruptedException {
        final Set<String> classNames = new HashSet<>();
        List<String> paths = getSourcePaths(context);
        //使用同步计数器判断均处理完成
        final CountDownLatch parserCtl = new CountDownLatch(paths.size());
        ThreadPoolExecutor threadPoolExecutor = DefaultPoolExecutor.newDefaultPoolExecutor(paths
                .size());
        for (final String path : paths) {
            threadPoolExecutor.execute(new Runnable() {

                @Override
                public void run() {
                    DexFile dexfile = null;
                    try {
                        //加载 apk中的dex 并遍历 获得所有包名为 {packageName} 的类
                        dexfile = new DexFile(path);
                        Enumeration<String> dexEntries = dexfile.entries();
                        while (dexEntries.hasMoreElements()) {
                            String className = dexEntries.nextElement();
                            if (className.startsWith(packageName)) {
                                classNames.add(className);
                            }
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    } finally {
                        ...
                        //释放1个
                        parserCtl.countDown();
                    }
                }
            });
        }
        //等待执行完成
        parserCtl.await();
        return classNames;
    }

    ```

然后调用表信息类的loadInfo方法,将所有的分组信息类存放到Warehouse的groupsIndex中。

```java
private static void loadInfo()  {
    //获得所有 apt生成的路由类的全类名 (路由表)
    Set<String> routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
    for (String className : routerMap) {
        if (className.startsWith(ROUTE_ROOT_PAKCAGE + "." + SDK_NAME + SEPARATOR +
                SUFFIX_ROOT)) {
            // root中注册的是分组信息 将分组信息加入仓库中
            ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto
                    (Warehouse.groupsIndex);
        }
    }
}

创建跳卡

先根据路由地址生成一个继承RouteMeta的跳卡Postcard,在Postcard中保存路由地址、分组名以及要传递的数据。

调用Postcard的withXXX方法,来添加要传递的数据,保存Postcard的一个Bundle对象中,比如:

    public Postcard withString(@Nullable String key, @Nullable String value) {
        mBundle.putString(key, value);
        return this;
    }


    public Postcard withBoolean(@Nullable String key, boolean value) {
        mBundle.putBoolean(key, value);
        return this;
    }

然后调用Postcard的navigation()进行页面跳转。

跳转页面

调用Postcard的navigation()进行页面跳转时,如果是activity页面,就直接调转;如果是IService页面,就返回这个IService对象。

先根据分组名从Warehouse的groupsIndex中找到分组信息类,创建这个类,调用它的loadInfo(),把这个分组所有的路由页面,按照路由路径添加到Warehouse.routes中。

//创建并调用 loadInto 函数,然后记录在仓库
Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(card
        .getGroup());
if (null == groupMeta) {
    throw new NoRouteFoundException("没找到对应路由: " + card.getGroup() + " " +
            card.getPath());
}
IRouteGroup iGroupInstance;
try {
    iGroupInstance = groupMeta.getConstructor().newInstance();
} catch (Exception e) {
    throw new RuntimeException("路由分组映射表记录失败.", e);
}
iGroupInstance.loadInto(Warehouse.routes);
//已经准备过了就可以移除了 (不会一直存在内存中)
Warehouse.groupsIndex.remove(card.getGroup());

然后根据路由路径查找路由页面的的class对象,保存在Postcard中。如果路由页面是IService实现类,还要将它保存在Postcard和Warehouse的services中。

//路由页面的class对象类
card.setDestination(routeMeta.getDestination());
//设置路由页面的类型(activity 或IService实现类)
card.setType(routeMeta.getType());
...
Class<?> destination = routeMeta.getDestination();
IService service = Warehouse.services.get(destination);
if (null == service) {
    try {
        service = (IService) destination.getConstructor().newInstance();
        Warehouse.services.put(destination, service);
    } catch (Exception e) {
        e.printStackTrace();
    }
}
card.setService(service);

然后,进行跳转。如果路由页面是activity,就根据路由页面的class对象生成Intent,并且添加需要传递的数据,再调用startActivity()就可以实现跳转了。如果路由页面是IService,就返回postcard.getService()。

switch (postcard.getType()) {
case ACTIVITY:
    final Context currentContext = null == context ? mContext : context;
    final Intent intent = new Intent(currentContext, postcard.getDestination());
    intent.putExtras(postcard.getExtras());
    int flags = postcard.getFlags();
    if (-1 != flags) {
        intent.setFlags(flags);
    } else if (!(currentContext instanceof Activity)) {
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    }
    new Handler(Looper.getMainLooper()).post(new Runnable() {
        @Override
        public void run() {
            //可能需要返回码
            if (requestCode > 0) {
                ActivityCompat.startActivityForResult((Activity) currentContext, intent,
                        requestCode, postcard.getOptionsBundle());
            } else {
                ActivityCompat.startActivity(currentContext, intent, postcard
                        .getOptionsBundle());
            }

            if ((0 != postcard.getEnterAnim() || 0 != postcard.getExitAnim()) &&
                    currentContext instanceof Activity) {
                //老版本
                ((Activity) currentContext).overridePendingTransition(postcard
                                .getEnterAnim()
                        , postcard.getExitAnim());
            }
            //跳转完成
            if (null != callback) {
                callback.onArrival(postcard);
            }
        }
    });
    break;
case ISERVICE:
    return postcard.getService();
default:
    break;
}

接受传递数据

使用调用Postcard的withXXX方法,就可以传递数据。接收传递的数据,需要通过Extra注解实现。

首先,在需要接收路由数据的页面,根据withXXX方法中传递key和数据类型,声明一个成员属性,并添加Extra注解,比如:

    @Extra
    String a;
    @Extra
    int b;
    @Extra
    short c;

然后,调用ToolRouter.getInstance().inject(this)来加载Extra信息。根据Activity的类名+Extra得到Extra信息类的类名,比如:ActivityToolRouterDemo1Extra,然后创建这个Extra信息类的对象,调用它的loadExtra()方法,给Activity的key1赋值。

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

推荐阅读更多精彩内容