Android: Realm + Retrofit 2 + Gson
У меня проблема при использовании Retrofit + Gson и Realm. Я знаю, что есть проблема с комбинацией этих 3 библиотек. Некоторые ответы предполагают, что установка ExclusionStrategy
для Gson может решить эту проблему, и я попробовал это, но это не сработало.
Мой код выглядит следующим образом:
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"}]}
Переведено автоматически
Ответ 1
Зачем писать все эти пользовательские сериализаторы, когда можно заставить Gson и Realm работать вместе всего с ОДНОЙ СТРОКОЙ КОДА?
TL; DR.
Вы можете просто решить эту проблему, передав unmanaged 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 завершает сериализацию с ошибкой RealmObject
только в том случае, если он управляемый. Это означает, что в данный момент открыт Realm
экземпляр, который проверяет, что это RealmObject
отражает то, что в данный момент хранится на уровне сохранения (в Realm
базе данных).
Причина, по которой это происходит, связана с противоречивым характером работы как Gson, так и Realm. Цитирую Чжуиндена о том, почему Gson видит null
везде:
... это потому, что GSON пытается прочитать поля объекта Realm через отражение, но для получения значений вам нужно использовать методы доступа, которые автоматически применяются ко всему доступу к полю в коде через Realm-transformer , но отражение по-прежнему видит null повсюду...
Кристиан Мельхиор предлагает обходной путь к этому конфликту, написав пользовательский JsonSerializers
для каждого созданногоModel
. Это обходной путь, который вы использовали, но я бы НЕ рекомендовал его. Как вы поняли, это требует написания большого количества кода, который подвержен ошибкам и, что хуже всего, убивает то, что Gson
есть (что делает нашу жизнь менее болезненной).
Рекомендуемые решения
Если мы сможем каким-то образом убедиться, что realmObject
мы передаем в Gson не 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());
В недавнем посте
Я дал ответ, объясняющий, почему мы получаем этот странный сериализованный вывод при использовании managed
realmObjects
. Я рекомендую вам взглянуть на это.
Бонус
Возможно, вы также захотите проверить RealmFieldNamesHelper, библиотеку, созданную Кристианом Мельхиором, "чтобы сделать запросы Realm более типобезопасными".
Ответ 2
Я тоже столкнулся с подобной проблемой. Это потому, что у вас неправильный формат запроса. В моем случае я пытаюсь отправить объект Realm, получая его из локальной базы данных SQLite вместо Java object. Retrofit преобразует только объект Java в JSON, но не объект Realm. Пожалуйста, убедитесь, что вы отправляете правильный JSON в качестве запроса при использовании Retrofit.
Затем я заменил это:
List<MyRealmModel> objectsToSync = mRealm.where(MyRealmModel.class).findAll();
Для:
List<MyRealmModel> objectsToSend = mRealm.copyFromRealm(objectsToSync);