Android Model正确使用姿势——AutoValue

Android Model正确使用姿势AutoValue

前言

简介

简单使用

ImmutableValue types

高级使用

Nullable

Gson序列化

Serializable Parcelable

Retrofit和Rxjava结合使用

相关插件

RoboPOJOGenerator

AutoValue plugin

原理介绍

AutoFactory

AutoService

AutoCommon

Auto相关使用

IntentBuilder

FragmentArgs

其他相关

Kotlin Data Class

Object-C

总结

参考连接

最近看到几篇博客是关于AutoValue的,然后自己十分喜欢,一下子觉的这样写代码很优雅,所以决定自己也写一篇文章介绍下AutoValue。

本文最先发表于Github,如有转载,请注明转载出处。

前言

首先说Android Model,在开发中网络请求,以及数据库操作等,我们都会定义一个Model,不同人对这个的说法不一样,比如有Entry,Bean,Pojo。

然后开发的过程中会遇到下面问题:

构成方法:自定义构造方法,如果实体比较复杂,可能会用到工厂模式或者是建造者模式

序列化:比如实现Serializable接口,Parcelable接口。

Json解析:有时候直接使用的是json数据,比如@SerializedName注解。

自定义方法:对Model的字段有setter,getter方法,toString的实现,在处理hash的时候,需要实现equals和hashcode方法。

以上这么问题,其实在Eclipse和Android Studio中都是有快捷功能帮我们自动生成,后面的代码示例,就是我用Android Studio自动生成的。

比如下面一个User类是我们的本次示例的一个Model,如果按照正常的写法,是这样的。


publicabstractclassUserimplementsSerializable{@SerializedName("id")privateintid;@SerializedName("name")privateString name;publicintgetId() {returnid;    }publicvoidsetId(intid) {this.id = id;    }publicStringgetName() {returnname;    }publicvoidsetName(String name) {this.name = name;    }@Overridepublicbooleanequals(Object o) {if(this== o)returntrue;if(o ==null|| getClass() != o.getClass())returnfalse;        User user = (User) o;if(id != user.id)returnfalse;returnname !=null? name.equals(user.name) : user.name ==null;    }@OverridepublicinthashCode() {intresult = id;        result =31* result + (name !=null? name.hashCode() :0);returnresult;    }@OverridepublicStringtoString() {return"User{"+"id="+ id +", name='"+ name +'\''+'}';    }}

简介

官方文档给出的解释是这样的,大致意思是说是一个生成Java不可变的值类型工具,仔细研读源代码后,使用的技术是Java Apt,这个后面再做详细解释。

AutoValue - Immutable value-type code generation for Java 1.6+.

简单使用

按照上面的例子,如果是AutoValue,代码是这样的。

首先需要在Android项目里面引入apt功能,在项目根目录的gradle中添加,

buildscript{

repositories {

jcenter()

}dependencies{        classpath'com.android.tools.build:gradle:2.2.2'// 引入apt插件        classpath'com.neenbedankt.gradle.plugins:android-apt:1.8'}}


其次在module(一般是app目录)中gradle使用apt插件。

apply plugin:'com.android.application'apply plugin:'com.neenbedankt.android-apt'


最后加入AutoValue依赖。

dependencies{    provided'com.google.auto.value:auto-value:1.3'apt'com.google.auto.value:auto-value:1.3'}

修改User类,如下所示,User已经变成了一个抽象类,类似于使用Retrofit一样,申明已经变成了一个接口,然后实现类是由AutoValue生成的代码。

importcom.google.auto.value.AutoValue;@AutoValuepublicabstractclassUser{publicabstractintid();publicabstractStringname();publicstaticUsernewInstance(intid, String name) {returnnewAutoValue_User(id, name);    }}


我们可以看看AutoValue到底干了什么?

AutoValue会自动生成一个AutoValue_User,这个类是继承了上面申明的User类,这个是默认default的访问权限,那么在其他package中是无法访问的,这样在其他代码里面也不会看到这么奇怪的名字。

同时所有的字段都是final类型,如果字段是对象类型的,那么还不能为空,这个问题先保留,后面再做详解。因为申明的是final类型,那么所有的字段都是没有setter方法的。

代码里同时也实现了equals、hashcode、toString方法。

finalclass AutoValue_User extends User {privatefinalintid;privatefinalString name;  AutoValue_User(intid,      String name) {this.id = id;if(name ==null) {thrownewNullPointerException("Null name");    }this.name = name;  }@Overridepublicintid() {returnid;  }@OverridepublicStringname() {returnname;  }@OverridepublicStringtoString() {return"User{"+"id="+ id +", "+"name="+ name        +"}";  }@Overridepublicbooleanequals(Object o) {if(o ==this) {returntrue;    }if(oinstanceofUser) {      User that = (User) o;return(this.id == that.id())          && (this.name.equals(that.name()));    }returnfalse;  }@OverridepublicinthashCode() {inth =1;    h *=1000003;    h ^=this.id;    h *=1000003;    h ^=this.name.hashCode();returnh;  }}

Immutable/Value types

刚刚上面说到,所有的字段都是final类型,那么而且实现类也是final的,有个专业术语叫Immutable。

Immutable/Value types 这个概念对有些朋友来说可能还比较陌生,简单来说就是一个数据对象一旦构造完成,就再也无法修改了。

这样有什么好处呢?最大的好处就是多线程访问可以省去很多同步控制,因为它们是不可变的,一旦构造完成,就不会存在多线程竞争访问问题了。多线程最麻烦的处理就是控制好读写问题,如果大家都是读,那么就不存控制了,所以省去了很多同步操作。

更多关于Immutable 的介绍,可以参阅wiki

举个Java中的例子:String和StringBuilder,String是immutable的,每次对于String对象的修改都将产生一个新的String对象,而原来的对象保持不变,而StringBuilder是mutable,因为每次对于它的对象的修改都作用于该对象本身,并没有产生新的对象。

Immutable objects 比传统的mutable对象在多线程应用中更具有优势,它不仅能够保证对象的状态不被改变,而且还可以不使用锁机制就能被其他线程共享。

总结下Immutable对象的优缺点:

优点

Immutable对象是线程安全的,可以不用被synchronize就在并发环境中共享

Immutable对象简化了程序开发,因为它无需使用额外的锁机制就可以在线程间共享

Immutable对象提高了程序的性能,因为它减少了synchroinzed的使用

Immutable对象是可以被重复使用的,你可以将它们缓存起来重复使用,就像字符串字面量和整型数字一样。你可以使用静态工厂方法来提供类似于valueOf()这样的方法,它可以从缓存中返回一个已经存在的Immutable对象,而不是重新创建一个。

缺点

Immutable也有一个缺点就是会制造大量垃圾,由于他们不能被重用而且对于它们的使用就是”用“然后”扔“,字符串就是一个典型的例子,它会创造很多的垃圾,给垃圾收集带来很大的麻烦。当然这只是个极端的例子,合理的使用immutable对象会创造很大的价值。

高级使用

Nullable

上面说过如果类中有对象类型的成员变量,那么是为非空的,但是在实际情况下,有的字段的是值就是为null,所以在申明时候可申明为Nullable就可以了。

importandroid.support.annotation.Nullable;importcom.google.auto.value.AutoValue;importcom.google.gson.annotations.SerializedName;@AutoValuepublicabstractclassNullableUser{@SerializedName("id")publicabstractintid();@Nullable@SerializedName("name")publicabstractStringname();publicstaticNullableUsernewInstance(intid, String name) {returnnewAutoValue_NullableUser(id, name);    }}

生成代码:

finalclass AutoValue_NullableUser extends NullableUser {privatefinalintid;privatefinalString name;  AutoValue_NullableUser(intid,@NullableString name) {this.id = id;this.name = name;  }}

测试用例

@Test(expected = NullPointerException.class)publicvoidtestUserNullPointException()throwsException {        User.newInstance(100,null);    }@TestpublicvoidtestUserNullable() {        NullableUser user = NullableUser.newInstance(100,"test");        System.out.println("user = "+ user);        Assert.assertEquals(user.id(),100);        Assert.assertEquals(user.name(),"test");    }

Gson序列化

Gson 使用比较麻烦,在普通的Model中,只需要在字段上面添加 @SerializedName注解即可。但是使用AutoValue,稍微有点繁琐。

首先需要引入一个依赖包,这里是Auto value gson Github

provided'com.ryanharter.auto.value:auto-value-gson:0.4.4'apt'com.ryanharter.auto.value:auto-value-gson:0.4.4'

其次申明的抽象类中,每个方法上面添加对应的注解,然后再添加一个typeAdapter方法,申明这个方法,Gson就会根据这个找到对应的adapter,如下所示。

@AutoValuepublicabstractclassUser{@SerializedName("id")publicabstractintid();@SerializedName("name")publicabstractStringname();publicstaticUsernewInstance(intid, String name) {returnnewAutoValue_User(id, name);    }publicstaticTypeAdaptertypeAdapter(Gson gson) {returnnewAutoValue_User.GsonTypeAdapter(gson);    }}


typeAdapter方法模板如下,T就是你当前Model的名字,写完以后会出现错误,没事重新编译下就好了,这样就会重新生成了代码。

publicstaticTypeAdaptertypeAdapter(Gson gson) {returnnewAutoValue_T.GsonTypeAdapter(gson);}


第三申明一个TypeAdapterFactory的一个实现类,这个类是abstract的,AutoValue也会自动生成其实现类。

@GsonTypeAdapterFactorypublicabstractclassMyAdapterFactoryimplementsTypeAdapterFactory{publicstaticTypeAdapterFactorycreate() {returnnewAutoValueGson_MyAdapterFactory();    }}


最后是单元测试,在json字符串转Model的时候,会使用一个Gson对象,这个对象不是平常使用的对象,需要自定义配置一些东西,然后这里就用到了上面所申明的MyAdapterFactory。

@TestpublicvoidtestUserToJson() {        User user = User.newInstance(100,"test");        String json =newGson().toJson(user);        System.out.println(json);        Assert.assertEquals("{\"id\":100,\"name\":\"test\"}", json);    }@TestpublicvoidtestUserParseFromJson() {        String json ="{\"id\":100,\"name\":\"test\"}";// 自定义的Gson对象,需要配置 MyAdapterFactoryGson gson =newGsonBuilder().registerTypeAdapterFactory(MyAdapterFactory.create()).create();        User user = gson.fromJson(json, User.class);        System.out.println(user);        Assert.assertNotNull(user);        Assert.assertEquals(user.name(),"test");        Assert.assertEquals(user.id(),100);        NullableUser nullableUser = gson.fromJson(json, NullableUser.class);        System.out.println(nullableUser);        Assert.assertNotNull(nullableUser);        Assert.assertEquals(nullableUser.name(),"test");        Assert.assertEquals(nullableUser.id(),100);    }


Serializable & Parcelable

Serializable是Java自带的序列化方式,和AutoValue结合不影响原先使用,只需要在申明的Model中实现Serializable接口即可。

Parcelable是Android提供的序列化方式,如果需要和AutoValue结合使用,和Serializable基本差不多,实现相关接口,然后在Gradle文件引入相关apt依赖即可。

apt'com.ryanharter.auto.value:auto-value-parcel:0.2.5'// OptionallyforTypeAdapter support    // compile'com.ryanharter.auto.value:auto-value-parcel-adapter:0.2.5'


auto-value-parcel Github地址

上面的auto-value-parcel-adapter是可选项,是auto-value-parcel提供自定义类型转化,相关使用可以参见Github地址。

检查下Autovalue自动给我们实现的代码,果然不出所料,全部自动生成了。

finalclass AutoValue_User extends $AutoValue_User {publicstaticfinalParcelable.Creator CREATOR =newParcelable.Creator() {@OverridepublicAutoValue_UsercreateFromParcel(Parcel in) {returnnewAutoValue_User(          in.readInt(),          in.readString()      );    }@OverridepublicAutoValue_User[]newArray(intsize) {returnnewAutoValue_User[size];    }  };  AutoValue_User(intid, String name) {super(id, name);  }@OverridepublicvoidwriteToParcel(Parcel dest,intflags) {    dest.writeInt(id());    dest.writeString(name());  }@OverridepublicintdescribeContents() {return0;  }}


Retrofit和Rxjava结合使用

Android 开发的时候,很多开发者使用Retrofit这个网络库,以及RxJava异步工具。下面举例如何结合使用AutoValue,Retrofit,Rxjava。

这里有个获取天气的接口,返回的结果是json,我们用这个来测试下Retrofit。

// https://api.thinkpage.cn/v3/weather/now.json?key=x4qjfuniyu97mt9y&location=beijing&language=zh-Hans&unit=c{"results": [    {"location": {"id":"WX4FBXXFKE4F","name":"北京","country":"CN","path":"北京,北京,中国","timezone":"Asia/Shanghai","timezone_offset":"+08:00"},"now": {"text":"霾","code":"31","temperature":"10"},"last_update":"2016-12-02T14:45:00+08:00"}  ]}


申明Retrofit Api接口,一个普通的调用,一个是RxJava的方式。

publicinterfaceIWeatherApi{@GET("/v3/weather/now.json?key=x4qjfuniyu97mt9y&location=beijing&language=zh-Hans&unit=c")    Call getWeather();@GET("/v3/weather/now.json?key=x4qjfuniyu97mt9y&location=beijing&language=zh-Hans&unit=c")    Observable getWeatherWithRx();}


Retrofit 接口创建

publicclassRetrofitUtil{publicstatic TcreateApi(@NonNull Class tClass, Gson gson) {returnnewRetrofit.Builder()                .baseUrl("https://api.thinkpage.cn")                .client(newOkHttpClient.Builder().build())                .addConverterFactory(GsonConverterFactory.create(gson))                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())                .build()                .create(tClass);    }}


Weather Model申明

publicabstractclassWeather{@SerializedName("results")publicabstractListresults();publicstaticTypeAdaptertypeAdapter(Gson gson) {returnnewAutoValue_Weather.GsonTypeAdapter(gson);    }}


测试用例,注意:Retrofit使用Gson和前面使用Gson使用方式一样,需要自己自定义,不然无法解决json解析问题。

@TestpublicvoidtestRetrofitWithAutoValue() {        Gson gson =newGsonBuilder().registerTypeAdapterFactory(MyAdapterFactory.create()).create();        IWeatherApi weatherApi = RetrofitUtil.createApi(IWeatherApi.class, gson);try{// 同步调用Weather weather = weatherApi.getWeather().execute().body();            Assert.assertNotNull(weather);            System.out.println(weather);// Rxjava 使用weatherApi.getWeatherWithRx().subscribe(newAction1() {@Overridepublicvoidcall(Weather weather) {                    System.out.println(weather);                }            });        }catch(IOException e) {            e.printStackTrace();        }    }

运行结果,正常的返回天气信息。

Weather{results=[ResultsItem{now=Now{code=31, temperature=9,text=霾}, lastUpdate=2016-12-02T14:15:00+08:00, location=Location{country=CN, path=北京,北京,中国, timezone=Asia/Shanghai, timezoneOffset=+08:00,name=北京,id=WX4FBXXFKE4F}}]}Weather{results=[ResultsItem{now=Now{code=31, temperature=9,text=霾}, lastUpdate=2016-12-02T14:15:00+08:00, location=Location{country=CN, path=北京,北京,中国, timezone=Asia/Shanghai, timezoneOffset=+08:00,name=北京,id=WX4FBXXFKE4F}}]}


相关插件

RoboPOJOGenerator

GsonFormat是一款Android Studio的插件,它可以把json字符串,转变成Model对象,很多人都喜欢用它。

但是如果使用了AutoValue,那么原先的插件就不能使用了,没有关系,本来打算自己高仿GsonFormat重新写了一个插件,以实现我们的需求,后面又发现有一款插件可以实现——RoboPOJOGenerator。

RoboPOJOGenerator使用,RoboPOJOGenerator Github地址

AutoValue plugin

上面我们发现有了json字符串,有时候还要写factory和buildder方法,那么问题来了,没有插件能帮我们实现这个步骤,然代码更加的优雅,开发更加高效?

答案是肯定的,Autovalue plugin就是干这个事的。

Auto value plugin Github

我们用刚刚上面的Weather做演示,相关演示:

原理介绍

本文重点介绍的AutoValue只是Google Auto中的一小部分,Auto中还有其他好玩的。

AutoFactory

AutoFactory和AutoValue类似,可以自动帮助代码生成工厂类,兼容Java 依赖注入标准(JSR-330)。

代码示例

@AutoFactorypublicclassFactoryUser{privatefinalintid;privatefinalString name;publicFactoryUser(intid, String name) {this.id = id;this.name = name;    }publicintgetId() {returnid;    }publicStringgetName() {returnname;    }@OverridepublicStringtoString() {return"FactoryUser{"+"id="+ id +", name='"+ name +'\''+'}';    }}

生成后的代码

publicfinalclassFactoryUserFactory{@InjectpublicFactoryUserFactory() {  }publicFactoryUsercreate(intid, String name) {returnnewFactoryUser(id, name);  }}

测试代码

@TestpublicvoidtestFactoryUser() {        FactoryUser user =newFactoryUserFactory().create(100,"test");        System.out.println(user);        Assert.assertNotNull(user);        Assert.assertEquals(100, user.getId());        Assert.assertEquals("test", user.getName());    }

AutoService

AutoService比较简单,就是在使用Java APT的时候,使用AutoService注解,可以自动生成meta信息。

AutoCommon

这个是Google对Java Apt的一个扩展,一般的在自己写Apt的时候,都需要继承AbstractProcessor,但是google对它进行了扩展,BasicAnnotationProcessor,如果你想自己写个工具,那么就可以使用这个了。

给大家举个栗子,Dagger当初是Square公司受到Guice的启发,然后自己开发出一套依赖注入框架,当时Dagger使用的是Java反射,大家知道Java反射的效率其实并不高。

再后来都到了AutoValue的启发,在Dagger的分支上切个新分支,开发出Dagger2,然后这个Dagger2是由Google维护的,我们可以在Dagger2的Github上面找到证据。

Auto相关使用

IntentBuilder

有时候几个Activity之间相互跳转的时候需要传递一些参数,这些参数可以是基本类型,也有可能是复杂的类型,如果是负责的类型,必须要实现Serializable 或 Parcelable接口,上面也有介绍。

下面推IntentBuilder,IntentBuilder也是利用代码生成的方法实现的。

IntentBuilder Github

Activity传参

@IntentBuilderclass DetailActivity extends Activity {@ExtraString id;@Extra@NullableString title;@OverridepublicvoidonCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);        DetailActivityIntentBuilder.inject(getIntent(),this);// TODO use id and title}}// 调用方式startActivity(newDetailActivityIntentBuilder("12345")        .title("MyTitle")        .build(context))

Service传参

@IntentBuilderclass DownloadService extends IntentService {@ExtraString downloadUrl;@OverrideprotectedvoidonHandleIntent(Intent intent) {        MyServiceIntentBuilder.inject(intent,this);    }}startService(newDownloadServiceIntentBuilder("http://google.com").build(context))

FragmentArgs

上面介绍了Activity、Service的传参,但Fragment的传参方式是不一样的,还有需要提醒一句一般通过setter方法给Fragment传参是不是正确的方式,必须通过setArgs的方式。

fragmentargs Github

相关代码示例:

@FragmentWithArgspublicclassMyFragmentextendsFragment{@Argintid;@OverridepublicvoidonCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);        FragmentArgs.inject(this);// inject 之后,就可以使用 id 了}}MyFragment fragment = MyFragmentBuilder.newMyFragment(101);

其他相关

Kotlin Data Class

Kotlin 是一个基于 JVM 的新的编程语言,由 JetBrains 开发。有机会可以向大家介绍这种语言。

Kotlin 中提供一种类似于AutoValue中的功能,Data Class表示这个类似是一个数据类型。

比如下面是kotlin中对Model的写法,就是这么的简单、明了、优雅。

dataclassKotlinUser(valid:Int,valname:String)

Kotlin与Java是可以相互调用的。下面是Java的测试用例。

publicclassUserTest{@TestpublicvoidtestUser() {        KotlinUser user =newKotlinUser(100,"test");        System.out.println(user);        Assert.assertEquals(100, user.getId());        Assert.assertEquals("test", user.getName());    }}

我们可以反编译Kotlin生成的class字节码,看看这个中间到底发生了什么,很明显Kotlin做了很多的语法糖,这里编译器生成的代码和上面Autovalue生成的代码很像。

Object-C

Object-C中可以过直接申明@property方式,然后就可以自动实现setter和getter方法,如果要实现Immutable type方式,需要注明readonly。

hash、equals、description如果使用APPCode,代码是可以自动生成的。

@interfaceOcUser:NSObject@property(readonly)intid;@property(retain,readonly)NSString*name;- (instancetype)initWithId:(int)idname:(NSString*)name;- (NSString*)description;- (BOOL)isEqual:(id)other;- (BOOL)isEqualToUser:(OcUser *)user;- (NSUInteger)hash;@end// ==========================#import"OcUser.h"@implementationOcUser{}- (instancetype)initWithId:(int)idname:(NSString*)name {self= [superinit];if(self) {        _id=id;        _name = name;    }returnself;}- (BOOL)isEqual:(id)other {if(other ==self)returnYES;if(!other || ![[other class] isEqual:[selfclass]])returnNO;return[selfisEqualToUser:other];}- (BOOL)isEqualToUser:(OcUser *)user {if(self== user)returnYES;if(user ==nil)returnNO;if(self.id!= user.id)returnNO;return!(self.name!= user.name&& ![self.nameisEqualToString:user.name]);}- (NSUInteger)hash {    NSUInteger hash = (NSUInteger)self.id;    hash = hash *31u + [self.namehash];returnhash;}- (NSString*)description {NSMutableString*description = [NSMutableStringstringWithFormat:@"<%@: ", NSStringFromClass([selfclass])];    [description appendFormat:@"self.id=%i",self.id];    [description appendFormat:@", self.name=%@",self.name];    [description appendString:@">"];returndescription;}@end

测试用例

#import#import"OcUser.h"intmain(intargc,constchar*argv[]) {    @autoreleasepool {        OcUser *user = [[OcUser alloc] initWithId:100name:@"test"];NSLog(@"user = %@", user);    }return0;}// 运行结果// user =

总结

本文主要介绍了Autovalue的主要用法,以及AutoValu周边只是,可能说的比较多,比较杂,而且有的地方也不够深入,但是个人觉的这是一种思路,一种解决方案,后面如果自己需要造轮子的时候,我们是可以借鉴的。

本示例代码地址AutoValueDemo

参考连接

AutoValueDemo

完美Model之AutoValue使用

完美的安卓 model 层架构(上)

完美的安卓 model 层架构(下)

AutoValue Github

Java Immutable 介绍

Auto value gson Github

Auto value parcel Github

RoboPOJOGenerator Github

Auto value plugin Github

IntentBuilder Github

Fragmentargs Github

版权声明:本文为博主原创文章,未经博主允许不得转载。

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

推荐阅读更多精彩内容