Android:使用Realm + Retrofit 2 + Gson

11

在使用 Retrofit + GsonRealm 时,我遇到了问题。我知道这三个库的组合存在问题。一些答案建议为 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"}]}

你如何获取 response.body().anotherObject; ?你应该使用 response.body().get(0..); - Jemshit Iskenderov
谢谢大家,现在问题已经解决了,我只是将exclusionstrategy添加到我的所有gson实例中。 - cana
1
嗨@Ewoks,感谢您的回复。 目前我不再从事Android方面的工作了。 - cana
3
@cana,请不要再将问题留在这种状态。当其他用户到达此处时,由于模糊的问题描述和"我刚刚解决了它,不重要了"之类的评论,他们只会浪费时间。请尽可能详细地描述您的问题,以便其他用户可以理解并提供有用的帮助。 - Ewoks
2
@cana 你能提供你的答案吗?这样做对于 Stack Overflow 上的其他用户有点不公平。这关乎到获取帮助和帮助他人的原则。 - Simon Schubert
显示剩余2条评论
2个回答

15
为什么要写那么多自定义序列化器,当你只需一行代码即可使Gson和Realm协同工作?
TL;DR.
您可以通过将未受管理的RealmObjects传递给Retrofit调用来简单解决此问题。
MyModel model = realm.where(MyModel.class).findFirst();
MyModel unmanagedModel = realm.copyFromRealm(model);
// then pass unmanagedModel to your retrofit calls

如果您不想阅读所有内容,可以直接跳到下面的“推荐解决方案”部分。

详细解答

这与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 只有在 RealmObjectmanaged 的情况下才无法序列化。这意味着当前有一个打开的 Realm 实例,确保此 RealmObject 反映了持久层中当前保存的内容(Realm 数据库)。

之所以会出现这种情况,是因为 GsonRealm 的工作方式存在冲突。引用 Zhuinden 对于为什么 Gson 到处看到 null

... 这是因为 GSON 尝试通过反射读取 Realm 对象的字段,但要获取值,需要使用访问器方法 - 这些方法会自动应用于代码中的所有字段访问,通过 Realm 转换器,但反射仍然到处看到 null...

Christian Melchior 提出了一种解决方案,通过为每个创建的 Model 编写自定义的 JsonSerializers 来解决此冲突。这是您使用的解决方法,但是我不建议这样做。正如您已经意识到的那样,它需要编写大量代码,这是容易出错的,而最糟糕的是,会破坏 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() {
        return isManaged() ? getRealm().copyFromRealm(this) : this;
    }
}

然后,您可以像这样做:

接下来的步骤如下:

// always convert toUnmanaged when serializing
new Gson().toJson(model.toUnmanaged());

解决方案3

这个方案并不是很实用,但值得一提。您可以选择深度克隆您的模型(来自此处)。

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

最近的一篇文章中

我提供了一个答案,解释了为什么在使用managedrealmObjects时会产生这种奇怪的序列化输出。我建议您查看一下这篇答案。

额外福利

您可能也想要检查RealmFieldNamesHelper,这是由Christian Melchior开发的库,旨在“使Realm查询更加类型安全”。


Retrofit默认不使用Gson。您必须显式设置数据转换器。 - OneCricketeer
因为最少的文档只是提供了使用Retrofit的复制粘贴版本。一些人会将其与Jackson一起使用。 - OneCricketeer
@cricket_007 Jackson能克服这些领域限制吗?我以前从未使用过它,所以我不知道它的限制。 - Anis LOUNIS aka AnixPasBesoin
还没有使用过 Realm,所以不知道。 - OneCricketeer

0

我也遇到了类似的问题。这是因为您的请求格式不正确。在我的情况下,我试图通过从本地SQLite数据库中获取它而不是Java对象来发送一个Realm对象。Retrofit只能将Java对象转换为JSON,而不能将Realm对象转换为JSON。请确保在使用Retrofit时发送正确的JSON作为请求。

然后我替换了这个:

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

到:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接