在LiveData的官方文档中有提到LiveData可以和Room数据库一起使用
也就是说Room查询时可以直接返回一个LiveData对象,给这个LiveData对象添加观察者之后只要数据库数据发生改变都可以收到回调。
Room的使用不在这里说了,直接贴上代码
添加依赖:
androidTestImplementation "android.arch.persistence.room:testing:1.1.1"
implementation 'android.arch.persistence.room:runtime:1.1.1'
annotationProcessor 'android.arch.persistence.room:compiler:1.1.1'
bean:
@Entity(tableName = "user")
public class User {
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@PrimaryKey
@ColumnInfo(name = "id")
private long id;
@ColumnInfo(name = "name")
private String name;
@ColumnInfo(name = "sex")
private String sex;
@ColumnInfo(name = "age")
private int age;
}
UserDao:
@Dao
public interface UserDao {
@Query("SELECT * FROM user")
LiveData<List<User>> getUserLiveData();//在这里可以直接返回LiveData<>封装的查询结果
@Insert
void insert(User user);
@Delete
void delect(User user);
@Update
void update(User user);
}
UserDatabase:
@Database(entities = {User.class},version = 1,exportSchema = false)
public abstract class UserDatabase extends RoomDatabase {
public static final String DB_NAME = "UserDataBase.db";
private static volatile UserDatabase instance;
public static synchronized UserDatabase getInstance(Context context){
if (instance == null){
instance = createDatabase(context);
}
return instance;
}
private static UserDatabase createDatabase(Context context) {
return Room.databaseBuilder(context,UserDatabase.class,DB_NAME).addCallback(new RoomDatabase.Callback(){
@Override
public void onCreate(@NonNull SupportSQLiteDatabase db) {
super.onCreate(db);
}
@Override
public void onOpen(@NonNull SupportSQLiteDatabase db) {
super.onOpen(db);
}
}).build();
}
public abstract UserDao getUserDao();
}
在Activity onCreate的时候去通过数据库获取数据,直接返回LiveData<List< User>>类型对象,给其添加观察者,当数据发生改变时刷新RecycleView
public void addObserver() {
UserDatabase.getInstance(mContext).getUserDao()
.getUserLiveData().observe(mContext, new Observer<List<User>>() {
@Override
public void onChanged(List<User> users) {
adapter.refreshList((ArrayList<User>) users);
}
});
}
在布局中添加按钮,点击添加一条新的数据到数据库
public void addData(View view) {
new Thread(new Runnable() {
@Override
public void run() {
User user = new User();
user.setId(new Random().nextLong());
user.setAge(25);
user.setName("老张");
user.setSex("男");
UserDatabase.getInstance(mContext).getUserDao().insert(user);
}
}).start();
}
点击后RecycleView自动添加一条数据。
这里需要注意一点,因为对数据库的操作很可能耗时比较久,所以Room对数据库的操作是需要在子线程中进行的,如果在主线程中操作数据库会报错。
Caused by: java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
at androidx.room.RoomDatabase.assertNotMainThread(RoomDatabase.java:209)
而我上面写的这个例子addObserver()中getUserLiveData()的时候并没有开线程,而且运行的时候也没有出现问题。
比较好奇就去看了下RoomDatabase.java中的代码
首先这个报错是对数据库操作的时候会去判断线程
/**
* Asserts that we are not on the main thread.
*
* @hide
*/
@SuppressWarnings("WeakerAccess")
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
// used in generated code
public void assertNotMainThread() {
if (mAllowMainThreadQueries) {
return;
}
if (isMainThread()) {
throw new IllegalStateException("Cannot access database on the main thread since"
+ " it may potentially lock the UI for a long period of time.");
}
}
从code中可以看出mAllowMainThreadQueries这个属性可以绕过这个限制。
@NonNull
public Builder<T> allowMainThreadQueries() {
mAllowMainThreadQueries = true;
return this;
}
也就是说在使用Builder创建时只要
Room.databaseBuilder(context,UserDatabase.class,DB_NAME).allowMainThreadQueries()
就可以允许在主线程中操作数据库,但这样肯定是不推荐的,可能造成卡顿甚至ANR,应该是测试的时候使用的。
除此之外我还发现了这个方法,也就是说Room可以自己传线程池去处理数据库耗时操作。
@NonNull
public Builder<T> setQueryExecutor(@NonNull Executor executor) {
mQueryExecutor = executor;
return this;
}
回到之前的问题,为什么使用LiveData作为返回值的时候可以不用自己开线程
这部分逻辑在Room编译时自动生成的类中
UserDao_Impl.java
我写了两个Query方法,一个返回LiveData对象,一个返回List< User>,看一下区别
@Override
public LiveData<List<User>> getUserLiveData() {
final String _sql = "SELECT * FROM user";
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
return new ComputableLiveData<List<User>>(__db.getQueryExecutor()) {
private Observer _observer;
@Override
protected List<User> compute() {
if (_observer == null) {
_observer = new Observer("user") {
@Override
public void onInvalidated(@NonNull Set<String> tables) {
invalidate();
}
};
__db.getInvalidationTracker().addWeakObserver(_observer);
}
final Cursor _cursor = __db.query(_statement);
try {
final int _cursorIndexOfId = _cursor.getColumnIndexOrThrow("id");
final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
final int _cursorIndexOfSex = _cursor.getColumnIndexOrThrow("sex");
final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("age");
final List<User> _result = new ArrayList<User>(_cursor.getCount());
while(_cursor.moveToNext()) {
final User _item;
_item = new User();
final long _tmpId;
_tmpId = _cursor.getLong(_cursorIndexOfId);
_item.setId(_tmpId);
final String _tmpName;
_tmpName = _cursor.getString(_cursorIndexOfName);
_item.setName(_tmpName);
final String _tmpSex;
_tmpSex = _cursor.getString(_cursorIndexOfSex);
_item.setSex(_tmpSex);
final int _tmpAge;
_tmpAge = _cursor.getInt(_cursorIndexOfAge);
_item.setAge(_tmpAge);
_result.add(_item);
}
return _result;
} finally {
_cursor.close();
}
}
@Override
protected void finalize() {
_statement.release();
}
}.getLiveData();
}
@Override
public List<User> getAll() {
final String _sql = "SELECT * FROM user";
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
final Cursor _cursor = __db.query(_statement);
try {
final int _cursorIndexOfId = _cursor.getColumnIndexOrThrow("id");
final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
final int _cursorIndexOfSex = _cursor.getColumnIndexOrThrow("sex");
final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("age");
final List<User> _result = new ArrayList<User>(_cursor.getCount());
while(_cursor.moveToNext()) {
final User _item;
_item = new User();
final long _tmpId;
_tmpId = _cursor.getLong(_cursorIndexOfId);
_item.setId(_tmpId);
final String _tmpName;
_tmpName = _cursor.getString(_cursorIndexOfName);
_item.setName(_tmpName);
final String _tmpSex;
_tmpSex = _cursor.getString(_cursorIndexOfSex);
_item.setSex(_tmpSex);
final int _tmpAge;
_tmpAge = _cursor.getInt(_cursorIndexOfAge);
_item.setAge(_tmpAge);
_result.add(_item);
}
return _result;
} finally {
_cursor.close();
_statement.release();
}
}
很明显没有使用LiveData的时候直接走RoomDatabase的query方法,是需要做线程检查的
/**
* Wrapper for {@link SupportSQLiteDatabase#query(SupportSQLiteQuery)}.
*
* @param query The Query which includes the SQL and a bind callback for bind arguments.
* @return Result of the query.
*/
public Cursor query(SupportSQLiteQuery query) {
assertNotMainThread();
return mOpenHelper.getWritableDatabase().query(query);
}
而使用LiveData作为返回值时用到了ComputableLiveData类,此类在构造的时候就将RoomDatabase中的mQueryExecutor传入了。如果在构造的时候没有传入自定义的Executor,那么会自动生成一个
if (mQueryExecutor == null) {
mQueryExecutor = ArchTaskExecutor.getIOThreadExecutor();
}
UserDao_Impl.java中复写了compute()方法
在ComputableLiveData.java中mRefreshRunnable 调用了compute() 方法
@VisibleForTesting
final Runnable mRefreshRunnable = new Runnable() {
@WorkerThread
@Override
public void run() {
boolean computed;
do {
computed = false;
// compute can happen only in 1 thread but no reason to lock others.
if (mComputing.compareAndSet(false, true)) {
// as long as it is invalid, keep computing.
try {
T value = null;
while (mInvalid.compareAndSet(true, false)) {
computed = true;
value = compute();
}
if (computed) {
mLiveData.postValue(value);
}
} finally {
// release compute lock
mComputing.set(false);
}
}
// check invalid after releasing compute lock to avoid the following scenario.
// Thread A runs compute()
// Thread A checks invalid, it is false
// Main thread sets invalid to true
// Thread B runs, fails to acquire compute lock and skips
// Thread A releases compute lock
// We've left invalid in set state. The check below recovers.
} while (computed && mInvalid.get());
}
};
ComputableLiveData构造方法中执行了这个runnable对象
/**
* Creates a computable live data that computes values on the specified executor.
*
* @param executor Executor that is used to compute new LiveData values.
*/
@SuppressWarnings("WeakerAccess")
public ComputableLiveData(@NonNull Executor executor) {
mExecutor = executor;
mLiveData = new LiveData<T>() {
@Override
protected void onActive() {
mExecutor.execute(mRefreshRunnable);
}
};
}
OK,看到这里就比较明了了,使用LiveData的时候自动生成的代码会使用子线程操作数据库,而不使用LiveData的时候直接走query方法,需要做线程检查。