最近要做的项目要涉及到数据库,准备使用谷歌新出的架构组件Room,于是学习学习,记录一下。
官方文档在这里,推荐一个系列的文章,很好的翻译了官方架构的文档——理解Android Architecture Components系列。
RoomDemo代码下载
1.引入
首先在project的gradle文件中添加 Google Maven 仓库
allprojects {
repositories {
jcenter()
google()
}
}
然后在APP的gradle文件中添加依赖
// Room (use 1.1.0-beta2 for latest beta)
implementation "android.arch.persistence.room:runtime:1.0.0"
annotationProcessor "android.arch.persistence.room:compiler:1.0.0"
2.三大组成部分
Room由三大部分组成
- Entity:数据库中表对应的Java实体
- DAO:操作数据库的方法
- Database:创建数据库
2.1Entity
Entity就是一般的Java实体类
这里只涉及到@Entity、 @PrimaryKey,2个注解。其实有其他好多注解,比如 @ColumnInfo(列名)、 @ForeignKey(外键)、@Index(索引)等等,具体用法还是看官方文档吧。
@Entity
public class UserEntity {
@PrimaryKey(autoGenerate = true)
private int uid;
private String name;
private String address;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public int getUid() {
return uid;
}
public void setUid(int uid) {
this.uid = uid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "UserEntity{" +
"uid=" + uid +
", name='" + name + '\'' +
", address='" + address + '\'' +
'}';
}
}
2.2 DAO
DAO的话就是创建一些访问数据库的方法
通过注解的方式实现增(@Insert)删(@Delete)改(@Update)查(@Query)
可以通过“:xxx”的方式引入参数
@Dao
public interface UserDao {
@Insert
void insert(UserEntity userEntity);
@Insert
void insertAll(List userEntities);
@Delete
void delete(UserEntity userEntity);
@Delete
void deleteAll(List userEntities);
@Update
void update(UserEntity userEntity);
@Query("SELECT * FROM UserEntity")
List getAll();
@Query("SELECT * FROM UserEntity WHERE uid = :uid")
UserEntity getByUid(int uid);
}
2.3 Database
上面两个文件建好后,就可以创建数据库了
要在里面声明你创建的DAO:public abstract UserDao userDao();
参考的是官方demo BasicSample
@Database(entities = {UserEntity.class},version = 1)
public abstract class AppDatabase extends RoomDatabase{
private static volatile AppDatabase sInstance;
@VisibleForTesting
public static final String DATABASE_NAME = "XXXdb";
/**
* LiveData相关,具体看上面推荐的系列文章LiveData
*/
private final MutableLiveData mIsDatabaseCreated = new MutableLiveData<>();
public abstract UserDao userDao();
public static AppDatabase getInstance(final Context context, final AppExecutors executors) {
if (sInstance == null) {
synchronized (AppDatabase.class) {
if (sInstance == null) {
sInstance = buildDatabase(context.getApplicationContext(), executors);
sInstance.updateDatabaseCreated(context.getApplicationContext());
}
}
}
return sInstance;
}
/**
* Build the database. {@link Builder#build()} only sets up the database configuration and
* creates a new instance of the database.
* The SQLite database is only created when it's accessed for the first time.
*/
private static AppDatabase buildDatabase(final Context appContext,
final AppExecutors executors) {
return Room.databaseBuilder(appContext, AppDatabase.class, DATABASE_NAME)
.addCallback(new Callback() {
@Override
public void onCreate(@NonNull SupportSQLiteDatabase db) {
super.onCreate(db);
executors.diskIO().execute(() -> {
// Add a delay to simulate a long-running operation
addDelay();
// Generate the data for pre-population
AppDatabase database = AppDatabase.getInstance(appContext, executors);
List users = DataGenerator.generateUsers();
insertData(database, users);
// notify that the database was created and it's ready to be used
database.setDatabaseCreated();
});
}
})
.build();
}
/**
* Check whether the database already exists and expose it via {@link #getDatabaseCreated()}
*/
private void updateDatabaseCreated(final Context context) {
if (context.getDatabasePath(DATABASE_NAME).exists()) {
setDatabaseCreated();
}
}
private static void addDelay() {
try {
Thread.sleep(4000);
} catch (InterruptedException ignored) {
}
}
private static void insertData(final AppDatabase database, final List users) {
database.runInTransaction(() -> {
database.userDao().insertAll(users);
});
}
public LiveData getDatabaseCreated() {
return mIsDatabaseCreated;
}
private void setDatabaseCreated(){
mIsDatabaseCreated.postValue(true);
}
}
数据生成工具:
/**
* Generates data to pre-populate the database
*/
public class DataGenerator {
private static final String[] NAME = new String[]{
"Special edition", "New", "Cheap", "Quality", "Used"};
private static final String[] ADDRESS = new String[]{
"Three-headed Monkey", "Rubber Chicken", "Pint of Grog", "Monocle"};
public static List generateUsers() {
List userEntities = new ArrayList<>();
for (int i = 0; i < MM.length; i++) {
UserEntity product = new UserEntity();
product.setName(NAME[ i ]);
product.setAddress(ADDRESS [ i ]);
userEntities.add(product);
}
return userEntities;
}
}
3数据库升级
数据库升级要创建Migration类,在migrate方法中添加改变的SQL语句,然后version加1
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE UserEntity "
+ " ADD COLUMN address TEXT");
}
};
在build()之前添加addMigrations
return Room.databaseBuilder(appContext, AppDatabase.class, DATABASE_NAME)
.addMigrations(MIGRATION_1_2)
.build();
@Database(entities = {UserEntity.class},version = 2)
4访问数据库
三大组件创建好,你就可以使用数据库啦
数据库和线程池工具(AppExecutors)我都放在自定义的Application当中去获取了。
其实可以再创建一个Repository仓库类,里面放本地数据库的访问和网络的访问,更好地管理数据来源。
private List mUserList;
MyApplication.getInstance().getAppExecutors().diskIO().execute(new Runnable() {
@Override
public void run() {
mUserList = MyApplication.getInstance().getDatabase().userDao().getAll();
}
});
MyApplication:
public class MyApplication extends Application{
private static MyApplication app;
private AppExecutors mAppExecutors;
@Override
public void onCreate() {
super.onCreate();
app = this;
mAppExecutors = new AppExecutors();
Logger.addLogAdapter(new AndroidLogAdapter());
}
public static MyApplication getInstance() {
return app;
}
public AppDatabase getDatabase() {
return AppDatabase.getInstance(this, mAppExecutors);
}
public AppExecutors getAppExecutors(){
return mAppExecutors;
}
}
AppExecutors:
/**
* Global executor pools for the whole application.
*
* Grouping tasks like this avoids the effects of task starvation (e.g. disk reads don't wait behind
* webservice requests).
*/
public class AppExecutors {
private final Executor mDiskIO;
private final Executor mNetworkIO;
private final Executor mMainThread;
private AppExecutors(Executor diskIO, Executor networkIO, Executor mainThread) {
this.mDiskIO = diskIO;
this.mNetworkIO = networkIO;
this.mMainThread = mainThread;
}
public AppExecutors() {
this(Executors.newSingleThreadExecutor(), Executors.newFixedThreadPool(3),
new MainThreadExecutor());
}
public Executor diskIO() {
return mDiskIO;
}
public Executor networkIO() {
return mNetworkIO;
}
public Executor mainThread() {
return mMainThread;
}
private static class MainThreadExecutor implements Executor {
private Handler mainThreadHandler = new Handler(Looper.getMainLooper());
@Override
public void execute(@NonNull Runnable command) {
mainThreadHandler.post(command);
}
}
}
5配合RxJava
大家发现了,访问数据库是不能再主线程中进行的,所以有了个AppExecutors 线程池来操作。但我们有了RxJava,是不是可以用它来替代,答案是肯定的,Room支持RxJava。
我这里也推荐RxJava使用的系列文章,真的很好——RxJava系列教程
首先在APP的gradle文件中再添加Room的RxJava支持
// RxJava support for Room (use 1.1.0-beta2 for latest alpha)
implementation "android.arch.persistence.room:rxjava2:1.0.0"
然后改写DAO中的返回类型
@Query("SELECT * FROM UserEntity")
Flowable> getAll();
Room 支持RxJava的三种对象
- Maybe
- Single
- Flowable
三者主要区别在于回调方法的触发,具体可以参考这篇文章——在Room中使用RxJava
这样Room就自动为我们创建好了可观察的对象,我们这样使用就好了
private List mUserList;
MyApplication.getInstance().getDatabase().userDao().getAll()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer>() {
@Override
public void accept(List userEntities) throws Exception {
mUserList = userEntities;
}
}, new Consumer() {
@Override
public void accept(Throwable throwable) throws Exception {
}
});
Consumer 是RxJava观察者的简写方式
flowable.subscribe(
new Consumer() {//相当于onNext
@Override
public void accept(String s) throws Exception {
}
}, new Consumer() {//相当于onError
@Override
public void accept(Throwable throwable) throws Exception {
}
}, new Action() {//相当于onComplete,注意这里是Action
@Override
public void run() throws Exception {
}
}, new Consumer() {//相当于onSubscribe
@Override
public void accept(Subscription subscription) throws Exception {
}
});
这里我有个疑问,如果我不用简写的方式,只能触发onSubscribe回调,在onNext回调中获取不到查询数据库的结果。(试了下,Single类型不会)
希望有大神能帮我解惑
MyApplication.getInstance().getDatabase().userDao().getAll()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new FlowableSubscriber>() {
@Override
public void onSubscribe(Subscription s) {
}
@Override
public void onNext(List userEntities) {
//获取不到
mUserList = userEntities;
}
@Override
public void onError(Throwable t) {
}
@Override
public void onComplete() {
}
});
有一点值得注意的是:如果使用Flowable,那么每当Flowable包裹的对象改变时,Flowable将自动发射,也就是自动执行accept回调。
看这样一个情况:
当我点击Item就会增加一条name为“Mao+position”的记录到数据库中:
然后我要点击右下角的FloatingActionButton删除这些新加的name为“Mao”开头的记录,那么应该是先查出这些记录,然后删除。(我增加了DataRepository仓库来操作)
对应的DAO:
@Query("SELECT * FROM userentity WHERE name like 'Mao%'")
Flowable> getAllLikeMao();
@Delete
void deleteAll(List userEntities);
对应的操作:
@SuppressLint("CheckResult")
public void deleteMao() {
mDataRepository.getAllLikeMao().
subscribeOn(Schedulers.io()).
observeOn(Schedulers.io()).
subscribe(userEntities -> {
mDataRepository.deleteAll(userEntities);
mUserEntityList.postValue(mDataRepository.getAll());
});
可以看到:点击FloatingActionButton确实删除了相应的记录,但是再次点击Item增加时,增加的Item立马被删除了。
究其原因就是Flowable的自动发射。每次点击增加Item改变了List
mDataRepository.deleteAll(userEntities);
mUserEntityList.postValue(mDataRepository.getAll());
让我们改成Single试试:
@Query("SELECT * FROM userentity WHERE name like 'Mao%'")
Single> getAllLikeMao();
这回没有自动发射了,也正常了,是不是数据库操作都应该使用Single而不是Flowable呢?希望大家讨论一下。
------------------------------------分割线---------------------------------------------
关于上面【这里我有个疑问,如果我不用简写的方式,只能触发onSubscribe回调,在onNext回调中获取不到查询数据库的结果。(试了下,Single类型不会)】这个问题:
其实是RxJava2.0后的一点小区别:出现了2种观察者模式
- Observable(被观察者)/Observer(观察者)
- Flowable(被观察者)/Subscriber(观察者)
Room支持的3种RxJava对象中,Maybe和Single 属于前一种,Flowable 属于后一种。
2种观察者的区别是:Observeable用于订阅Observer,是不支持背压的,而Flowable用于订阅Subscriber,是支持背压(Backpressure)的。
背压是指在异步场景中,被观察者发送事件速度远快于观察者的处理速度的情况下,一种告诉上游的被观察者降低发送速度的策略。
Observable在 subscribe方法中调用emitter.onNext()才能触发之后的onNext方法;Flowable也相同,也需要一个通知,这里发挥作用的就是subscription.request(n)方法,参数n代表上游发送多少个数据。
其实Flowable也支持emitter.onNext(),但是要指定具体的背压策略,具体可以看这篇文章:关于 RxJava 最友好的文章—— RxJava 2.0 全新来袭。