LiveData结合Room数据库使用以及线程问题

在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方法,需要做线程检查。

发布了28 篇原创文章 · 获赞 27 · 访问量 8299

猜你喜欢

转载自blog.csdn.net/weixin_44666188/article/details/105500779