WMRouter源码解析之ServiceLoader

Java 中有个 SPI 的机制,可以用来很好的做功能的扩展和模块之间的接口通信,这方面不太了解的可以参考我之前的文章 Android 模块开发之 SPI, 今天接着分析 WMRouter 中的 ServiceLoader 功能和实现。

1.ServiceLoader 功能

这方面其实官网WMRouter说的很清楚了,我们直接拿过来看下:

  1. 使用注解自动配置
  2. 支持获取接口的所有实现,或根据 Key 获取特定实现
  3. 支持获取 Class 或获取实例
  4. 支持无参构造、Context 构造,或自定义 Factory、Provider 构造
  5. 支持单例管理
  6. 支持方法调用

看一个官方例子,先看下服务的注解定义, 官方注释的很清晰,可以通过 interface 接口和 key 来加载实现类。还可以声明服务是否是单例的。

// RouterService.java
/**
 * 声明一个Service,通过interface和key加载实现类。此注解可以用在任意静态类上。
 *
 * Created by jzj on 2018/3/29.
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface RouterService {

    /**
     * 实现的接口(或继承的父类)
     */
    Class[] interfaces();

    /**
     * 同一个接口的多个实现类之间,可以通过唯一的key区分。
     */
    String[] key() default {};

    /**
     * 是否为单例。如果是单例,则使用ServiceLoader.getService不会重复创建实例。
     */
    boolean singleton() default false;

    /**
     * 是否设置为默认实现类。如果是默认实现类,则在获取该实现类实例时可以不指定key
     * @return
     */
    boolean defaultImpl() default false;
}

再接着往下看获取接口的所有实现的 class
工程里有两个 module libmodule1 和 libmodule2,两个的对外服务都通过接口名标识

public abstract class LibraryModule {

    public abstract String getModuleName();

    @Override
    public String toString() {
        return "Module: " + getModuleName();
    }
}

// libmodule1
@RouterService(interfaces = LibraryModule.class)
public class LibraryModule1 extends LibraryModule {

    @Override
    public String getModuleName() {
        return "lib1";
    }
}

// libmodule2
@RouterService(interfaces = LibraryModule.class)
public class LibraryModule2 extends LibraryModule {

    @Override
    public String getModuleName() {
        return "lib2";
    }
}

然后在主工程中写如下代码, 可以拿到全部两个的实现类 class

// 通过接口(或父类)加载所有实现类
// ServiceLoaderActivity.java
List<Class<LibraryModule>> classList = Router.getAllServiceClasses(LibraryModule.class);

那拿到所有的类实现就简单了,简单根据反射就行, 框架提供了接口

// 通过接口(或父类)创建所有实现类的实例
// ServiceLoaderActivity.java
List<LibraryModule> list = Router.getAllServices(LibraryModule.class);

通过 key 来获取特定的实现类就简单增加一个 key 就行

// TestFragment.java
@RouterService(interfaces = Fragment.class, key = DemoConstant.TEST_FRAGMENT)
public class TestFragment extends Fragment {

}

// ServiceLoaderActivity.java
Class<Fragment> fragmentClass = Router.getServiceClass(Fragment.class,DemoConstant.TEST_FRAGMENT);

再接着往下单例的使用,
有一个账户服务,功能是可以打开登录页,记录登录状态等。通过注解@RouterService 声明接口是 IAccountService,key 是 DemoConstant.SINGLETON("/singleton"),并且是单例

// FakeAccountService.java
@RouterService(interfaces = IAccountService.class, key = DemoConstant.SINGLETON, singleton = true)
public class FakeAccountService implements IAccountService {

    @RouterProvider
    public static FakeAccountService getInstance() {
        return new FakeAccountService(Router.getService(Context.class, "/application"));
    }

    private boolean mIsLogin = false;
    private final List<Observer> mObservers = new ArrayList<>();

    private FakeAccountService(Context context) {
        // ...
    }

    @Override
    public void startLogin(Context context) {
        Router.startUri(context, DemoConstant.LOGIN);
    }

    @Override
    public boolean isLogin() {
        return mIsLogin;
    }

    @Override
    public void registerObserver(Observer observer) {
        if (observer != null && !mObservers.contains(observer)) {
            mObservers.add(observer);
        }
    }

    @Override
    public void unregisterObserver(Observer observer) {
        if (observer != null) {
            mObservers.remove(observer);
        }
    }

    @Override
    public void notifyLoginSuccess() {
        mIsLogin = true;
        Observer[] observers = getObservers();
        for (int i = observers.length - 1; i >= 0; --i) {
            observers[i].onLoginSuccess();
        }
    }

    @Override
    public void notifyLoginCancel() {
        Observer[] observers = getObservers();
        for (int i = observers.length - 1; i >= 0; --i) {
            observers[i].onLoginCancel();
        }
    }

    @Override
    public void notifyLoginFailure() {
        Observer[] observers = getObservers();
        for (int i = observers.length - 1; i >= 0; --i) {
            observers[i].onLoginFailure();
        }
    }

    @Override
    public void notifyLogout() {
        mIsLogin = false;
        Observer[] observers = getObservers();
        for (int i = observers.length - 1; i >= 0; --i) {
            observers[i].onLogout();
        }
    }

    @NonNull
    private Observer[] getObservers() {
        return mObservers.toArray(new Observer[mObservers.size()]);
    }
}

在外面使用方式, 通过接口 class 和 key 就可以拿到账号服务

// ServiceLoaderActivity.java
IAccountService accountService1 = Router.getService(IAccountService.class,DemoConstant.SINGLETON);

再看下构造的使用,可以有无参构造,context 构造,自定义构造和 RouterProvider 构造四种方式:

// IFactoryService.java
public interface IFactoryService {

    String name();
}

// FactoryServiceImpl.java
@RouterService(interfaces = IFactoryService.class, key = "/factory")
public class FactoryServiceImpl implements IFactoryService {

    private final String mName;

    @RouterProvider
    public static FactoryServiceImpl provideService() {
        return new FactoryServiceImpl("CreateByProvider");
    }

    public FactoryServiceImpl() {
        mName = "CreateWithEmptyArgs";
    }

    public FactoryServiceImpl(Context context) {
        mName = "CreateWithContext";
    }

    public FactoryServiceImpl(String name) {
        mName = name;
    }

    @Override
    public String name() {
        return mName;
    }
}

通过下面代码调用,其中无参构造其实就是反射调用无参构造,四种调用的顺序是单例,factory,如果没有传入 factory 默认找 Provider,找不到通过默认无参构造

// ServiceLoaderActivity.java
// EmptyArgsFactory
IFactoryService service1 = Router.getService(IFactoryService.class, "/factory", EmptyArgsFactory.INSTANCE);
// Provider
IFactoryService service2 = Router.getService(IFactoryService.class, "/factory");
// ContextFactory
IFactoryService service3 = Router.getService(IFactoryService.class, "/factory", this);
// CustomFactory
IFactoryService service4 = Router.getService(IFactoryService.class, "/factory", new IFactory() {
    @NonNull
    @Override
    public <T> T create(@NonNull Class<T> clazz) throws Exception {
        return clazz.getConstructor(String.class).newInstance("CreateByCustomFactory");
    }
    });
public class EmptyArgsFactory implements IFactory {

    public static final EmptyArgsFactory INSTANCE = new EmptyArgsFactory();

    private EmptyArgsFactory() {

    }

    @NonNull
    @Override
    public <T> T create(@NonNull Class<T> clazz) throws Exception {
        return clazz.newInstance();
    }
}

再看一下最后一个功能,方法调用,方法其实也是封装成了类,该类还是通过注解 RouterService,并且实现特定的 FunX 接口

@RouterService(interfaces = Func2.class, key = DemoConstant.ADD_METHOD, singleton = true)
public class AddMethod implements Func2<Integer, Integer, Integer> {

    @Override
    public Integer call(Integer a, Integer b) {
        return a + b;
    }
}

在核心库下面定义了下面这些函数调用接口,定义了 Func0 到 Func9 和 FuncN

// Func0.java
public interface Func0<R> extends Function {
    R call();
}

// Func1.java
public interface Func1<T, R> extends Function {
    R call(T t);
}

// Func2.java
public interface Func2<T1, T2, R> extends Function {
    R call(T1 t1, T2 t2);
}

// FuncN.java
public interface FuncN<R> extends Function {
    R call(Object... args);
}

// Function.java
public interface Function {

}

然后在主工程中调用:

// ServiceLoaderActivity.java
Integer result = Router.callMethod(DemoConstant.ADD_METHOD, 1, 2);

其实是根据参数个数匹配接口:

/**
 * 调用方法。方法应该实现 {@link Func0} ~ {@link FuncN} 接口,根据参数个数匹配接口。
*/
@SuppressWarnings("unchecked")
public static <T> T callMethod(String key, Object... args) {
        switch (args.length) {
            case 0:
                return (T) getService(Func0.class, key).call();
            case 1:
                return (T) getService(Func1.class, key).call(args[0]);
            case 2:
                return (T) getService(Func2.class, key).call(args[0], args[1]);
            case 3:
                return (T) getService(Func3.class, key).call(args[0], args[1], args[2]);
            case 4:
                return (T) getService(Func4.class, key).call(
                        args[0], args[1], args[2], args[3]);
            case 5:
                return (T) getService(Func5.class, key).call(
                        args[0], args[1], args[2], args[3], args[4]);
            case 6:
                return (T) getService(Func6.class, key).call(
                        args[0], args[1], args[2], args[3], args[4], args[5]);
            case 7:
                return (T) getService(Func7.class, key).call(
                        args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
            case 8:
                return (T) getService(Func8.class, key).call(
                        args[0], args[1], args[2], args[3],
                        args[4], args[5], args[6], args[7]);
            case 9:
                return (T) getService(Func9.class, key).call(
                        args[0], args[1], args[2], args[3],
                        args[4], args[5], args[6], args[7], args[8]);
            default:
                return (T) getService(FuncN.class, key).call(args);
        }
}

最后也支持 kotlin 的服务调用:

// KotlinService.java
@RouterService(interfaces = [Object::class], key = [DemoConstant.KOTLIN_SERVICE])
class KotlinService {

}

// ServiceLoaderActivity.java
Object service = Router.getService(Object.class, DemoConstant.KOTLIN_SERVICE);

上面看了 ServiceLoader 的使用,下面看下具体的源码分析

2. ServiceLoader源码分析

先从初始化入口着手,一般建议我们在 Application 中进行异步初始化,当然这样也行,因为会在每次跳转之前判断ServiceLoaderInit.class是否加载了:

// DemoApplication.java
// 懒加载后台初始化(可选)
new AsyncTask<Void, Void, Void>() {
    @Override
    protected Void doInBackground(Void... voids) {
        Router.lazyInit();
        return null;
    }
}.execute();

// Router.java
    /**
     * 此初始化方法的调用不是必须的。
     * 使用时会按需初始化;但也可以提前调用并初始化,使用时会等待初始化完成。
     * 本方法线程安全。
     */
    public static void lazyInit() {
        ServiceLoader.lazyInit();
        getRootHandler().lazyInit();
    }

再看到 ServiceLoader.lazyInit, 其中的
Const.SERVICE_LOADER_INIT = com.sankuai.waimai.router.generated.ServiceLoaderInit

// ServiceLoader.java
public static void lazyInit() {
    sInitHelper.lazyInit();
}

private static final LazyInitHelper sInitHelper = new LazyInitHelper("ServiceLoader") {
        @Override
        protected void doInit() {
            try {
// 反射调用Init类,避免引用的类过多,导致main dex capacity exceeded问题
                Class.forName(Const.SERVICE_LOADER_INIT)
                        .getMethod(Const.INIT_METHOD)
                        .invoke(null);
                Debugger.i("[ServiceLoader] init class invoked");
            } catch (Exception e) {
                Debugger.fatal(e);
            }
        }
};

上面反射调用的类是编译时生成的,这个可以参考上一篇博客 WMRouter 源码解析之 Transform

看下这个 com.sankuai.waimai.router.generated.ServiceLoaderInit.class 的内容,其实就是调用工程所有动态生成的 ServiceInit_xxx 的初始化方法,

public class ServiceLoaderInit {
    public static void init() {
        ServiceInit_aea7f96d0419b507d9b0ef471913b2f5.init();
        ServiceInit_f3649d9f5ff15a62b844e64ca8434259.init();
        ServiceInit_eb71854fbd69455ef4e0aa026c2e9881.init();
        ServiceInit_b57118238b4f9112ddd862e55789c834.init();
        ServiceInit_f1e07218f6691f962a9f674eb5b4b8bd.init();
        ServiceInit_4268a3e74040533ba48f2e1679155468.init();
        ServiceInit_e694d982fb5d7a3a8c6b7085829e74a6.init();
        ServiceInit_ee5f6404731417fe1433da40fd3c9708.init();
        ServiceInit_9482ef47a8cf887ff1dc4bf705d5fc0a.init();
        ServiceInit_36ed390bf4b81a8381d45028b37cc645.init();
    }
}

看下主工程的一些 ServiceInit_xxx, 看到了我们前面举例的 FakeAccountService 和 Func2

// ServiceInit_b57118238b4f9112ddd862e55789c834.java
public class ServiceInit_b57118238b4f9112ddd862e55789c834 {
  public static void init() {
    ServiceLoader.put(Context.class, "/application", DemoApplication.class, true);
    ServiceLoader.put(ILocationService.class, "/singleton", FakeLocationService.class, true);
    ServiceLoader.put(Func0.class, "/method/get_version_code", GetVersionCodeMethod.class, true);
    ServiceLoader.put(IAccountService.class, "/singleton", FakeAccountService.class, true);
    ServiceLoader.put(Fragment.class, "/fragment/test", TestFragment.class, false);
    ServiceLoader.put(TestServiceAnnotation.IService.class, "com.sankuai.waimai.router.demo.testannotation.TestServiceAnnotation.ServiceImpl2", TestServiceAnnotation.ServiceImpl2.class, false);
    ServiceLoader.put(TestServiceAnnotation.IService.class, "com.sankuai.waimai.router.demo.testannotation.TestServiceAnnotation.ServiceImpl1", TestServiceAnnotation.ServiceImpl1.class, false);
    ServiceLoader.put(Func2.class, "/method/add", AddMethod.class, true);
    ServiceLoader.put(Object.class, "/service/test_annotation_2", TestServiceAnnotation.TestPathService2.class, false);
    ServiceLoader.put(Object.class, "/service/test_annotation_1", TestServiceAnnotation.TestPathService1.class, false);
  }
}

// ServiceInit_eb71854fbd69455ef4e0aa026c2e9881.java
public class ServiceInit_eb71854fbd69455ef4e0aa026c2e9881 {
  public static void init() {
    ServiceLoader.put(IUriAnnotationInit.class, "com.sankuai.waimai.router.generated.UriAnnotationInit_72565413b8384a4bebb02d352762d60d", com.sankuai.waimai.router.generated.UriAnnotationInit_72565413b8384a4bebb02d352762d60d.class, false);
  }
}

接着看ServiceLoader.java的源码,
内部有个接口类型 -> ServiceLoader 的静态映射容器

// ServiceLoader.java

private static final Map<Class, ServiceLoader> SERVICES = new HashMap<>();

public static void put(Class interfaceClass, String key, Class implementClass, boolean singleton) {
    ServiceLoader loader = SERVICES.get(interfaceClass);
    if (loader == null) {
        loader = new ServiceLoader(interfaceClass);
        SERVICES.put(interfaceClass, loader);
    }
    loader.putImpl(key, implementClass, singleton);
}

前面的putImpl就是往另外一个私有容器里面根据key对应构造一个ServiceImpl放入这个私有容器中。

    /**
     * key --> class name
     */
    private HashMap<String, ServiceImpl> mMap = new HashMap<>();

    private final String mInterfaceName;

    private ServiceLoader(Class interfaceClass) {
        if (interfaceClass == null) {
            mInterfaceName = "";
        } else {
            mInterfaceName = interfaceClass.getName();
        }
    }

    private void putImpl(String key, Class implementClass, boolean singleton) {
        if (key != null && implementClass != null) {
            mMap.put(key, new ServiceImpl(key, implementClass, singleton));
        }
    }

ServiceImpl只是几个属性的简单包装,就不贴代码了

再分别看下get 类型和实例的实现代码,就是简单的get

public <T extends I> T get(String key, Context context) {
    return createInstance(mMap.get(key), new ContextFactory(context));
}

public <T extends I> Class<T> getClass(String key) {
    return (Class<T>) mMap.get(key).getImplementationClazz();
}

再看下createInstance

  1. 如果是单例,会通过SingletonPool进行get
  2. 如果没有传入IFactory,会默认构造一个,里面会通过provider获取实例或者默认构造
    @Nullable
    private <T extends I> T createInstance(@Nullable ServiceImpl impl, @Nullable IFactory factory) {
        if (impl == null) {
            return null;
        }
        Class<T> clazz = (Class<T>) impl.getImplementationClazz();
        if (impl.isSingleton()) {
            try {
                return SingletonPool.get(clazz, factory);
            } catch (Exception e) {
                Debugger.fatal(e);
            }
        } else {
            try {
                if (factory == null) {
                    factory = RouterComponents.getDefaultFactory();
                }
                T t = factory.create(clazz);
                Debugger.i("[ServiceLoader] create instance: %s, result = %s", clazz, t);
                return t;
            } catch (Exception e) {
                Debugger.fatal(e);
            }
        }
        return null;
    }

先看下RouterComponents.getDefaultFactory:

// RouterComponents.java
@NonNull
public static IFactory getDefaultFactory() {
    return sDefaultFactory;
}

@NonNull
private static IFactory sDefaultFactory = DefaultFactory.INSTANCE;

// DefaultFactory.java
public class DefaultFactory implements IFactory {

    public static final DefaultFactory INSTANCE = new DefaultFactory();

    private DefaultFactory() {

    }

    @NonNull
    @Override
    public <T> T create(@NonNull Class<T> clazz) throws Exception {
        T t = ProviderPool.create(clazz);
        if (t != null) {
            return t;
        } else {
            return clazz.newInstance();
        }
    }
}

上面就是ServiceLoader的大部分实现,其实大体上就是一个接口对应一个ServiceLoader,然后每个ServiceLoader中有个key -> ServiceLoaderImpl的映射容器。

最后就是剩下两个pool,一个是单例SingletonPool,另外一个就是ProviderPool,先看下SingletonPool, 逻辑比较简单,通过静态的缓存,如果没有缓存就通过传进来的factory进行构造。

// SingletonPool.java
public class SingletonPool {

    private static final Map<Class, Object> CACHE = new HashMap<>();

    @SuppressWarnings("unchecked")
    public static <I, T extends I> T get(Class<I> clazz, IFactory factory) throws Exception {
        if (clazz == null) {
            return null;
        }
        if (factory == null) {
            factory = RouterComponents.getDefaultFactory();
        }
        Object instance = getInstance(clazz, factory);
        Debugger.i("[SingletonPool]   get instance of class = %s, result = %s", clazz, instance);
        return (T) instance;
    }

    @NonNull
    private static Object getInstance(@NonNull Class clazz, @NonNull IFactory factory) throws Exception {
        Object t = CACHE.get(clazz);
        if (t != null) {
            return t;
        } else {
            synchronized (CACHE) {
                t = CACHE.get(clazz);
                if (t == null) {
                    Debugger.i("[SingletonPool] >>> create instance: %s", clazz);
                    t = factory.create(clazz);
                    //noinspection ConstantConditions
                    if (t != null) {
                        CACHE.put(clazz, t);
                    }
                }
            }
            return t;
        }
    }
}

再接着看下ProviderPool,是用来缓存通过注解RouterProvider的静态方法

/**
 * 指定一个静态方法,用于构造Service
 * Created by jzj on 2018/6/7.
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RouterProvider {

}

通过getProivider找到注解的方法,如果没有找到就报错,找到就通过反射调用返回

    private static final HashMap<Class, Method> CACHE = new HashMap<>();

    private static final Method NOT_FOUND = ProviderPool.class.getDeclaredMethods()[0];

    @SuppressWarnings("unchecked")
    public static <T> T create(Class<T> clazz) {
        if (clazz == null) {
            return null;
        }
        Method provider = getProvider(clazz);
        if (provider == NOT_FOUND) {
            Debugger.i("[ProviderPool] provider not found: %s", clazz);
            return null;
        } else {
            Debugger.i("[ProviderPool] provider found: %s", provider);
            try {
                return (T) provider.invoke(null);
            } catch (Exception e) {
                Debugger.fatal(e);
            }
        }
        return null;
    }

再看下上面的getProvider方法, 通过double check的方式防止多线程环境出错。
findProvider通过拿到RouterProvider注解的方法,同时满足是静态方法,返回类型是类构造传进来的实现类型,另外没有参数的方法

@NonNull
private static <T> Method getProvider(@NonNull Class<T> clazz) {
        Method provider = CACHE.get(clazz);
        if (provider == null) {
            synchronized (CACHE) {
                provider = CACHE.get(clazz);
                if (provider == null) {
                    provider = findProvider(clazz);
                    CACHE.put(clazz, provider);
                }
            }
        }
        return provider;
}

@NonNull
private static Method findProvider(@NonNull Class clazz) {
        Debugger.i("[ProviderPool] >>> find provider with reflection: %s", clazz);
        for (Method method : clazz.getDeclaredMethods()) {
            if (method.getAnnotation(RouterProvider.class) != null) {
                if (Modifier.isStatic(method.getModifiers()) &&
                        method.getReturnType() == clazz &&
                        RouterUtils.isEmpty(method.getParameterTypes())) {
                    return method;
                } else {
                    Debugger.fatal("[ProviderPool] RouterProvider注解的应该是静态无参数方法,且返回值类型为当前Class");
                    return NOT_FOUND;
                }
            }
        }
        return NOT_FOUND;
}

最后看一张ServiceLoader的运行时图,有个总体的了解

ServiceLoader.png

3.总结

学习优秀的代码可以促进自己的进步,传统的SPI机制会有运行时IO和反射的缺点,而WMRouter中就可以通过后台线程进行懒加载,做了缓存用空间换取时间,并且提供了获取实现类和实例的接口,也支持单例和方法的调用。

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