最近看到几篇博客是关于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-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就是干这个事的。
我们用刚刚上面的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也是利用代码生成的方法实现的。
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的方式。
相关代码示例:
@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
参考连接
版权声明:本文为博主原创文章,未经博主允许不得转载。