Android:Realm + Retrofit 2 + Gson

时间:2015-12-11 18:44:54

标签: android gson retrofit realm

使用 Retrofit + Gson Realm 时遇到问题。我知道这3个库的组合存在问题。一些答案表明,为 Gson 设置ExclusionStrategy可以解决此问题,我尝试了但是它没有用。

我的代码如下:

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();
        }
    });

使用当前代码,我仍然会收到内存泄漏。这段代码有什么建议吗?

我的json结构如下:

{"anotherObject":[{"propA": "someValue"}]}

2 个答案:

答案 0 :(得分:13)

  

为什么要在编写 Gson 时编写所有这些自定义序列化程序    Realm 一行代码一起工作?

TL; DR

您可以通过将非托管RealmObjects传递到 Retrofit 电话来解决此问题。

如果您不想完成所有这些答案,请跳至下面发布的“推荐解决方案”部分。

长谈(详细回答)

这与 Retrofit 无关。如果您已将 Gson 设置为当前 Retrofit 实例的数据转换器,那么您可以确定它是 Gson 谁失败了。

假设我们有这个模型:

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;
    }
}

对于此代码,我们没有问题:

Model unmanagedModel = new Model(5, true); // unmanagedModel
new Gson().toJson(unmanagedModel);   // {id : 5, happy : true}

但对于这个:

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}

我们会感到惊讶。我们到处都看到nulls

为什么?

Gson 只有在{em>托管的情况下才会序列化RealmObject。这意味着当前有一个打开的Realm实例,确保此RealmObject反映了持久层(Realm数据库)中当前持有的内容。

之所以发生这种情况,是因为 Gson Realm 的工作方式存在冲突。引用Zhuinden为什么 Gson 在任何地方看到null

  

...那是因为GSON试图读取的字段   Realm对象通过反射,但要获取值,需要使用   存取方法 - 自动应用于所有字段访问   在代码中通过Realm-transformer,但反射仍然看到空值   无处不在...

Christian Melchior通过向每个已创建的JsonSerializers编写自定义Model,为此冲突提出workaround。这是您使用过的解决方法,但我不推荐。正如您所知,它需要编写大量代码 容易出错,最糟糕的是,会杀死Gson所关注的内容(这使我们的生活减少痛苦)。

推荐的解决方案

如果我们能够以某种方式确保我们传递给Gson的realmObject不是managed,我们将避免这种冲突。

解决方案1 ​​

获取托管RealmObject的内存副本并将其传递给 Gson

new Gson().toJson(realm.copyFromRealm(managedModel));

解决方案2

(包装第一个解决方案)。如果第一个解决方案对您来说过于冗长,那么让您的模型看起来像这样:

public class Model extends RealmObject {
    @PrimaryKey
    long id;
    boolean happy;

    // Some methods ...

    public Model toUnmanaged(Realm realm) {
        return isManaged() ? realm.copyFromRealm(this) : this;
    }
}

然后,你可以这样做:

// You can pass null when you're sure your model is unmanged
new Gson().toJson(model.toUnmanaged(realm));

解决方案3

如果(像我一样)你认为在进行某些序列化时传递nulls或要求Realm会很难看,那么你可以继续克隆你的模型。不需要Realm个实例!

已发布here(向下滚动并查找@ AnixPasBesoin的帖子)。

1 - 创建一个通用接口CloneableRealmObject:

interface CloneableRealmObject<T> {
    T cloneRealmObject();
}

2 - 让你的realmObjetcs实现上述界面:

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 - 在传递给你的Retrofit电话之前克隆对象。

new Gson().toJson(model.cloneRealmObject());

在最近的帖子中

我提供answer解释了为什么我们在使用managed realmObjects时获得这种奇怪的序列化输出。我建议你看一下。

加成

您可能还想检查RealmFieldNamesHelperChristian Melchior创建的库,以使Realm查询更安全“。

答案 1 :(得分:0)

我也面临类似的问题。这是因为您的请求格式不正确。就我而言,我试图通过从本地SQLite DB而非Java对象获取来发送Realm对象。改造仅将Java对象转换为JSON,而不将Realm对象转换。使用Retrofit时,请确保发送正确的JSON作为请求。

然后我替换了它:

List<MyRealmModel> objectsToSync = mRealm.where(MyRealmModel.class).findAll();

收件人:

List<MyRealmModel> objectsToSend = mRealm.copyFromRealm(objectsToSync);