在 Android 应用程序中抽象 Realm 的正确方法

Proper way to abstract Realm in Android apps

我正在 Realm.io 中尝试 Android 应用程序,不过,为了安全起见,我想抽象 DB 层,以便在需要时,我无需重写大部分应用程序即可切换回基于标准 SQLite 的数据库。

然而,由于 Realm 的特殊性质,我发现很难正确抽象它:

我已经使用最近的 Realm.copyFromRealm() API 而不是传递与 Realm 相关的 RealmObjects 来绕过这些限制,但我认为这样我就失去了所有使用领域的好处(是吗?)。

有什么建议吗?

我会尝试回答您的第一个困惑。 确实没有必要通过意图传递 RealmObjects,或者换句话说,它们不需要 parcelableserializable

您应该做的是通过意图传递特定 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实例生命周期由ViewModelclass管理,在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);
}