Android 让Retrofit与Realm、Parceler一起使用
来源:互联网 发布:网络安全法解读—试卷 编辑:程序博客网 时间:2024/05/01 22:59
英文原文: Using Retrofit with Realm and Parceler 。
Retrofit是一个绝大多数app都会考虑使用的一个库。如果你的app需要一个后端,那么你就应该使用Retrofit去和RESTful服务交互。它的使用极为简单,你可以瞬间就让网络运行起来,Retrofit自动把获取的响应结果解析到你的model对象中。
通常,你还会考虑把从后端获取的某些数据保存在本地。Realm现在非常流行,用它你可以使用“普通”的对象,而不是使用SQLite(虽然有很多ORM库存在)。而且,RealmObject是live Object,因此你总是能得到最新的数据。
另一个经常出现的问题是你可能只想保存一部分获取的对象到数据库,但是剩下的数据你仍然需要在Activity被杀死的时候使用onSaveInstanceState保存下来(比如一个一个本地缓存的书签或者喜欢item的列表)。Parceler 为这个问题提供了一个简单的解决方法。因为它可以通过注解处理器让你的对象可序列化。
这三个库一起可以让处理后端数据变的简单快速。这篇博客将带你学习设置过程。
Retrofit
本文假设你的后台公开的是一个RESTful服务,响应是JSON格式的。对于JSON的解析,我们将使用Gson。在build.gradle中添加如下依赖:
- compile 'com.google.code.gson:gson:2.6.2'
- compile 'com.squareup.retrofit2:retrofit:2.0.2'
- compile 'com.squareup.retrofit2:converter-gson:2.0.2'
- compile 'com.squareup.okhttp3:okhttp:3.2.0'
- compile 'com.squareup.okhttp3:logging-interceptor:3.2.0'
- public class Country {
- public String alpha2Code;
- public String name;
- public String region;
- public List<String> languages;
- ...
- }
- public interface ICountryApi {
- @GET("region/{region}")
- Call<List<Country>> getAllCountries(@Path("region") String region);
- }
- Gson gson = new GsonBuilder()
- .setExclusionStrategies(new ExclusionStrategy() {
- // This is required to make Gson work with RealmObjects
- @Override public boolean shouldSkipField(FieldAttributes f) {
- return f.getDeclaringClass().equals(RealmObject.class);
- }
- @Override public boolean shouldSkipClass(Class<?> clazz) {
- return false;
- }
- }).create();
- OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
- if(BuildConfig.DEBUG) {
- // enable logging for debug builds
- HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
- loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
- httpClientBuilder.addInterceptor(loggingInterceptor);
- }
- ICountryApi countryApi = new Retrofit.Builder()
- .baseUrl("https://restcountries.eu/rest/v1/")
- .addConverterFactory(GsonConverterFactory.create(gson))
- .callFactory(httpClientBuilder.build())
- .build().create(ICountryApi.class);
- countryApi.getAllCountries().enqueue(new Callback<List<Country>>() {
- @Override public void onResponse(Call<List<Country>> call, Response<List<Country>> response) {
- if(response.isSuccessful()) {
- List<Country> countries = response.body();
- } else {
- // handle error
- }
- }
- @Override public void onFailure(Call<List<Country>> call, Throwable t) {
- // handle error
- }
- });
添加对RxJava的支持
后端调用同样可以用RxJava Observable<ModelClass>作为返回类型,这样可以利用Observable带来的好处,比如链式调用。为此,你需要再添加一个依赖并且在创建Retrofit service的时候添加一个call adapter factory:
- compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2'
- w Retrofit.Builder()
- .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
- ...
Realm
为了把Realm引入你的app,你需要添加一个buildscript dependency "io.realm:realm-gradle-plugin:0.88.2"并且在app的build.gradle中应用一个plugin:apply plugin: 'realm-Android'。记住Realm会让你的apk增大约3m,因为为了兼容多个CPU架构,Realm要使用的本地libs必须包含进来。不过你可以使用APK Splits来减小这个负担。
RealmObject是通过打开和查询一个Realm获得。一个Realm总是被限制在一个线程中,如果你想在另一个线程中获取RealmObject,必须打开一个新的Realm。但是因为RealmObject是live Object,你总能得到最新的数据,不过如果你的线程不是一个looper线程,你需要手动调用Realm instance的 refresh() 才能看见发生变化变化。你也可以实现interface,添加自定义的setters/getters方法,或者直接使用public 成员。但是对象仍然必须继承RealmObject。你还可以通过注解添加一个primary key或者定义索引。更多信息参见Realm 文档。
- public class Country extends RealmObject {
- @PrimaryKey
- public String alpha2Code;
- public String name;
- public String region;
- public RealmList<RealmString> languages;
- ...
- }
另一个(烦人)的限制是一个RealmList只能包含RealmObject。因此你需要如下封装基本数据类型和String:
- public class RealmString extends RealmObject {
- public String value;
- }
但是,现在Retrofit 和 Gson的问题来了。Gson并不知道RealmString是一个封装的对象。。。因此让我们实现一个TypeAdapter来让它正常工作:
- public class RealmStringListTypeAdapter extends TypeAdapter<RealmList<RealmString>> {
- public static final TypeAdapter<RealmList<RealmString>> INSTANCE =
- new RealmStringListTypeAdapter().nullSafe();
- private RealmStringListTypeAdapter() { }
- @Override public void write(JsonWriter out, RealmList<RealmString> src) throws IOException {
- out.beginArray();
- for(RealmString realmString : src) { out.value(realmString.value); }
- out.endArray();
- }
- @Override public RealmList<RealmString> read(JsonReader in) throws IOException {
- RealmList<RealmString> realmStrings = new RealmList<>();
- in.beginArray();
- while (in.hasNext()) {
- if(in.peek() == JsonToken.NULL) {
- in.nextNull();
- } else {
- RealmString realmString = new RealmString();
- realmString.value = in.nextString();
- realmStrings.add(realmString);
- }
- }
- in.endArray();
- return realmStrings;
- }
- }
你必须在建立Gson实例的时候注册这个TypeAdapter:
- new GsonBuilder()
- .registerTypeAdapter(new TypeToken<RealmList<RealmString>>(){}.getType(),
- RealmStringListTypeAdapter.INSTANCE)
- ...
- Realm realm = Realm.getDefaultInstance();
- List<Country> countries = response.body();
- realm.beginTransaction();
- realm.copyToRealmOrUpdate(countries);
- realm.commitTransaction();
- List<Country> allSavedCountries = realm.allObjects(Country.class);
- Country specificCountry = realm.where(Country.class).equalTo("alpha2Code", "AT").findFirst();
使用这种方法,你很容易混淆live RealmObject与同一类的detached object。如果你想得到一个detached 拷贝,你可以使用realm.copyFromRealm(liveRealmObject);你可以使用realmObject.isValid()去检查一个RealmObject是一个 live instance还是一个detached 拷贝。 ps :没看懂。
Parceler
在安卓中要传递数据或者保存状态,对象需要实现Serializable或者Parcelable。Parcelable被认为更快,因为它没有反射的负担(以及更少的内存),因此更适合移动app。但是实现一个Parcelable需要做更多的工作。虽然Android Studio有一个自动生成代码的工具,但是每次class改变的时候都要重复这一步。Parceler可以解决这个问题。
因为Parceler使用了一个注解处理器,因此首先需要应用Android APT 插件,那样你的IDE才能知道生成的类,而注解处理产生的代码菜不会包含在apk中。添加'com.neenbedankt.gradle.plugins:android-apt:1.8' buildscript dependency并apply plugin: 'com.neenbedankt.android-apt'。然后你就能在依赖中添加Parceler lib了:
- compile 'org.parceler:parceler-api:1.1.1'
- apt 'org.parceler:parceler:1.1.1'
- @Parcel(implementations = { CountryRealmProxy.class },
- value = Parcel.Serialization.FIELD,
- analyze = { Country.class })
- public class Country extends RealmObject {
- @PrimaryKey
- public String alpha2Code;
- public String name;
- public String region;
- @ParcelPropertyConverter(RealmListParcelConverter.class)
- public RealmList<RealmString> languages;
- ...
- }
通过设置analyze与implementations属性,告诉Parcele接受CountryRealmProxy对象-Realm使用的代理class。如果你的RealmObject的RealmProxy类不存在,尝试编译项目,就像这一步里它被生成的那样。
Parceler默认并不知道如何处理一个RealmList。你必须提供一个自定义的ParcelConverter。我写了一个可以和任意RealmList工作的RealmListParcelConverter,只要 item也是用@Parcel注解的。
- public class RealmListParcelConverter
- implements TypeRangeParcelConverter<RealmList<? extends RealmObject>,
- RealmList<? extends RealmObject>> {
- private static final int NULL = -1;
- @Override
- public void toParcel(RealmList<? extends RealmObject> input, Parcel parcel) {
- parcel.writeInt(input == null ? NULL : input.size());
- if (input != null) {
- for (RealmObject item : input) {
- parcel.writeParcelable(Parcels.wrap(item), 0);
- }
- }
- }
- @Override
- public RealmList fromParcel(Parcel parcel) {
- int size = parcel.readInt();
- RealmList list = new RealmList();
- for (int i=0; i<size; i++) {
- Parcelable parcelable = parcel.readParcelable(getClass().getClassLoader());
- list.add((RealmObject) Parcels.unwrap(parcelable));
- }
- return list;
- }
- }
要把@Parcel 注解的对象当作Parcelable使用,你必须用Parcels.wrap(object)来封装它们。如果要从一个Parcelable中获得原始的对象,调用Parcels.unwrap(parcelable)。这些方法对@Parcel注解的对象的list同样适用。
结论
设置Retrofit与Realm和Parceler一起使用需要些初始工作,但是使用起来真的很简单:
- List<Country> countries;
- public void getCountries() {
- countryApi.getAllCountries().enqueue(new Callback<List<Country>>() {
- @Override public void onResponse(Call<List<Country>> call, Response<List<Country>> response) {
- if(response.isSuccessful()) {
- countries = response.body();
- realm.beginTransaction();
- // Copy the objects to realm. The list still contains detached objects.
- // If you want to use the live objects, you have to use the return value
- // of this call.
- realm.copyToRealmOrUpdate(countries);
- realm.commitTransaction();
- } else {
- // handle error
- }
- }
- @Override public void onFailure(Call<List<Country>> call, Throwable t) {
- // handle error
- }
- });
- }
- @Override
- protected void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- outState.putParcelable("countries", Parcels.wrap(countries));
- }
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- ...
- if(savedInstanceState != null) {
- countries = Parcels.unwrap(savedInstanceState.getParcelable("countries"));
- }
- }
本文相关项目可以在 GitHub上获取。
转自:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2016/0428/4190.html
- Android 让Retrofit与Realm、Parceler一起使用
- Android 让Retrofit与Realm、Parceler一起使用
- Android Realm数据库使用
- Android 使用RxJava+Retrofit +Realm 组合加载数据 <读取缓存 显示 请求网络数据 缓存最新数据 更新界面>(一)
- Android 使用RxJava+Retrofit +Realm 组合加载数据 <读取缓存 显示 请求网络数据 缓存最新数据 更新界面>(二)
- android realm数据库基本使用
- Android for Realm 初级使用
- Android Realm数据库的使用
- Realm android 使用(一)
- Android-->Realm数据库使用注意事项
- android之Realm数据库使用
- Android realm数据库使用笔记
- Realm for Android 使用入门
- Realm for Android 使用入门
- Android开发使用retrofit上传文件和多个参数一起时失败问题
- Retrofit使用与解析
- retrofit与rxjava使用
- 带你一起探究Retrofit 源码,让你不再畏惧Retrofit的面试提问
- HDU 5776 sum(水~)
- 设计模式-简单工厂模式
- spfa+枚举 hdu 3768 Shopping
- java字节流实现文件拷贝
- 后台管理系统分类
- Android 让Retrofit与Realm、Parceler一起使用
- 树结构练习——排序二叉树的中序遍历
- Android应用相互启动
- Android LCD(一):LCD基本原理篇
- Android 5.0 Camera系统源码分析(1):CameraService启动流程
- delegate和block如何选择
- QDUOJ 43 - 我也想学数据结构(感谢cqupt...(AVL树最小节点数)
- 使用Maven Hibernate5 之Spring整合
- [转载]普通运动控制卡在LabVIEW平台上的应用