Android:Realm + Retrofit 2 + Gson [英] Android: Realm + Retrofit 2 + Gson
问题描述
使用 Retrofit + Gson 和 Realm 时遇到问题。我知道这3个库的组合存在问题。一些解决方案建议为 Gson 设置 ExclusionStrategy
可以解决这个问题,并且我尝试了它,但它不起作用。
我的代码如下所示:
public class ObjectList {
public List< AnotherObject> anotherObject;
}
公共类AnotherObject扩展RealmObject {
private String propA;
public void setPropA(String propA){
this.setPropA = propA
}
public String getPropA(){
return propA
} $ b $ setExclusionStrategies(new ExclusionStrategy(){
@Override
public boolean shouldSkipField(FieldAttributes f){
return f。 getDeclaringClass()。equals(RealmObject.class);
}
@Override
public boolean shouldSkipClass(Class<?> clazz){
return false;
}
})。create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(http:// localhost / api /)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
ObjectAPI objectAPI = retrofit.create(ObjectAPI.class);
call.enqueue(new Callback< ObjectList>(){
@Override
public void onResponse(Response< ObjectList> response,Retrofit retrofit){
objectList = response.body ).anotherObject;
onRefreshComplete();
}
@Override
public void onFailure(Throwable t){
Toast.makeText(context,连接到服务器失败,请检查您的连接,Toast.LENGTH_LONG).show();
}
});
使用当前的代码,我仍然会发生内存泄漏。这个代码有什么建议吗?
我的json结构如下所示:
{anotherObject:[{propA:someValue}]}
为什么在编写 Gson 和
Realm 时要编写所有这些自定义序列化器只是一行代码?
TL; DR。
您可以通过将非托管 RealmObjects
传递给您的 Retrofit 调用来解决此问题。
如果您不想完全解答所有问题,请跳至下面的推荐解决方案部分。
长谈话(详细回答)
这与改造。如果您已将 Gson 设置为当前的 Retrofit 实例的数据转换器,那么您可以确定它是 谁失败。 p>
假设我们有这个模型:
public class Model extends RealmObject {
或要求
@PrimaryKey
long id;
布尔幸福;
public Model(){/ * Realm和Gson都需要* /}
public模型(long id,boolean happy){
this.id = ID;
this.happy =开心;
}
public long getId(){
return id;
}
public boolean isHappy(){
return happy;
$ b $ p
$ b对于这段代码,我们没有问题:
Model unmanagedModel = new Model(5,true); // unmanagedModel
new Gson()。toJson(unmanagedModel); // {id:5,happy:true}
但是对于这一个:
领域realm = /*...*/;
Model managedModel = realm.copyToRealm(unmanagedModel);
new Gson()。toJson(managedModel); // {id:0,happy:false}
//我们会得到同样的代码
Model anotherManagedModel = realm.where(Model.class).equalTo(id ,5).findFirst();
new Gson()。toJson(anotherManagedModel); // {id:0,happy:false}
我们会很惊讶。我们看到
nulls
无处不在!
为什么?
Gson 仅当它是托管时才会序列化RealmObject
。这意味着目前有一个打开的Realm
实例,确保这个RealmObject
反映了当前持久层中的内容(Realm
数据库)。
这种情况发生的原因是由于 Gson 和 Realm 工作。为什么 Gson 看到
null
引用 Zhuinden >无处不在:
...这是因为GSON试图通过反射读取
Realm对象的字段,但是要获得值,您需要使用
访问器方法 - 它们通过Realm-transformer自动应用于代码中的所有字段访问
,但反射在任何地方都会看到空值
... p>
Christian Melchior 提议解决方法通过编写自定义
JsonSerializers
分配给每个创建的Model
。这是您使用的解决方法,但我不会推荐它。正如您已经意识到的那样,它需要编写错误和最糟糕的代码,杀死Gson
code>是关于(这使我们的生活变得更加痛苦)。
推荐的解决方案
如果我们可以确保
realmObject
我们传给Gson不是托管$ c $我们将避免这种冲突。
解决方案1
获取托管RealmObject的内存副本并将其传递给 Gson
new Gson()。的toJSON(realm.copyFromRealm(managedModel));
解决方案2
(包装第一个解决方案)。如果第一个解决方案对你来说太冗长了,那么让你的模型看起来像这样:
public class Model extends RealmObject {
@PrimaryKey
long id;
布尔幸福;
//一些方法...
public Model toUnmanaged(Realm realm){
return isManaged()? realm.copyFromRealm(this):this;
$ / code>然后,你可以这样做:
//当你确定你的模型是unmanged时,你可以传递null
new Gson()。toJson(model .toUnmanaged(境界));
解决方案3
如果(像我一样)认为在执行
nulls Realm
时,一些序列化,那么你可以去克隆你的模型。没有Realm
需要实例!
已发布 here (向下滚动并查找@ AnixPasBesoin的帖子)。
1 - 创建一个通用接口CloneableRealmObject:
接口CloneableRealmObject< T> {
T cloneRealmObject();
$ b 2让你的realmObjetcs像这样实现上述接口:public class Model extends RealmObject implements CloneableRealmObject< Model> {
@PrimaryKey
long id;
public Model(){
// Realm所需的空构造函数。
$ b @Override
public Model cloneRealmObject(){
Model clone = new Model();
clone.id = this.id;
return clone;
3 - 在传递给您的Retrofit调用之前克隆对象。
new Gson()。toJson(model.cloneRealmObject());
在最近的帖子中
我给出了一个答案,解释了为什么我们在使用
managed
realmObjects
。我建议你看一看。
奖金
还希望检查由 RealmFieldNamesHelper /用户/ 1389357 / christian-melchior> Christian Melchior 使Realm查询更安全。
I have a problem when using Retrofit + Gson and Realm. I know that there is an issue with the combination of these 3 libraries. Some answers suggest that setting an
ExclusionStrategy
for Gson can solve this issue, and I tried it but it didn't work.My code looks like:
public class ObjectList { public List<AnotherObject> anotherObject; } public class AnotherObject extends RealmObject { private String propA; public void setPropA(String propA){ this.setPropA = propA } public String getPropA(){ return propA } } Gson gson = new GsonBuilder().setExclusionStrategies(new ExclusionStrategy() { @Override public boolean shouldSkipField(FieldAttributes f) { return f.getDeclaringClass().equals(RealmObject.class); } @Override public boolean shouldSkipClass(Class<?> clazz) { return false; } }).create(); Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://localhost/api/") .addConverterFactory(GsonConverterFactory.create(gson)) .build(); ObjectAPI objectAPI = retrofit.create(ObjectAPI.class); call.enqueue(new Callback<ObjectList>() { @Override public void onResponse(Response<ObjectList> response, Retrofit retrofit) { objectList = response.body().anotherObject; onRefreshComplete(); } @Override public void onFailure(Throwable t) { Toast.makeText(context, "Connection to server failed, please check your connection", Toast.LENGTH_LONG).show(); } });
With the current code, I'm still getting the memory leak. Is there any suggestion for this code?
My json structure looks like:
{"anotherObject":[{"propA": "someValue"}]}
解决方案Why writing all these custom serializers when you can make Gson and Realm work together with just ONE LINE OF CODE?
TL;DR.
You can simply solve this by passing unmanaged
RealmObjects
to your Retrofit calls.If you don't want to go through all this answer, then skip to the "Recommended solutions" section posted down below.
Long talk (verbose answer)
This has nothing to do with Retrofit. If you have set Gson to be the data converter to your current Retrofit instance, then you can be sure that it's Gson who's failing.
Suppose we have this Model:
public class Model extends RealmObject { @PrimaryKey long id; boolean happy; public Model() {/* Required by both Realm and Gson*/} public Model(long id, boolean happy) { this.id = id; this.happy = happy; } public long getId() { return id; } public boolean isHappy() { return happy; } }
For this code, we'll have no issue:
Model unmanagedModel = new Model(5, true); // unmanagedModel new Gson().toJson(unmanagedModel); // {id : 5, happy : true}
But for this one:
Realm realm = /*...*/; Model managedModel = realm.copyToRealm(unmanagedModel); new Gson().toJson(managedModel); // {id : 0, happy : false} // We'll get the samething for this code Model anotherManagedModel = realm.where(Model.class).equalTo("id",5).findFirst(); new Gson().toJson(anotherManagedModel); // {id : 0, happy : false}
We'll be surprised. We're seeing
nulls
everywhere!.Why?
Gson fails serializing a
RealmObject
only if it's a managed one. Which means that there's currently an openedRealm
instance making sure thisRealmObject
is reflecting what is currently held in the persistence layer (theRealm
database).The reason why this is happening is due to the conflicting nature of how both Gson and Realm work. Quoting Zhuinden on why Gson sees
null
everywhere:... that's because GSON tries to read the fields of the Realm object via reflection, but to obtain the values, you need to use accessor methods - which are automatically applied to all field access in the code via the Realm-transformer, but reflection still sees nulls everywhere...
Christian Melchior proposes a workaround to this conflict by writing a custom
JsonSerializers
to every createdModel
. This is the workaround you have used, but I would NOT recommend it. As you have realized, it requires writing a lot of code which is error prone and the worst of all, kills whatGson
is about (which is making our life less painful).Recommended solutions
If we can somehow make sure the
realmObject
we pass to Gson is not amanaged
one, we'll avoid this conflict.Solution 1
Get a copy in memory of the managed RealmObject and pass it to Gson
new Gson().toJson(realm.copyFromRealm(managedModel));
Solution 2
(Wrapping the 1st solution). If the 1st solution is too verbose for you, make your models look like this one:
public class Model extends RealmObject { @PrimaryKey long id; boolean happy; // Some methods ... public Model toUnmanaged(Realm realm) { return isManaged() ? realm.copyFromRealm(this) : this; } }
And then, you can do something like this:
// You can pass null when you're sure your model is unmanged new Gson().toJson(model.toUnmanaged(realm));
Solution 3
If (like me) you think it makes it ugly to pass
nulls
or to require aRealm
when doing some serialization, then you can go with cloning your models. NoRealm
instance needed!Already posted here (scroll down and look for @AnixPasBesoin's post).
1 - Create a generic interface CloneableRealmObject:
interface CloneableRealmObject<T> { T cloneRealmObject(); }
2 - Make your realmObjetcs implement the above interface like so:
public class Model extends RealmObject implements CloneableRealmObject<Model> { @PrimaryKey long id; public Model() { // Empty constructor required by Realm. } @Override public Model cloneRealmObject() { Model clone = new Model(); clone.id = this.id; return clone; } }
3 - Clone the object before passing to your Retrofit calls.
new Gson().toJson(model.cloneRealmObject());
In a recent post
I gave an answer explaining why we're getting this weird serialized output when using
managed
realmObjects
. I recommend you to take a look at it.Bonus
You might also want to check RealmFieldNamesHelper, a library made by Christian Melchior "to make Realm queries more type safe".
这篇关于Android:Realm + Retrofit 2 + Gson的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!