最近要做的項(xiàng)目要涉及到數(shù)據(jù)庫(kù)碴裙,準(zhǔn)備使用谷歌新出的架構(gòu)組件Room,于是學(xué)習(xí)學(xué)習(xí)焕阿,記錄一下。
官方文檔在這里首启,推薦一個(gè)系列的文章暮屡,很好的翻譯了官方架構(gòu)的文檔——理解Android Architecture Components系列。
1.引入
首先在project的gradle文件中添加 Google Maven 倉(cāng)庫(kù)
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:數(shù)據(jù)庫(kù)中表對(duì)應(yīng)的Java實(shí)體
- DAO:操作數(shù)據(jù)庫(kù)的方法
- Database:創(chuàng)建數(shù)據(jù)庫(kù)
2.1Entity
Entity就是一般的Java實(shí)體類
這里只涉及到@Entity毅桃、 @PrimaryKey褒纲,2個(gè)注解准夷。其實(shí)有其他好多注解,比如 @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的話就是創(chuàng)建一些訪問(wèn)數(shù)據(jù)庫(kù)的方法
通過(guò)注解的方式實(shí)現(xiàn)增(@Insert)刪(@Delete)改(@Update)查(@Query)
可以通過(guò)“:xxx”的方式引入?yún)?shù)
@Dao
public interface UserDao {
@Insert
void insert(UserEntity userEntity);
@Insert
void insertAll(List<UserEntity> userEntities);
@Delete
void delete(UserEntity userEntity);
@Delete
void deleteAll(List<UserEntity> userEntities);
@Update
void update(UserEntity userEntity);
@Query("SELECT * FROM UserEntity")
List<UserEntity> getAll();
@Query("SELECT * FROM UserEntity WHERE uid = :uid")
UserEntity getByUid(int uid);
}
2.3 Database
上面兩個(gè)文件建好后渐扮,就可以創(chuàng)建數(shù)據(jù)庫(kù)了
要在里面聲明你創(chuàng)建的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相關(guān),具體看上面推薦的系列文章LiveData
*/
private final MutableLiveData<Boolean> 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<UserEntity> 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<UserEntity> users) {
database.runInTransaction(() -> {
database.userDao().insertAll(users);
});
}
public LiveData<Boolean> getDatabaseCreated() {
return mIsDatabaseCreated;
}
private void setDatabaseCreated(){
mIsDatabaseCreated.postValue(true);
}
}
數(shù)據(jù)生成工具:
/**
* 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<UserEntity> generateUsers() {
List<UserEntity> 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數(shù)據(jù)庫(kù)升級(jí)
數(shù)據(jù)庫(kù)升級(jí)要?jiǎng)?chuàng)建Migration類掖棉,在migrate方法中添加改變的SQL語(yǔ)句墓律,然后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訪問(wèn)數(shù)據(jù)庫(kù)
三大組件創(chuàng)建好,你就可以使用數(shù)據(jù)庫(kù)啦
數(shù)據(jù)庫(kù)和線程池工具(AppExecutors)我都放在自定義的Application當(dāng)中去獲取了幔亥。
其實(shí)可以再創(chuàng)建一個(gè)Repository倉(cāng)庫(kù)類耻讽,里面放本地?cái)?shù)據(jù)庫(kù)的訪問(wèn)和網(wǎng)絡(luò)的訪問(wèn),更好地管理數(shù)據(jù)來(lái)源帕棉。
private List<UserEntity> 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.
* <p>
* 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
大家發(fā)現(xiàn)了针肥,訪問(wèn)數(shù)據(jù)庫(kù)是不能再主線程中進(jìn)行的,所以有了個(gè)AppExecutors 線程池來(lái)操作香伴。但我們有了RxJava慰枕,是不是可以用它來(lái)替代,答案是肯定的即纲,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"
然后改寫(xiě)DAO中的返回類型
@Query("SELECT * FROM UserEntity")
Flowable<List<UserEntity>> getAll();
Room 支持RxJava的三種對(duì)象
- Maybe
- Single
- Flowable
三者主要區(qū)別在于回調(diào)方法的觸發(fā)低斋,具體可以參考這篇文章——在Room中使用RxJava
這樣Room就自動(dòng)為我們創(chuàng)建好了可觀察的對(duì)象蜂厅,我們這樣使用就好了
private List<UserEntity> mUserList;
MyApplication.getInstance().getDatabase().userDao().getAll()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<List<UserEntity>>() {
@Override
public void accept(List<UserEntity> userEntities) throws Exception {
mUserList = userEntities;
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
}
});
Consumer 是RxJava觀察者的簡(jiǎn)寫(xiě)方式
flowable.subscribe(
new Consumer<String>() {//相當(dāng)于onNext
@Override
public void accept(String s) throws Exception {
}
}, new Consumer<Throwable>() {//相當(dāng)于onError
@Override
public void accept(Throwable throwable) throws Exception {
}
}, new Action() {//相當(dāng)于onComplete,注意這里是Action
@Override
public void run() throws Exception {
}
}, new Consumer<Subscription>() {//相當(dāng)于onSubscribe
@Override
public void accept(Subscription subscription) throws Exception {
}
});
這里我有個(gè)疑問(wèn)膊畴,如果我不用簡(jiǎn)寫(xiě)的方式掘猿,只能觸發(fā)onSubscribe回調(diào),在onNext回調(diào)中獲取不到查詢數(shù)據(jù)庫(kù)的結(jié)果巴比。(試了下术奖,Single類型不會(huì))
希望有大神能幫我解惑
MyApplication.getInstance().getDatabase().userDao().getAll()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new FlowableSubscriber<List<UserEntity>>() {
@Override
public void onSubscribe(Subscription s) {
}
@Override
public void onNext(List<UserEntity> userEntities) {
//獲取不到
mUserList = userEntities;
}
@Override
public void onError(Throwable t) {
}
@Override
public void onComplete() {
}
});
有一點(diǎn)值得注意的是:如果使用Flowable,那么每當(dāng)Flowable包裹的對(duì)象改變時(shí)轻绞,F(xiàn)lowable將自動(dòng)發(fā)射,也就是自動(dòng)執(zhí)行accept回調(diào)佣耐。
看這樣一個(gè)情況:
當(dāng)我點(diǎn)擊Item就會(huì)增加一條name為“Mao+position”的記錄到數(shù)據(jù)庫(kù)中:
然后我要點(diǎn)擊右下角的FloatingActionButton刪除這些新加的name為“Mao”開(kāi)頭的記錄政勃,那么應(yīng)該是先查出這些記錄,然后刪除兼砖。(我增加了DataRepository倉(cāng)庫(kù)來(lái)操作)
對(duì)應(yīng)的DAO:
@Query("SELECT * FROM userentity WHERE name like 'Mao%'")
Flowable<List<UserEntity>> getAllLikeMao();
@Delete
void deleteAll(List<UserEntity> userEntities);
對(duì)應(yīng)的操作:
@SuppressLint("CheckResult")
public void deleteMao() {
mDataRepository.getAllLikeMao().
subscribeOn(Schedulers.io()).
observeOn(Schedulers.io()).
subscribe(userEntities -> {
mDataRepository.deleteAll(userEntities);
mUserEntityList.postValue(mDataRepository.getAll());
});
可以看到:點(diǎn)擊FloatingActionButton確實(shí)刪除了相應(yīng)的記錄奸远,但是再次點(diǎn)擊Item增加時(shí)既棺,增加的Item立馬被刪除了。
究其原因就是Flowable的自動(dòng)發(fā)射懒叛。每次點(diǎn)擊增加Item改變了List<UserEntity>丸冕,所以就每次觸發(fā)了
mDataRepository.deleteAll(userEntities);
mUserEntityList.postValue(mDataRepository.getAll());
讓我們改成Single試試:
@Query("SELECT * FROM userentity WHERE name like 'Mao%'")
Single<List<UserEntity>> getAllLikeMao();
這回沒(méi)有自動(dòng)發(fā)射了,也正常了薛窥,是不是數(shù)據(jù)庫(kù)操作都應(yīng)該使用Single而不是Flowable呢胖烛?希望大家討論一下。
------------------------------------分割線---------------------------------------------
關(guān)于上面【這里我有個(gè)疑問(wèn)诅迷,如果我不用簡(jiǎn)寫(xiě)的方式佩番,只能觸發(fā)onSubscribe回調(diào),在onNext回調(diào)中獲取不到查詢數(shù)據(jù)庫(kù)的結(jié)果罢杉。(試了下趟畏,Single類型不會(huì))】這個(gè)問(wèn)題:
其實(shí)是RxJava2.0后的一點(diǎn)小區(qū)別:出現(xiàn)了2種觀察者模式
- Observable(被觀察者)/Observer(觀察者)
- Flowable(被觀察者)/Subscriber(觀察者)
Room支持的3種RxJava對(duì)象中,Maybe和Single 屬于前一種滩租,F(xiàn)lowable 屬于后一種赋秀。
2種觀察者的區(qū)別是:Observeable用于訂閱Observer,是不支持背壓的律想,而Flowable用于訂閱Subscriber猎莲,是支持背壓(Backpressure)的。
背壓是指在異步場(chǎng)景中蜘欲,被觀察者發(fā)送事件速度遠(yuǎn)快于觀察者的處理速度的情況下益眉,一種告訴上游的被觀察者降低發(fā)送速度的策略。
Observable在 subscribe方法中調(diào)用emitter.onNext()才能觸發(fā)之后的onNext方法姥份;Flowable也相同郭脂,也需要一個(gè)通知,這里發(fā)揮作用的就是subscription.request(n)方法,參數(shù)n代表上游發(fā)送多少個(gè)數(shù)據(jù)澈歉。
其實(shí)Flowable也支持emitter.onNext()展鸡,但是要指定具體的背壓策略,具體可以看這篇文章:關(guān)于 RxJava 最友好的文章—— RxJava 2.0 全新來(lái)襲埃难。