ARouter源码解析

1-初始化

ARouter.init()开始,init方法的主要工作就是ARouter实例化,_ARouter的初始化。_ARouter是具体实现类。这里用到了外观模式,将所有API通过ARouter暴露,具体实现则交由_ARouter

//ARouter.java
public static void init(Application application) {
        if (!hasInit) {
            //日志类
            logger = _ARouter.logger;
            _ARouter.logger.info(Consts.TAG, "ARouter init start.");
            //@1._ARouter初始化
            hasInit = _ARouter.init(application);

            if (hasInit) {
                //@4.通过路由开启Service,预解析拦截器
                _ARouter.afterInit();
            }

            _ARouter.logger.info(Consts.TAG, "ARouter init over.");
        }
    }

@1._ARouter的初始化:

protected static synchronized boolean init(Application application) {
        mContext = application;
        //@2.LogisticsCenter初始化
        LogisticsCenter.init(mContext, executor);
        logger.info(Consts.TAG, "ARouter init success!");
        hasInit = true;
        //主线程Looper创建一个Handler
        mHandler = new Handler(Looper.getMainLooper());

        return true;
    }

@2.LogisticsCenter初始化。主要工作就是初始化所有类的map。在编译期通过gradle插件(arouter-compiler)扫描文件,生成路由映射文件,apt生成路由映射关系文件。

public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
        mContext = context;
        executor = tpe;

        try {
            。。。
            if (registerByPlugin) {
                。。。
            } else {
                Set<String> routerMap;
                //debuggable模式或版本更新,重新获取routerMap映射表
                if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
                    logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
                    // These class was generated by arouter-compiler.
                    //遍历arouter-compiler生成的包名下所有文件,读取routerMap路由映射表,将routerMap存储在sp中
                    routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
                    if (!routerMap.isEmpty()) {
                        context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
                    }

                    PackageUtils.updateVersion(context);    // Save new version name when router map update finishes.
                } else {
                    //直接从sp中读取routerMap。set<String>类型,存储的是className
                    logger.info(TAG, "Load router map from cache.");
                    routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
                }

                logger.info(TAG, "Find router map finished, map size = " + routerMap.size() + ", cost " + (System.currentTimeMillis() - startInit) + " ms.");
                startInit = System.currentTimeMillis();
                //@3.遍历routerMap,根据文件类型存储到Warehouse对应的字段。通过反射实例化对象,调用loadInto实现。
                for (String className : routerMap) {
                    if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
                        // This one of root elements, load root.
                        ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
                    } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
                        // Load interceptorMeta
                        ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
                    } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
                        // Load providerIndex
                        ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
                    }
                }
            }

            logger.info(TAG, "Load root element finished, cost " + (System.currentTimeMillis() - startInit) + " ms.");

            if (Warehouse.groupsIndex.size() == 0) {
                logger.error(TAG, "No mapping files were found, check your configuration please!");
            }

            if (ARouter.debuggable()) {
                logger.debug(TAG, String.format(Locale.getDefault(), "LogisticsCenter has already been loaded, GroupIndex[%d], InterceptorIndex[%d], ProviderIndex[%d]", Warehouse.groupsIndex.size(), Warehouse.interceptorsIndex.size(), Warehouse.providersIndex.size()));
            }
        } catch (Exception e) {
            throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
        }
    }

@3.这里存储到Warehose里的主要是三个HashMap,分别是groupsIndex、interceptorsIndex、providersIndex

  • interceptorsIndex-TreeMap实现,根据拦截器的优先级自平衡的有序结构,红黑树实现。key是优先级,value是拦截器类名
  • groupsIndex-key是路由的group,value是该group下的路由映射关系类
  • providersIndex-

存储过程是通过routerMap拿到className,反射生成实例调用其loadInto方法,该方法是APT在@Route注解的类中自动生成的代码

public class ARouter$$Root$$app implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    //key为分组名,即路径的第一段,value为分组中所有的映射关系
    routes.put("service", ARouter$$Group$$service.class);
    routes.put("test", ARouter$$Group$$test.class);
  }
}

//将module中使用@Route注解的activity或Fragment添加到集合中,这里的方法会在之后调用
public class ARouter$$Group$$test implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/test/activity1", RouteMeta.build(RouteType.ACTIVITY, Test1Activity.class, "/test/activity1", "test", new java.util.HashMap<String, Integer>(){{put("pac", 9); put("ch", 5); put("fl", 6); put("obj", 10); put("name", 8); put("dou", 7); put("boy", 0); put("objList", 10); put("map", 10); put("age", 3); put("url", 8); put("height", 3); }}, -1, -2147483648));
    atlas.put("/test/activity2", RouteMeta.build(RouteType.ACTIVITY, Test2Activity.class, "/test/activity2", "test", new java.util.HashMap<String, Integer>(){{put("key1", 8); }}, -1, -2147483648));
    atlas.put("/test/activity3", RouteMeta.build(RouteType.ACTIVITY, Test3Activity.class, "/test/activity3", "test", new java.util.HashMap<String, Integer>(){{put("name", 8); put("boy", 0); put("age", 3); }}, -1, -2147483648));
    atlas.put("/test/activity4", RouteMeta.build(RouteType.ACTIVITY, Test4Activity.class, "/test/activity4", "test", null, -1, -2147483648));
    atlas.put("/test/fragment", RouteMeta.build(RouteType.FRAGMENT, BlankFragment.class, "/test/fragment", "test", null, -1, -2147483648));
    atlas.put("/test/webview", RouteMeta.build(RouteType.ACTIVITY, TestWebview.class, "/test/webview", "test", null, -1, -2147483648));
  }
}

@4._ARouter.afterInit()方法。通过ARouter获取管理拦截器的Service的实例,并开启服务进行拦截器的一些初始化工作。

static void afterInit() {
        // Trigger interceptor init, use byName.
        interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation();
    }

InterceptorServiceImpl服务主要是实例化拦截器,并将实例添加到Warehose的interceptors中

@Route(path = "/arouter/service/interceptor")
public class InterceptorServiceImpl implements InterceptorService {
    private static boolean interceptorHasInit;
    private static final Object interceptorInitLock = new Object();

    @Override
    public void init(final Context context) {
       ... //省略子线程以及同步处理,下面的操作实际是在子线程处理的
        if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {
            for (Map.Entry<Integer, Class<? extends IInterceptor>> entry : Warehouse.interceptorsIndex.entrySet()) {
                Class<? extends IInterceptor> interceptorClass = entry.getValue();
                try {
                    IInterceptor iInterceptor = interceptorClass.getConstructor().newInstance();
                    iInterceptor.init(context);
                    Warehouse.interceptors.add(iInterceptor);
                } catch (Exception ex) {
                    throw new HandlerException(TAG + "ARouter init interceptor error! name = [" + interceptorClass.getName() + "], reason = [" + ex.getMessage() + "]");
                }
            }
        ...
    }
}

总结初始化过程:

  • (1)ARouter.init()-->_ARouter.init()-->LogisticsCenter.init()
  • (2)debuggable()模式或新版本,则从指定的router-compiler生成的目录遍历文件加载路由映射表routerMap,并缓存到SP;否则直接读取SP中的routerMap
  • (3)遍历routerMap,根据className的类型依次初始化并存储到内存中。Warehose对应的三个map。groupsIndex(gourp名-该分组下的所有映射关系类名)、interceptorsIndex(优先级-拦截器名)、providersIndex()
  • (4)_ARouter.afterInit().通过路由打开InterceptorService,遍历Warehose中的interceptorsIndex,实例化对象并存储到Warehose.interceptors


    图1-初始化流程

2-路由跳转

跳转的代码从ARouter.getInstance()-->ARouter.build()

ARouter.getInstance().build("/test/activity").navigation();

ARouter.build()-->_ARouter.getInstance().build()

protected Postcard build(String path) {
        if (TextUtils.isEmpty(path)) {
            throw new HandlerException(Consts.TAG + "Parameter is invalid!");
        } else {
            //PathReplaceService是实现路径动态变化,也就是重定向功能。
            PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
            if (null != pService) {
                //拿到通过PathReplaceService修改后的path
                path = pService.forString(path);
            }
            //@1.extractGroup(path)解析出group名
            return build(path, extractGroup(path));
        }
    }

@1._ARouter.build方法。返回一个Postcard对象用于存储跳转信息。Postcard继承RouteMeta,封装了路由类型、@Autowired属性集合Map(属性名-属性类型)等信息

protected Postcard build(String path, String group) {
        if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {
            throw new HandlerException(Consts.TAG + "Parameter is invalid!");
        } else {
            PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
            if (null != pService) {
                path = pService.forString(path);
            }
            return new Postcard(path, group);
        }
    }

然后调用_ARouter.navigation方法

protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        。。。
        try {
            //@2.查找对应的路由信息
            LogisticsCenter.completion(postcard);
        } catch (NoRouteFoundException ex) {
            logger.warning(Consts.TAG, ex.getMessage());

            if (debuggable()) {
                // Show friendly tips for user.
                runInMainThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(mContext, "There's no route matched!\n" +
                                " Path = [" + postcard.getPath() + "]\n" +
                                " Group = [" + postcard.getGroup() + "]", Toast.LENGTH_LONG).show();
                    }
                });
            }

            if (null != callback) {
                //调用onLost回调
                callback.onLost(postcard);
            } else {
                // 或者调用DegradeService全局降级的onLost处理
                DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);
                if (null != degradeService) {
                    degradeService.onLost(context, postcard);
                }
            }

            return null;
        }

        if (null != callback) {
            callback.onFound(postcard);
        }

        if (!postcard.isGreenChannel()) {   // 需要拦截处理
            //@3.调用InterceptorService拦截
            interceptorService.doInterceptions(postcard, new InterceptorCallback() {
            
                @Override
                public void onContinue(Postcard postcard) {
                    _navigation(context, postcard, requestCode, callback);
                }

                
                @Override
                public void onInterrupt(Throwable exception) {
                    if (null != callback) {
                        callback.onInterrupt(postcard);
                    }

                    logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());
                }
            });
        } else {//不需要拦截处理
            //@3.最终跳转
            return _navigation(context, postcard, requestCode, callback);
        }

        return null;
    }

@2.LogisticsCenter.completion方法。主要工作就是从Warehouse中懒加载路由信息并缓存到Postcard,包括@Autowired注解的参数列表封装到Postecard的mBundle中

public synchronized static void completion(Postcard postcard) {
        if (null == postcard) {
            throw new NoRouteFoundException(TAG + "No postcard!");
        }

        RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
        if (null == routeMeta) {    // Maybe its does't exist, or didn't load.
            Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());  // Load route meta.
            if (null == groupMeta) {
                throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
            } else {
                // Load route and cache it into memory, then delete from metas.
                try {
                    if (ARouter.debuggable()) {
                        logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] starts loading, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
                    }
                    //从Warehouse.groupIndex读取到对应的映射列表。并将列表中的路由加到Warehouse.routes
                    //同时已加载的group从groupIndex中移除
                    IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
                    iGroupInstance.loadInto(Warehouse.routes);
                    Warehouse.groupsIndex.remove(postcard.getGroup());

                    if (ARouter.debuggable()) {
                        logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] has already been loaded, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
                    }
                } catch (Exception e) {
                    throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
                }
                //再次加载
                completion(postcard);   // Reload
            }
        } else {
            //将路由参数存储到Postcard
            postcard.setDestination(routeMeta.getDestination());
            postcard.setType(routeMeta.getType());
            postcard.setPriority(routeMeta.getPriority());
            postcard.setExtra(routeMeta.getExtra());

            Uri rawUri = postcard.getUri();
            if (null != rawUri) {   // Try to set params into bundle.
                Map<String, String> resultMap = TextUtils.splitQueryParameters(rawUri);
                Map<String, Integer> paramsType = routeMeta.getParamsType();

                if (MapUtils.isNotEmpty(paramsType)) {
                    // Set value by its type, just for params which annotation by @Param
                    for (Map.Entry<String, Integer> params : paramsType.entrySet()) {
                        setValue(postcard,
                                params.getValue(),
                                params.getKey(),
                                resultMap.get(params.getKey()));
                    }

                    // Save params name which need auto inject.
                    postcard.getExtras().putStringArray(ARouter.AUTO_INJECT, paramsType.keySet().toArray(new String[]{}));
                }

                // Save raw uri
                postcard.withString(ARouter.RAW_URI, rawUri.toString());
            }

            switch (routeMeta.getType()) {
                case PROVIDER:  // if the route is provider, should find its instance
                    // Its provider, so it must implement IProvider
                    Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
                    IProvider instance = Warehouse.providers.get(providerMeta);
                    if (null == instance) { // There's no instance of this provider
                        IProvider provider;
                        try {
                            provider = providerMeta.getConstructor().newInstance();
                            provider.init(mContext);
                            Warehouse.providers.put(providerMeta, provider);
                            instance = provider;
                        } catch (Exception e) {
                            throw new HandlerException("Init provider failed! " + e.getMessage());
                        }
                    }
                    postcard.setProvider(instance);
                    postcard.greenChannel();    // Provider should skip all of interceptors
                    break;
                case FRAGMENT:
                    postcard.greenChannel();    // Fragment needn't interceptors
                default:
                    break;
            }
        }
    }

@3.跳转_navigation

  • Activity。则构建Intent,将相关信息封装到Intent,包括action、bundle等。在主线程调用startActivity跳转。
  • Fragment。创建Fragment实例,设置bundle参数,返回该Fragment实例
private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        final Context currentContext = null == context ? mContext : context;

        switch (postcard.getType()) {
            case ACTIVITY:
                // 构建intent
                final Intent intent = new Intent(currentContext, postcard.getDestination());
                intent.putExtras(postcard.getExtras());

                // 设置Activity启动模式
                int flags = postcard.getFlags();
                if (-1 != flags) {
                    intent.setFlags(flags);
                } else if (!(currentContext instanceof Activity)) {    // Non activity, need less one flag.
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                }

                // 设置Actions
                String action = postcard.getAction();
                if (!TextUtils.isEmpty(action)) {
                    intent.setAction(action);
                }

                // 主线程调用startActivity跳转
                runInMainThread(new Runnable() {
                    @Override
                    public void run() {
                        startActivity(requestCode, currentContext, intent, postcard, callback);
                    }
                });

                break;
            case PROVIDER:
                return postcard.getProvider();
            case BOARDCAST:
            case CONTENT_PROVIDER:
            case FRAGMENT:
                Class fragmentMeta = postcard.getDestination();
                try {
                    //生成fragment实例
                    Object instance = fragmentMeta.getConstructor().newInstance();
                    //设置bundle参数
                    if (instance instanceof Fragment) {
                        ((Fragment) instance).setArguments(postcard.getExtras());
                    } else if (instance instanceof android.support.v4.app.Fragment) {
                        ((android.support.v4.app.Fragment) instance).setArguments(postcard.getExtras());
                    }
                    //返回fragment实例
                    return instance;
                } catch (Exception ex) {
                    logger.error(Consts.TAG, "Fetch fragment instance error, " + TextUtils.formatStackTrace(ex.getStackTrace()));
                }
            case METHOD:
            case SERVICE:
            default:
                return null;
        }

        return null;
    }

跳转过程总结:封装Postcard -> 查找信息集合,实例化目标类 -> 返回实例或者跳转。

  • (1)ARouter.getInstance().build()-->_ARouter.getInstance().build()
  • (2)PathReplaceService执行path重定向
  • (3)_ARouter.navigation-->LogisticsCenter.completion。
  • (4)Warehouse的routes中取路由参数,获取失败则解析groupsIndex中对应group的路由列表,添加到routes中。
  • (5)添加完毕再次从routes获取,将路由参数封装到Poster并返回
  • (6)启动InterceptorService执行拦截器
  • (7)执行_navigation。若是Activity,则将poster信息封装到Intent,通过在主线程startActivity跳转。若是Fragment,根据poster信息创建Fragment实例并返回


    图2-跳转流程

3-@Interceptor拦截

@4.InterceptorService的doInterceptions进行拦截处理。通过CountDownLatch实现计数阻塞。计数初始值为拦截器个数,每执行一个拦截器操作计数-1。计数为0或timeout则取消线程阻塞

@Override
public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {
    if (null != Warehouse.interceptors && Warehouse.interceptors.size() > 0) {
        ... //省略同步等待初始化
        LogisticsCenter.executor.execute(new Runnable() {
            @Override
            public void run() {
                //拦截器个数作为计数初始值
                CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size());
                try {
                    _excute(0, interceptorCounter, postcard);
                    //阻塞线程直到超时,或者计数归0
                    interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);
                    if (interceptorCounter.getCount() > 0) { //拦截超时
                        callback.onInterrupt(new HandlerException("The interceptor processing timed out."));
                    } else if (null != postcard.getTag()) {  // 被拦截
                        callback.onInterrupt(new HandlerException(postcard.getTag().toString()));
                    } else { //放行
                        callback.onContinue(postcard);
                    }
                } catch (Exception e) {
                    callback.onInterrupt(e);
                }
            }
        });
    } else {
        callback.onContinue(postcard);
    }
}

/**
 * Excute interceptor
 *
 * @param index    current interceptor index
 * @param counter  interceptor counter
 * @param postcard routeMeta
 */
private static void _excute(final int index, final CancelableCountDownLatch counter, final Postcard postcard) {
    //有下一个拦截器
    if (index < Warehouse.interceptors.size()) {
        IInterceptor iInterceptor = Warehouse.interceptors.get(index);
        iInterceptor.process(postcard, new InterceptorCallback() {
            @Override
            public void onContinue(Postcard postcard) {
                // 如果放行,则计数减1,执行后一个拦截器
                counter.countDown();
                _excute(index + 1, counter, postcard); 
            }

            @Override
            public void onInterrupt(Throwable exception) {
                // 拦截,将exception存入postcard的tag字段,计数归零
                postcard.setTag(null == exception ? new HandlerException("No message.") : exception.getMessage());
                counter.cancel();
            });
        }
    }

4-@Autowired参数注解

@Route(path = "/arouter/service/autowired")
public class AutowiredServiceImpl implements AutowiredService {
    private LruCache<String, ISyringe> classCache;
    private List<String> blackList;

    @Override
    public void init(Context context) {
        classCache = new LruCache<>(66);
        blackList = new ArrayList<>();
    }

    @Override
    public void autowire(Object instance) {
        String className = instance.getClass().getName();
        try {
            if (!blackList.contains(className)) {
                ISyringe autowiredHelper = classCache.get(className);
                if (null == autowiredHelper) {  // No cache.
                    autowiredHelper = (ISyringe) Class.forName(instance.getClass().getName() + SUFFIX_AUTOWIRED).getConstructor().newInstance();
                }
                //从mBundle中取出参数赋值
                autowiredHelper.inject(instance);
                classCache.put(className, autowiredHelper);
            }
        } catch (Exception ex) {
            blackList.add(className);    // This instance need not autowired.
        }
    }
}

ISyringe是apt在注解@Autowired时自动生成一个对应的ISyringe实现。将bundle中的数据取出,赋值给@Autowired注解的属性。

总的参数注入过程:

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