Android Model正確使用姿勢——AutoValue

Android Model正確使用姿勢——AutoValue


[TOC]

最近看到幾篇博客是關于AutoValue的,然后自己十分喜歡,一下子覺的這樣寫代碼很優(yōu)雅,所以決定自己也寫一篇文章介紹下AutoValue。

本文最先發(fā)表于Github,如有轉載,請注明轉載出處。

前言

首先說Android Model,在開發(fā)中網絡請求,以及數據庫操作等,我們都會定義一個Model,不同人對這個的說法不一樣,比如有Entry,Bean,Pojo。

然后開發(fā)的過程中會遇到下面問題:

  • 構成方法:自定義構造方法,如果實體比較復雜,可能會用到工廠模式或者是建造者模式

  • 序列化:比如實現Serializable接口,Parcelable接口。

  • Json解析:有時候直接使用的是json數據,比如@SerializedName注解。

  • 自定義方法:對Model的字段有setter,getter方法,toString的實現,在處理hash的時候,需要實現equals和hashcode方法。

以上這么問題,其實在Eclipse和Android Studio中都是有快捷功能幫我們自動生成,后面的代碼示例,就是我用Android Studio自動生成的。

比如下面一個User類是我們的本次示例的一個Model,如果按照正常的寫法,是這樣的。

public abstract class User implements Serializable {

    @SerializedName("id")
    private int id;

    @SerializedName("name")
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        User user = (User) o;

        if (id != user.id) return false;
        return name != null ? name.equals(user.name) : user.name == null;

    }

    @Override
    public int hashCode() {
        int result = id;
        result = 31 * result + (name != null ? name.hashCode() : 0);
        return result;
    }

    @Override
    public String toString() {
        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生成的代碼。

import com.google.auto.value.AutoValue;

@AutoValue
public abstract class User {

    public abstract int id();

    public abstract String name();

    public static User newInstance(int id, String name) {
        return new AutoValue_User(id, name);
    }
}

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

AutoValue會自動生成一個AutoValue_User,這個類是繼承了上面申明的User類,這個是默認default的訪問權限,那么在其他package中是無法訪問的,這樣在其他代碼里面也不會看到這么奇怪的名字。

同時所有的字段都是final類型,如果字段是對象類型的,那么還不能為空,這個問題先保留,后面再做詳解。因為申明的是final類型,那么所有的字段都是沒有setter方法的。

代碼里同時也實現了equals、hashcode、toString方法。

 final class AutoValue_User extends User {

  private final int id;
  private final String name;

  AutoValue_User(
      int id,
      String name) {
    this.id = id;
    if (name == null) {
      throw new NullPointerException("Null name");
    }
    this.name = name;
  }

  @Override
  public int id() {
    return id;
  }

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

  @Override
  public String toString() {
    return "User{"
        + "id=" + id + ", "
        + "name=" + name
        + "}";
  }

  @Override
  public boolean equals(Object o) {
    if (o == this) {
      return true;
    }
    if (o instanceof User) {
      User that = (User) o;
      return (this.id == that.id())
           && (this.name.equals(that.name()));
    }
    return false;
  }

  @Override
  public int hashCode() {
    int h = 1;
    h *= 1000003;
    h ^= this.id;
    h *= 1000003;
    h ^= this.name.hashCode();
    return h;
  }
}

Immutable/Value types

剛剛上面說到,所有的字段都是final類型,那么而且實現類也是final的,有個專業(yè)術語叫Immutable。

Immutable/Value types 這個概念對有些朋友來說可能還比較陌生,簡單來說就是一個數據對象一旦構造完成,就再也無法修改了。

這樣有什么好處呢?最大的好處就是多線程訪問可以省去很多同步控制,因為它們是不可變的,一旦構造完成,就不會存在多線程競爭訪問問題了。多線程最麻煩的處理就是控制好讀寫問題,如果大家都是讀,那么就不存控制了,所以省去了很多同步操作。

更多關于Immutable 的介紹,可以參閱 wiki

舉個Java中的例子:String和StringBuilder,String是immutable的,每次對于String對象的修改都將產生一個新的String對象,而原來的對象保持不變,而StringBuilder是mutable,因為每次對于它的對象的修改都作用于該對象本身,并沒有產生新的對象。

Immutable objects 比傳統(tǒng)的mutable對象在多線程應用中更具有優(yōu)勢,它不僅能夠保證對象的狀態(tài)不被改變,而且還可以不使用鎖機制就能被其他線程共享。

總結下Immutable對象的優(yōu)缺點:

優(yōu)點

  • Immutable對象是線程安全的,可以不用被synchronize就在并發(fā)環(huán)境中共享
  • Immutable對象簡化了程序開發(fā),因為它無需使用額外的鎖機制就可以在線程間共享
  • Immutable對象提高了程序的性能,因為它減少了synchroinzed的使用
  • Immutable對象是可以被重復使用的,你可以將它們緩存起來重復使用,就像字符串字面量和整型數字一樣。你可以使用靜態(tài)工廠方法來提供類似于valueOf()這樣的方法,它可以從緩存中返回一個已經存在的Immutable對象,而不是重新創(chuàng)建一個。

缺點

Immutable也有一個缺點就是會制造大量垃圾,由于他們不能被重用而且對于它們的使用就是”用“然后”扔“,字符串就是一個典型的例子,它會創(chuàng)造很多的垃圾,給垃圾收集帶來很大的麻煩。當然這只是個極端的例子,合理的使用immutable對象會創(chuàng)造很大的價值。

高級使用

Nullable

上面說過如果類中有對象類型的成員變量,那么是為非空的,但是在實際情況下,有的字段的是值就是為null,所以在申明時候可申明為Nullable就可以了。

import android.support.annotation.Nullable;

import com.google.auto.value.AutoValue;
import com.google.gson.annotations.SerializedName;

@AutoValue
public abstract class NullableUser {

    @SerializedName("id")
    public abstract int id();

    @Nullable
    @SerializedName("name")
    public abstract String name();

    public static NullableUser newInstance(int id, String name) {
        return new AutoValue_NullableUser(id, name);
    }
}

生成代碼:

final class AutoValue_NullableUser extends NullableUser {

  private final int id;
  private final String name;

  AutoValue_NullableUser(
      int id,
      @Nullable String name) {
    this.id = id;
    this.name = name;
  }
}

測試用例

    @Test(expected = NullPointerException.class)
    public void testUserNullPointException() throws Exception {
        User.newInstance(100, null);
    }

    @Test
    public void testUserNullable() {
        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,如下所示。

@AutoValue
public abstract class User {

    @SerializedName("id")
    public abstract int id();

    @SerializedName("name")
    public abstract String name();

    public static User newInstance(int id, String name) {
        return new AutoValue_User(id, name);
    }

    public static TypeAdapter<User> typeAdapter(Gson gson) {
        return new AutoValue_User.GsonTypeAdapter(gson);
    }
}

typeAdapter方法模板如下,T就是你當前Model的名字,寫完以后會出現錯誤,沒事重新編譯下就好了,這樣就會重新生成了代碼。

public static TypeAdapter<T> typeAdapter(Gson gson) {
   return new AutoValue_T.GsonTypeAdapter(gson);
}

第三申明一個TypeAdapterFactory的一個實現類,這個類是abstract的,AutoValue也會自動生成其實現類。

@GsonTypeAdapterFactory
public abstract class MyAdapterFactory implements TypeAdapterFactory {

    public static TypeAdapterFactory create() {
        return new AutoValueGson_MyAdapterFactory();
    }
}

最后是單元測試,在json字符串轉Model的時候,會使用一個Gson對象,這個對象不是平常使用的對象,需要自定義配置一些東西,然后這里就用到了上面所申明的MyAdapterFactory。

    @Test
    public void testUserToJson() {
        User user = User.newInstance(100, "test");

        String json = new Gson().toJson(user);
        System.out.println(json);

        Assert.assertEquals("{\"id\":100,\"name\":\"test\"}", json);
    }

    @Test
    public void testUserParseFromJson() {
        String json = "{\"id\":100,\"name\":\"test\"}";
        
        // 自定義的Gson對象,需要配置 MyAdapterFactory
        Gson gson = new GsonBuilder().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'
    // Optionally for TypeAdapter 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自動給我們實現的代碼,果然不出所料,全部自動生成了。

final class AutoValue_User extends $AutoValue_User {
  public static final Parcelable.Creator<AutoValue_User> CREATOR = new Parcelable.Creator<AutoValue_User>() {
    @Override
    public AutoValue_User createFromParcel(Parcel in) {
      return new AutoValue_User(
          in.readInt(),
          in.readString()
      );
    }
    @Override
    public AutoValue_User[] newArray(int size) {
      return new AutoValue_User[size];
    }
  };

  AutoValue_User(int id, String name) {
    super(id, name);
  }

  @Override
  public void writeToParcel(Parcel dest, int flags) {
    dest.writeInt(id());
    dest.writeString(name());
  }

  @Override
  public int describeContents() {
    return 0;
  }
}

Retrofit和Rxjava結合使用

Android 開發(fā)的時候,很多開發(fā)者使用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的方式。

public interface IWeatherApi {

    @GET("/v3/weather/now.json?key=x4qjfuniyu97mt9y&location=beijing&language=zh-Hans&unit=c")
    Call<Weather> getWeather();

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

Retrofit 接口創(chuàng)建

public class RetrofitUtil {
    public static <T> T createApi(@NonNull Class<T> tClass, Gson gson) {
        return new Retrofit.Builder()
                .baseUrl("https://api.thinkpage.cn")
                .client(new OkHttpClient.Builder().build())
                .addConverterFactory(GsonConverterFactory.create(gson))
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .build()
                .create(tClass);
    }
}

Weather Model申明

public abstract class Weather {

    @SerializedName("results")
    public abstract List<ResultsItem> results();

    public static TypeAdapter<Weather> typeAdapter(Gson gson) {
        return new AutoValue_Weather.GsonTypeAdapter(gson);
    }
}

測試用例,注意:Retrofit使用Gson和前面使用Gson使用方式一樣,需要自己自定義,不然無法解決json解析問題。

    @Test
    public void testRetrofitWithAutoValue() {
        Gson gson = new GsonBuilder().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(new Action1<Weather>() {
                @Override
                public void call(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重新寫了一個插件,以實現我們的需求,后面又發(fā)現有一款插件可以實現——RoboPOJOGenerator。

RoboPOJOGenerator使用, RoboPOJOGenerator Github地址

AutoValue plugin

上面我們發(fā)現有了json字符串,有時候還要寫factory和buildder方法,那么問題來了,沒有插件能幫我們實現這個步驟,然代碼更加的優(yōu)雅,開發(fā)更加高效?

答案是肯定的,Autovalue plugin就是干這個事的。

Auto value plugin Github

我們用剛剛上面的Weather做演示,相關演示:

原理介紹

本文重點介紹的AutoValue只是 Google Auto 中的一小部分,Auto中還有其他好玩的。

AutoFactory

AutoFactory和AutoValue類似,可以自動幫助代碼生成工廠類,兼容Java 依賴注入標準(JSR-330)。

代碼示例

@AutoFactory
public class FactoryUser {

    private final int id;

    private final String name;

    public FactoryUser(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "FactoryUser{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

生成后的代碼

public final class FactoryUserFactory {
  @Inject
  public FactoryUserFactory() {
  }
  public FactoryUser create(int id, String name) {
    return new FactoryUser(id, name);
  }
}

測試代碼

    @Test
    public void testFactoryUser() {
        FactoryUser user = new FactoryUserFactory().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的啟發(fā),然后自己開發(fā)出一套依賴注入框架,當時Dagger使用的是Java反射,大家知道Java反射的效率其實并不高。

再后來都到了AutoValue的啟發(fā),在Dagger的分支上切個新分支,開發(fā)出Dagger2,然后這個Dagger2是由Google維護的,我們可以在Dagger2的Github上面找到證據。

Auto相關使用

IntentBuilder

有時候幾個Activity之間相互跳轉的時候需要傳遞一些參數,這些參數可以是基本類型,也有可能是復雜的類型,如果是負責的類型,必須要實現Serializable 或 Parcelable接口,上面也有介紹。

下面推IntentBuilder,IntentBuilder也是利用代碼生成的方法實現的。

IntentBuilder Github

Activity傳參

@IntentBuilder
class DetailActivity extends Activity {

    @Extra
    String id;

    @Extra @Nullable
    String title;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        DetailActivityIntentBuilder.inject(getIntent(), this);
        // TODO use id and title
    }
}

// 調用方式
startActivity(
    new DetailActivityIntentBuilder("12345")
        .title("MyTitle")
        .build(context)
)

Service傳參

@IntentBuilder
class DownloadService extends IntentService {

    @Extra
    String downloadUrl;

    @Override
    protected void onHandleIntent(Intent intent) {
        MyServiceIntentBuilder.inject(intent, this);
    }

}

startService(new DownloadServiceIntentBuilder("http://google.com").build(context))

FragmentArgs

上面介紹了Activity、Service的傳參,但Fragment的傳參方式是不一樣的,還有需要提醒一句一般通過setter方法給Fragment傳參是不是正確的方式,必須通過setArgs的方式。

fragmentargs Github

相關代碼示例:

@FragmentWithArgs
public class MyFragment extends Fragment {
    @Arg
    int id;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        FragmentArgs.inject(this); 
        // inject 之后,就可以使用 id 了
    }
}

MyFragment fragment = MyFragmentBuilder.newMyFragment(101);

其他相關

Kotlin Data Class

Kotlin 是一個基于 JVM 的新的編程語言,由 JetBrains 開發(fā)。有機會可以向大家介紹這種語言。

Kotlin 中提供一種類似于AutoValue中的功能,Data Class表示這個類似是一個數據類型。

比如下面是kotlin中對Model的寫法,就是這么的簡單、明了、優(yōu)雅。

data class KotlinUser(val id: Int,
                      val name: String)

Kotlin與Java是可以相互調用的。下面是Java的測試用例。

public class UserTest {

    @Test
    public void testUser() {
        KotlinUser user = new KotlinUser(100, "test");

        System.out.println(user);

        Assert.assertEquals(100, user.getId());
        Assert.assertEquals("test", user.getName());
    }
}

我們可以反編譯Kotlin生成的class字節(jié)碼,看看這個中間到底發(fā)生了什么,很明顯Kotlin做了很多的語法糖,這里編譯器生成的代碼和上面Autovalue生成的代碼很像。

Object-C

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

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

@interface OcUser : NSObject

@property(readonly) int id;

@property(retain, readonly) NSString *name;

- (instancetype)initWithId:(int)id name:(NSString *)name;

- (NSString *)description;

- (BOOL)isEqual:(id)other;

- (BOOL)isEqualToUser:(OcUser *)user;

- (NSUInteger)hash;

@end

// ==========================

#import "OcUser.h"
@implementation OcUser {

}
- (instancetype)initWithId:(int)id name:(NSString *)name {
    self = [super init];
    if (self) {
        _id   = id;
        _name = name;
    }

    return self;
}

- (BOOL)isEqual:(id)other {
    if (other == self)
        return YES;
    if (!other || ![[other class] isEqual:[self class]])
        return NO;

    return [self isEqualToUser:other];
}

- (BOOL)isEqualToUser:(OcUser *)user {
    if (self == user)
        return YES;
    if (user == nil)
        return NO;
    if (self.id != user.id)
        return NO;
    return !(self.name != user.name && ![self.name isEqualToString:user.name]);
}

- (NSUInteger)hash {
    NSUInteger hash = (NSUInteger) self.id;
    hash = hash * 31u + [self.name hash];
    return hash;
}

- (NSString *)description {
    NSMutableString *description = [NSMutableString stringWithFormat:@"<%@: ", NSStringFromClass([self class])];
    [description appendFormat:@"self.id=%i", self.id];
    [description appendFormat:@", self.name=%@", self.name];
    [description appendString:@">"];
    return description;
}
@end

測試用例

#import <Foundation/Foundation.h>
#import "OcUser.h"

int main(int argc, const char *argv[]) {
    @autoreleasepool {
        OcUser *user = [[OcUser alloc] initWithId:100 name:@"test"];

        NSLog(@"user = %@", user);
    }

    return 0;
}

// 運行結果
// user = <OcUser: self.id=100, self.name=test>

總結

本文主要介紹了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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容