在 Android 应用程序中抽象 Realm 的正确方法
Proper way to abstract Realm in Android apps
我正在 Realm.io 中尝试 Android 应用程序,不过,为了安全起见,我想抽象 DB 层,以便在需要时,我无需重写大部分应用程序即可切换回基于标准 SQLite 的数据库。
然而,由于 Realm 的特殊性质,我发现很难正确抽象它:
- 当绑定到一个领域时,RealmObjects 是代理,所以我不能像 POJO 一样传递它们。
- 所有 Realm 实例都需要为它们在其中使用的每个线程正确打开和关闭。
我已经使用最近的 Realm.copyFromRealm() API 而不是传递与 Realm 相关的 RealmObjects 来绕过这些限制,但我认为这样我就失去了所有使用领域的好处(是吗?)。
有什么建议吗?
我会尝试回答您的第一个困惑。
确实没有必要通过意图传递 RealmObject
s,或者换句话说,它们不需要 parcelable
或 serializable
您应该做的是通过意图传递特定 primary id
或该特定 RealmObject
的其他参数,以便您可以在下一个 activity 中再次查询该 RealmObject。
例如,假设您在启动 activity、
时传递了 primaryId
Intent intent = new Intent(this, NextActivity.class);
intent.putExtra(Constants.INTENT_EXTRA_PRIMARY_ID, id);
现在在 NextActivity
中从 intent 中获取 id 并简单地查询 RealmObject
,即
int primaryId = getIntent().getIntExtra(Constants.INTENT_EXTRA_PRIMARY_ID, -1);
YourRealmObject mObject = realm.where(YourRealmObject.class).equalTo("id", primaryId).findFirst();
看,你在新的 activity 中得到了你的对象,而不是担心传递对象。
我不知道您遇到了关于打开和关闭领域对象的具体问题。你试过 Realm 的 Transaction blocks
了吗?用了就不用依赖开合了
希望对您有所帮助。
更新
既然你在寻找抽象,
只需创建一个像 DbUtils
的 class 并在那里有一个像 getYourObjectById
的方法,然后传递上面的 id 并在 return 中检索你的对象。这使得它在某种程度上是抽象的。然后您可以保留 class 和方法,如果您切换到另一个数据库解决方案,只需更改方法内容即可。
提供不同意见,因为我否决了另一个答案。我想先讨论一下你的 objective
in case of need, I can switch back to a standard SQLite based DB without rewriting most of the app
Realm 不等同于标准的基于 SQLite 的数据库。在这种情况下,您正在寻找的是 StorIO 之类的东西。 Realm 的设计方式使得您必须使用 RealmObject
作为模型的基础。如果您只想从 table 检索数据,Realm 不适合您。
现在,在 Parse 崩溃之后,抽象第三方库的需求变得明显。所以,如果这是你的动机,那么你真的必须包装你在 Realm 中使用的每一件东西。像这样:
public class Db {
public class Query<M extends RealmObject> {
private final RealmQuery<M> realmQuery;
Query(Db db, Class<M> clazz) {
this.realmQuery = RealmQuery.createQuery(db.realmInstance, clazz);
}
public Query<M> equalTo(String field, Integer value) {
realmQuery.equalTo(field, value);
return this;
}
public Results<M> findAll() {
return new Results<>(realmQuery.findAll());
}
public M findFirst() {
return realmQuery.findFirst();
}
}
public class Results<M extends RealmObject> extends AbstractList<M> {
private final RealmResults<M> results;
Results(RealmResults<M> results) {
this.results = results;
}
public void removeLast() {
results.removeLast();
}
@Override
public M get(int i) {
return results.get(i);
}
@Override
public int size() {
return results.size();
}
}
public interface Transaction {
void execute();
abstract class Callback {
public abstract void onSuccess();
public void onError(Throwable error) {
}
}
}
private Realm realmInstance;
Db() {
realmInstance = Realm.getDefaultInstance();
}
public void executeTransaction(@NonNull Db.Transaction dbTransaction) {
realmInstance.executeTransaction(realm -> dbTransaction.execute());
}
public void close() {
realmInstance.close();
}
}
此时,您只需在 Db
class 上定义您在应用中使用的任何操作。而且,如果你像我一样,你可能不会使用所有这些,所以你应该没问题。
这个好看吗?没有,有必要吗?取决于你想达到什么。
编辑:为了加分,让Db
实施AutoCloseable
:
try(Db db = new Db()) {
db.executeTransaction(() -> /*...*/);
}
根据 Google I/O 2017 年 Android Architectural Components
的最新公告,在 Android 应用程序中抽象 Realm 的正确方法是:
1.) Realm实例生命周期由ViewModel
class管理,在onCleared()
方法
中关闭
2.) RealmResults 是一个 MutableLiveData<List<T>>
,因此您可以创建一个 RealmLiveData<T>
class 来包装 RealmResults<T>
.
因此,您可以像这样创建视图模型:
// based on https://github.com/googlesamples/android-architecture-components/blob/178fe541643adb122d2a8925cf61a21950a4611c/BasicSample/app/src/main/java/com/example/android/persistence/viewmodel/ProductListViewModel.java
public class ProductListViewModel {
private final MutableLiveData<List<ProductEntity>> observableProducts = new MutableLiveData<>();
Realm realm;
RealmResults<ProductEntity> results;
RealmChangeListener<RealmResults<ProductEntity>> realmChangeListener = (results) -> {
if(results.isLoaded() && results.isValid()) { // you probably don't need this, just making sure.
observableProducts.setValue(results);
}
};
public ProductListViewModel() {
realm = Realm.getDefaultInstance();
results = realm.where(ProductEntity.class).findAllSortedAsync("id");
// could use a Realm DAO class here
results.addChangeListener(realmChangeListener);
observableProducts.setValue(null); // if using async query API, the change listener will set the loaded results.
}
public LiveData<List<ProductEntity>> getProducts() {
return observableProducts;
}
@Override
protected void onCleared() {
results.removeChangeListener(realmChangeListener);
realm.close();
realm = null;
}
}
或者你可以根据 this article:
将它们分成一个领域视图模型和一个领域实时数据
public class LiveRealmData<T extends RealmModel> extends LiveData<RealmResults<T>> {
private RealmResults<T> results;
private final RealmChangeListener<RealmResults<T>> listener =
new RealmChangeListener<RealmResults<T>>() {
@Override
public void onChange(RealmResults<T> results) { setValue(results);}
};
public LiveRealmData(RealmResults<T> realmResults) {
results = realmResults;
}
@Override
protected void onActive() {
results.addChangeListener(listener);
}
@Override
protected void onInactive() {
results.removeChangeListener(listener);
}
}
public class CustomResultViewModel extends ViewModel {
private Realm mDb;
private LiveData<String> mLoansResult;
public CustomResultViewModel() {
mDb = Realm.getDefaultInstance();
mLoansResult = RealmUtils.loanDao(mDb).getAll();
}
public LiveData<String> getLoansResult() {
return mLoansResult;
}
@Override
protected void onCleared() {
mDb.close();
super.onCleared();
}
}
无论哪种方式,您都将 Realm 的自动更新和延迟加载结果集包装到 LiveData 和 ViewModel 中,与 fragments/adapters:
分开
// based on https://github.com/googlesamples/android-architecture-components/blob/178fe541643adb122d2a8925cf61a21950a4611c/BasicSample/app/src/main/java/com/example/android/persistence/ProductListFragment.java
public class ProductListFragment extends LifecycleFragment {
private ProductAdapter productAdapter;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
//...
productAdapter = new ProductAdapter(mProductClickCallback);
//...
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
final ProductListViewModel viewModel =
ViewModelProviders.of(this).get(ProductListViewModel.class); // <-- !
subscribeUi(viewModel);
}
private void subscribeUi(ProductListViewModel viewModel) {
// Update the list when the data changes
viewModel.getProducts().observe(this, (myProducts) -> {
if (myProducts == null) {
// ...
} else {
productAdapter.setProductList(myProducts);
//...
}
});
}
}
但是,如果您不使用Android架构组件,即便如此,需要记住的是:
RealmResults is a list of proxy objects that mutates in place, and it has change listeners.
所以你需要的是将其包装为具有最新背压的 Flowable,类似于
private io.reactivex.Flowable<RealmResults<T>> realmResults() {
return io.reactivex.Flowable.create(new FlowableOnSubscribe<RealmResults<T>>() {
@Override
public void subscribe(FlowableEmitter<RealmResults<T>> emitter)
throws Exception {
Realm observableRealm = Realm.getDefaultInstance();
RealmResults<T> results = realm.where(clazz)./*...*/.findAllSortedAsync("field", Sort.ASCENDING);
final RealmChangeListener<RealmResults<T>> listener = _results -> {
if(!emitter.isDisposed()) {
emitter.onNext(_results);
}
};
emitter.setDisposable(Disposables.fromRunnable(() -> {
observableRealm.removeChangeListener(listener);
observableRealm.close();
}));
observableRealm.addChangeListener(listener);
emitter.onNext(observableRealm);
}
}, BackpressureStrategy.LATEST).subscribeOn(scheduler).unsubscribeOn(scheduler);
或创建您自己的 MutableLiveList
界面。
public interface MutableLiveList<T> extends List<T> {
public interface ChangeListener {
void onChange(MutableLiveList<T> list);
}
void addChangeListener(ChangeListener listener);
void removeChangeListener(ChangeListener listener);
}
我正在 Realm.io 中尝试 Android 应用程序,不过,为了安全起见,我想抽象 DB 层,以便在需要时,我无需重写大部分应用程序即可切换回基于标准 SQLite 的数据库。
然而,由于 Realm 的特殊性质,我发现很难正确抽象它:
- 当绑定到一个领域时,RealmObjects 是代理,所以我不能像 POJO 一样传递它们。
- 所有 Realm 实例都需要为它们在其中使用的每个线程正确打开和关闭。
我已经使用最近的 Realm.copyFromRealm() API 而不是传递与 Realm 相关的 RealmObjects 来绕过这些限制,但我认为这样我就失去了所有使用领域的好处(是吗?)。
有什么建议吗?
我会尝试回答您的第一个困惑。
确实没有必要通过意图传递 RealmObject
s,或者换句话说,它们不需要 parcelable
或 serializable
您应该做的是通过意图传递特定 primary id
或该特定 RealmObject
的其他参数,以便您可以在下一个 activity 中再次查询该 RealmObject。
例如,假设您在启动 activity、
时传递了 primaryIdIntent intent = new Intent(this, NextActivity.class);
intent.putExtra(Constants.INTENT_EXTRA_PRIMARY_ID, id);
现在在 NextActivity
中从 intent 中获取 id 并简单地查询 RealmObject
,即
int primaryId = getIntent().getIntExtra(Constants.INTENT_EXTRA_PRIMARY_ID, -1);
YourRealmObject mObject = realm.where(YourRealmObject.class).equalTo("id", primaryId).findFirst();
看,你在新的 activity 中得到了你的对象,而不是担心传递对象。
我不知道您遇到了关于打开和关闭领域对象的具体问题。你试过 Realm 的 Transaction blocks
了吗?用了就不用依赖开合了
希望对您有所帮助。
更新
既然你在寻找抽象,
只需创建一个像 DbUtils
的 class 并在那里有一个像 getYourObjectById
的方法,然后传递上面的 id 并在 return 中检索你的对象。这使得它在某种程度上是抽象的。然后您可以保留 class 和方法,如果您切换到另一个数据库解决方案,只需更改方法内容即可。
提供不同意见,因为我否决了另一个答案。我想先讨论一下你的 objective
in case of need, I can switch back to a standard SQLite based DB without rewriting most of the app
Realm 不等同于标准的基于 SQLite 的数据库。在这种情况下,您正在寻找的是 StorIO 之类的东西。 Realm 的设计方式使得您必须使用 RealmObject
作为模型的基础。如果您只想从 table 检索数据,Realm 不适合您。
现在,在 Parse 崩溃之后,抽象第三方库的需求变得明显。所以,如果这是你的动机,那么你真的必须包装你在 Realm 中使用的每一件东西。像这样:
public class Db {
public class Query<M extends RealmObject> {
private final RealmQuery<M> realmQuery;
Query(Db db, Class<M> clazz) {
this.realmQuery = RealmQuery.createQuery(db.realmInstance, clazz);
}
public Query<M> equalTo(String field, Integer value) {
realmQuery.equalTo(field, value);
return this;
}
public Results<M> findAll() {
return new Results<>(realmQuery.findAll());
}
public M findFirst() {
return realmQuery.findFirst();
}
}
public class Results<M extends RealmObject> extends AbstractList<M> {
private final RealmResults<M> results;
Results(RealmResults<M> results) {
this.results = results;
}
public void removeLast() {
results.removeLast();
}
@Override
public M get(int i) {
return results.get(i);
}
@Override
public int size() {
return results.size();
}
}
public interface Transaction {
void execute();
abstract class Callback {
public abstract void onSuccess();
public void onError(Throwable error) {
}
}
}
private Realm realmInstance;
Db() {
realmInstance = Realm.getDefaultInstance();
}
public void executeTransaction(@NonNull Db.Transaction dbTransaction) {
realmInstance.executeTransaction(realm -> dbTransaction.execute());
}
public void close() {
realmInstance.close();
}
}
此时,您只需在 Db
class 上定义您在应用中使用的任何操作。而且,如果你像我一样,你可能不会使用所有这些,所以你应该没问题。
这个好看吗?没有,有必要吗?取决于你想达到什么。
编辑:为了加分,让Db
实施AutoCloseable
:
try(Db db = new Db()) {
db.executeTransaction(() -> /*...*/);
}
根据 Google I/O 2017 年 Android Architectural Components
的最新公告,在 Android 应用程序中抽象 Realm 的正确方法是:
1.) Realm实例生命周期由ViewModel
class管理,在onCleared()
方法
2.) RealmResults 是一个 MutableLiveData<List<T>>
,因此您可以创建一个 RealmLiveData<T>
class 来包装 RealmResults<T>
.
因此,您可以像这样创建视图模型:
// based on https://github.com/googlesamples/android-architecture-components/blob/178fe541643adb122d2a8925cf61a21950a4611c/BasicSample/app/src/main/java/com/example/android/persistence/viewmodel/ProductListViewModel.java
public class ProductListViewModel {
private final MutableLiveData<List<ProductEntity>> observableProducts = new MutableLiveData<>();
Realm realm;
RealmResults<ProductEntity> results;
RealmChangeListener<RealmResults<ProductEntity>> realmChangeListener = (results) -> {
if(results.isLoaded() && results.isValid()) { // you probably don't need this, just making sure.
observableProducts.setValue(results);
}
};
public ProductListViewModel() {
realm = Realm.getDefaultInstance();
results = realm.where(ProductEntity.class).findAllSortedAsync("id");
// could use a Realm DAO class here
results.addChangeListener(realmChangeListener);
observableProducts.setValue(null); // if using async query API, the change listener will set the loaded results.
}
public LiveData<List<ProductEntity>> getProducts() {
return observableProducts;
}
@Override
protected void onCleared() {
results.removeChangeListener(realmChangeListener);
realm.close();
realm = null;
}
}
或者你可以根据 this article:
将它们分成一个领域视图模型和一个领域实时数据public class LiveRealmData<T extends RealmModel> extends LiveData<RealmResults<T>> {
private RealmResults<T> results;
private final RealmChangeListener<RealmResults<T>> listener =
new RealmChangeListener<RealmResults<T>>() {
@Override
public void onChange(RealmResults<T> results) { setValue(results);}
};
public LiveRealmData(RealmResults<T> realmResults) {
results = realmResults;
}
@Override
protected void onActive() {
results.addChangeListener(listener);
}
@Override
protected void onInactive() {
results.removeChangeListener(listener);
}
}
public class CustomResultViewModel extends ViewModel {
private Realm mDb;
private LiveData<String> mLoansResult;
public CustomResultViewModel() {
mDb = Realm.getDefaultInstance();
mLoansResult = RealmUtils.loanDao(mDb).getAll();
}
public LiveData<String> getLoansResult() {
return mLoansResult;
}
@Override
protected void onCleared() {
mDb.close();
super.onCleared();
}
}
无论哪种方式,您都将 Realm 的自动更新和延迟加载结果集包装到 LiveData 和 ViewModel 中,与 fragments/adapters:
分开// based on https://github.com/googlesamples/android-architecture-components/blob/178fe541643adb122d2a8925cf61a21950a4611c/BasicSample/app/src/main/java/com/example/android/persistence/ProductListFragment.java
public class ProductListFragment extends LifecycleFragment {
private ProductAdapter productAdapter;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
//...
productAdapter = new ProductAdapter(mProductClickCallback);
//...
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
final ProductListViewModel viewModel =
ViewModelProviders.of(this).get(ProductListViewModel.class); // <-- !
subscribeUi(viewModel);
}
private void subscribeUi(ProductListViewModel viewModel) {
// Update the list when the data changes
viewModel.getProducts().observe(this, (myProducts) -> {
if (myProducts == null) {
// ...
} else {
productAdapter.setProductList(myProducts);
//...
}
});
}
}
但是,如果您不使用Android架构组件,即便如此,需要记住的是:
RealmResults is a list of proxy objects that mutates in place, and it has change listeners.
所以你需要的是将其包装为具有最新背压的 Flowable,类似于
private io.reactivex.Flowable<RealmResults<T>> realmResults() {
return io.reactivex.Flowable.create(new FlowableOnSubscribe<RealmResults<T>>() {
@Override
public void subscribe(FlowableEmitter<RealmResults<T>> emitter)
throws Exception {
Realm observableRealm = Realm.getDefaultInstance();
RealmResults<T> results = realm.where(clazz)./*...*/.findAllSortedAsync("field", Sort.ASCENDING);
final RealmChangeListener<RealmResults<T>> listener = _results -> {
if(!emitter.isDisposed()) {
emitter.onNext(_results);
}
};
emitter.setDisposable(Disposables.fromRunnable(() -> {
observableRealm.removeChangeListener(listener);
observableRealm.close();
}));
observableRealm.addChangeListener(listener);
emitter.onNext(observableRealm);
}
}, BackpressureStrategy.LATEST).subscribeOn(scheduler).unsubscribeOn(scheduler);
或创建您自己的 MutableLiveList
界面。
public interface MutableLiveList<T> extends List<T> {
public interface ChangeListener {
void onChange(MutableLiveList<T> list);
}
void addChangeListener(ChangeListener listener);
void removeChangeListener(ChangeListener listener);
}