我使用RxJava和Retrofit的组合实现网络API,并使用Realm作为我的数据库。我得到了相当多的工作,但我想知道这是否是正确的方法和事件的流程。所以,这是RetrofitApiManager
。
public class RetrofitApiManager {
private static final String BASE_URL = "***";
private final ShopApi shopApi;
public RetrofitApiManager(OkHttpClient okHttpClient) {
// GSON INITIALIZATION
Retrofit retrofit = new Retrofit.Builder()
.client(okHttpClient)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl(BASE_URL)
.build();
shopApi = retrofit.create(ShopApi.class);
}
public Observable<RealmResults<Shop>> getShops() {
return shopApi.getShops()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(response -> {
Realm realm = Realm.getDefaultInstance();
realm.executeTransaction(realm1 ->
realm1.copyToRealmOrUpdate(response.shops));
realm.close();
})
.flatMap(response -> {
Realm realm = Realm.getDefaultInstance();
Observable<RealmResults<Shop>> results = realm.where(Shop.class)
.findAllAsync()
.asObservable()
.filter(RealmResults::isLoaded);
realm.close();
return results;
});
}
}
以下是调用RealmResults<Shop>
内的Fragment
。
realm.where(Shop.class)
.findAllAsync()
.asObservable()
.filter(RealmResults::isLoaded)
.first()
.flatMap(shops ->
shops.isEmpty() ? retrofitApiManager.getShops() : Observable.just(shops))
.subscribe(
shops -> initRecyclerView(),
throwable -> processError(throwable));
以下是我的问题:
对于上述示例中的连锁事件,这是一种正确的方法,还是应该以不同的方式管理它们?
在Realm
方法中使用getShops()
实例并在那里关闭我或者将它作为参数传递然后以某种方式管理它会更好吗?虽然这个想法似乎对线程有点问题,并且总是在正确的时间调用Realm.close()
。
答案 0 :(得分:10)
1)我会尝试在后台线程上尽可能多地做,现在你在UI线程上做了很多工作。
2)
public Observable<RealmResults<Shop>> getShops() {
return shopApi.getShops()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(response -> {
try(Realm realm = Realm.getDefaultInstance()) {
realm.executeTransaction(realm1 ->
realm1.insertOrUpdate(response.shops));
} // auto-close
})
.flatMap(response -> {
try(Realm realm = Realm.getDefaultInstance()) {
Observable<RealmResults<Shop>> results = realm.where(Shop.class)
.findAllAsync()
.asObservable()
.filter(RealmResults::isLoaded);
} // auto-close
return results;
});
}
所有Realm数据都是延迟加载的,因此它仅在Realm实例打开时可用,因此在检索它之后关闭它很可能无法正常工作。在你的情况下,虽然你是主线程上的平面映射,所以很可能已经有一个打开的实例。
如果您希望可以使用copyFromRealm()
来获取可以跨线程移动的非托管数据,并且不再连接到Realm,但它们也将失去其实时更新功能并占用更多内存。
它可能会这样做:
public Observable<RealmResults<Shop>> getShops() {
return shopApi.getShops()
.subscribeOn(Schedulers.io())
.doOnNext(response -> {
try(Realm realm = Realm.getDefaultInstance()) {
realm.executeTransaction(realm1 ->
realm1.copyToRealmOrUpdate(response.shops));
} // auto-close
})
.observeOn(AndroidSchedulers.mainThread())
.flatMap(response -> {
Observable<RealmResults<Shop>> results = realm.where(Shop.class)
.findAllAsync()
.asObservable()
.filter(RealmResults::isLoaded);
return results;
});
或者,您可以将网络请求视为副作用,并且只需依赖于Realm在发生更改时通知您(当您将网络与数据库访问分开时更好地接近IMO,例如存储库模式的内容)
public Observable<RealmResults<Shop>> getShops() {
// Realm will automatically notify this observable whenever data is saved from the network
return realm.where(Shop.class).findAllAsync().asObservable()
.filter(RealmResults::isLoaded)
.doOnNext(results -> {
if (results.size() == 0) {
loadShopsFromNetwork();
}
});
}
private void loadShopsFromNetwork() {
shopApi.getShops()
.subscribeOn(Schedulers.io())
.subscribe(response -> {
try(Realm realm = Realm.getDefaultInstance()) {
realm.executeTransaction(r -> r.insertOrUpdate(response.shops));
} // auto-close
});
}
答案 1 :(得分:1)
Christian Melchior在他的回答中提到的是完全合理的,并且应该解决你手头的问题,但是这种方法可能引入其他问题。
在一个好的架构中,所有主要模块(或库)都应与其余代码隔离开来。由于Realm,RealmObject或RealmResult不能跨线程传递,因此制作Realm&amp;与其余代码隔离的领域相关操作。
对于每个jsonModel类,您应该有一个realmModel类和一个DAO(数据访问对象)。这里的想法是除了DAO类之外,没有一个类必须知道或访问realmModel或Realm。 DAO类接受jsonModel,转换为realmModel,执行读/写/编辑/删除操作,对于读取操作,DAO将realmModel转换为jsonModel并随之返回。
这样很容易维护Realm,避免所有线程相关的问题,易于测试和调试。
这是一篇关于Realm最佳实践的文章,其中有一个很好的架构https://medium.com/@Viraj.Tank/realm-integration-in-android-best-practices-449919d25f2f
此外,一个示例项目演示了Android上的Realm与MVP(模型视图演示者),RxJava,Retrofit,Dagger,Annotations&amp;测试。 https://github.com/viraj49/Realm_android-injection-rx-test
答案 2 :(得分:1)
就我而言,我似乎已经为RealmRecyclerViewAdapter
定义了一个查询:
recyclerView.setAdapter(new CatAdapter(getContext(),
realm.where(Cat.class).findAllSortedAsync(CatFields.RANK, Sort.ASCENDING)));
以其他方式为RxJava定义Retrofit的条件,以便在满足条件时下载更多内容:
Subscription downloadCats = Observable.create(new RecyclerViewScrollBottomOnSubscribe(recyclerView))
.filter(isScrollEvent -> isScrollEvent || realm.where(Cat.class).count() <= 0)
.switchMap(isScrollEvent -> catService.getCats().subscribeOn(Schedulers.io())) // RETROFIT
.retry()
.subscribe(catsBO -> {
try(Realm outRealm = Realm.getDefaultInstance()) {
outRealm.executeTransaction((realm) -> {
Cat defaultCat = new Cat();
long rank;
if(realm.where(Cat.class).count() > 0) {
rank = realm.where(Cat.class).max(Cat.Fields.RANK.getField()).longValue();
} else {
rank = 0;
}
for(CatBO catBO : catsBO.getCats()) {
defaultCat.setId(catBO.getId());
defaultCat.setRank(++rank);
defaultCat.setSourceUrl(catBO.getSourceUrl());
defaultCat.setUrl(catBO.getUrl());
realm.insertOrUpdate(defaultCat);
}
});
}
}, throwable -> {
Log.e(TAG, "An error occurred", throwable);
});
这是一个基于编辑文本输入的搜索:
Subscription filterDogs = RxTextView.textChanges(editText)
.switchMap((charSequence) ->
realm.where(Dog.class)
.contains(DogFields.NAME, charSequence.toString())
.findAllAsyncSorted(DogFields.NAME, Sort.ASCENDING)
.asObservable())
.filter(RealmResults::isLoaded)
.subscribe(dogs -> realmRecyclerAdapter.updateData(dogs));