Jetpack Room 浅入浅出 Part II

253 阅读4分钟

Room 的内部基本实现

Room在编译期通过kapt处理@Dao@Database注解,并生成DAODatabase的实现类,实现一个简单的例子:

Database:

定义一个UserDatabase,只有一个实体User

@Database(entities = [User::class], version = 1)
abstract class UserDatabase : RoomDatabase() {
  abstract fun userDao(): UserDao
}

Entity: User

User有三个字段(Column):

@Entity
data class User(
    @PrimaryKey val uid: Int,
    @ColumnInfo(name = "first_name") val firstName: String?,
    @ColumnInfo(name = "last_name") val lastName: String?
)

UserDao

接口定义 UserDao:

@Dao
interface UserDao {
    @Query("SELECT * FROM user")
    fun getAll(): List<User>

    @Insert
    fun insertAll(vararg users: User)

    @Delete
    fun delete(user: User)
}

**kapt **生成的代码在 build/generated/source/kapt/中生成了UserDatabase_ImplUserDao_Impl:

kapt-impls.png

UserDatabase_Impl:


@SuppressWarnings({"unchecked", "deprecation"})
public final class UserDatabase_Impl extends UserDatabase {
  private volatile UserDao _userDao;

  @Override
  protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration configuration) {
    final SupportSQLiteOpenHelper.Callback _openCallback = new RoomOpenHelper(configuration, new RoomOpenHelper.Delegate(1) {
      @Override
      public void createAllTables(SupportSQLiteDatabase _db) {
        // impl
      }

      @Override
      public void dropAllTables(SupportSQLiteDatabase _db) {
        // impl
      }

      @Override
      protected void onCreate(SupportSQLiteDatabase _db) {
        // impl
      }

      @Override
      public void onOpen(SupportSQLiteDatabase _db) {
        
      }

      @Override
      public void onPreMigrate(SupportSQLiteDatabase _db) {
        // impl
      }

      @Override
      public void onPostMigrate(SupportSQLiteDatabase _db) {
        // impl
      }
      

  @Override
  protected InvalidationTracker createInvalidationTracker() {
    final HashMap<String, String> _shadowTablesMap = new HashMap<String, String>(0);
    HashMap<String, Set<String>> _viewTables = new HashMap<String, Set<String>>(0);
    return new InvalidationTracker(this, _shadowTablesMap, _viewTables, "User");
  }

  @Override
  public void clearAllTables() {
    super.assertNotMainThread();
    // impl
  }

  @Override
  public UserDao userDao() {
    // impl
  }
}

  • createOpenHelperRoom.databaseBuilder().build()创建Database时,会调用实现类的createOpenHelper()创建SupportSQLiteOpenHelper,此Helper用来创建DB以及管理版本
  • createInvalidationTracker :创建跟踪器,确保table的记录修改时能通知到相关回调方
  • clearAllTables:清空table的实现
  • userDao:创建 UserDao_Impl

User_Impl:


@SuppressWarnings({"unchecked", "deprecation"})
public final class UserDao_Impl implements UserDao {
  private final RoomDatabase __db;

  private final EntityInsertionAdapter<User> __insertionAdapterOfUser;

  private final EntityDeletionOrUpdateAdapter<User> __deletionAdapterOfUser;

  public UserDao_Impl(RoomDatabase __db) {
    this.__db = __db;
    this.__insertionAdapterOfUser = new EntityInsertionAdapter<User>(__db) {
      // Impl
    };
    this.__deletionAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
      // Impl
    };
  }

  @Override
  public void insertAll(final User... users) {
    // impl
  }

  @Override
  public void delete(final User user) {
    // impl
  }

  @Override
  public List<User> getAll() {
    // impl
  }

  public static List<Class<?>> getRequiredConverters() {
    return Collections.emptyList();
  }
}

UserDao_Impl 主要有三个属性:

  • __db:RoomDatabase的实例
  • __insertionAdapterOfUserEntityInsertionAdapterd实例,用于数据 insert。上例中,将在installAll()中调用
  • __deletionAdapterOfUserEntityDeletionOrUpdateAdapter实例,用于数据的 update/delete。 上例中,在delete()中调用

RoomDatabase.Builder

Room通过Build模式创建Database实例

val userDatabase = Room.databaseBuilder(
    applicationContext,
    UserDatabase::class.java,
    "users-db"
).build()

Builder的好处时便于对Database进行配置

  • createFromAsset()/createFromFile() :从SD卡或者Asset的db文件创建RoomDatabase实例
  • addMigrations() :添加一个数据库迁移(migration),当进行数据版本升级时需要
  • allowMainThreadQueries() :允许在UI线程进行数据库查询,默认是不允许的
  • fallbackToDestructiveMigration() :如果找不到migration则重建数据库表(会造成数据丢失)

除上面以外,还有其他很多配置。调用build()后,创建UserDatabase_Impl,并调用init(),内部会调用createOpenHelper()

UserDao:

@Override
public UserDao userDao() {
  if (_userDao != null) {
    return _userDao;
  } else {
    synchronized(this) {
      if(_userDao == null) {
        _userDao = new UserDao_Impl(this);
      }
      return _userDao;
    }
  } 
}

重写userDao(),向RoomDatabase传入 UserDao_Impl

insertAll()

  @Override
  public void insertAll(final User... users) {
    __db.assertNotSuspendingTransaction();
    __db.beginTransaction();
    try {
      __insertionAdapterOfUser.insert(users);
      __db.setTransactionSuccessful();
    } finally {
      __db.endTransaction();
    }
  }

使用 __db开启事务,使用__insertionAdapterOfUser执行插入操作

delete()

@Override
public void delete(final User user) {
  __db.assertNotSuspendingTransaction();
  __db.beginTransaction();
  try {
    __deletionAdapterOfUser.handle(user);
    __db.setTransactionSuccessful();
  } finally {
    __db.endTransaction();
  }
}

使用 __db开启事务,使用__deletionAdapterOfUser执行删除

getAll()

@Override
  public List<User> getAll() {
    final String _sql = "SELECT * FROM user";
    final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
    __db.assertNotSuspendingTransaction();
    final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
    try {
      final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
      final int _cursorIndexOfFirstName = CursorUtil.getColumnIndexOrThrow(_cursor, "first_name");
      final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "last_name");
      final List<User> _result = new ArrayList<User>(_cursor.getCount());
      while(_cursor.moveToNext()) {
        final User _item;
        final int _tmpUid;
        _tmpUid = _cursor.getInt(_cursorIndexOfUid);
        final String _tmpFirstName;
        if (_cursor.isNull(_cursorIndexOfFirstName)) {
          _tmpFirstName = null;
        } else {
          _tmpFirstName = _cursor.getString(_cursorIndexOfFirstName);
        }
        final String _tmpLastName;
        if (_cursor.isNull(_cursorIndexOfLastName)) {
          _tmpLastName = null;
        } else {
          _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
        }
        _item = new User(_tmpUid,_tmpFirstName,_tmpLastName);
        _result.add(_item);
      }
      return _result;
    } finally {
      _cursor.close();
      _statement.release();
    }
  }

基于@Query注解的sql语句创建RoomSQLiteQuery,然后创建cursor进行后续操作

数据库升级

升级

当数据库的表结构发生变化时,我们需要通过数据库迁移(Migrations)升级表结构,避免数据丢失。

例如,我们想要为User表增加age字段

| uid | first_name | last_name || uid | first_name | last_name | age |

数据迁移需要使用Migration类:

val MIGRATION_1_2 = object : Migration(1, 2) {
    override fun migrate(database: SupportSQLiteDatabase) {
        database.execSQL("ALTER TABLE user ADD COLUMN age INTEGER")
    }
}		

Migration通过startVersionendVersion表明当前是哪个版本间的迁移,然后在运行时,按照版本顺序调用各Migration,最终迁移到最新的Version

创建Database时设置Migration

Room.databaseBuilder(
    applicationContext, 
    UserDatabase::class.java, 
    "user"
).addMigrations(MIGRATION_1_2)
.build()

失败的升级:

迁移中如果找不到对应版的Migration,会抛出IllegalStateException

java.lang.IllegalStateException: A migration from 1 to 2 is necessary. Please provide a Migration in the builder or call fallbackToDestructiveMigration in the builder in which case Room will re-create all of the tables.

避免升级失败导致crash

Room.databaseBuilder(
    applicationContext, 
    UserDatabase::class.java, 
    "users-db"
).fallbackToDestructiveMigration()
.build()

  • fallbackToDestructiveMigration:迁移失败时,重建数据库表
  • fallbackToDestructiveMigrationFrom:迁移失败时,基于某版本重建数据库表
  • fallbackToDestructiveMigrationOnDowngrade:迁移失败,数据库表降级到上一个正常版本

Dao 配合第三方库使用

作为Jetpack生态的成员,Room可以很好地兼容Jetpack的其他组件, 例如LiveData、RxJava、 协程。

LiveData

DAO可以定义LiveData类型的结果,Room内部兼容了LiveData的响应式逻辑。通常的Query需要命令式的获取结果,LiveData可以让结果的更新可被观察(Observable Queries):

@Dao
interface UserDao {
    @Query("SELECT * FROM users")
    fun getAllLiveData(): LiveData<List<User>>
}

当DB的数据发生变化时,Room会更新LiveData

@Override
public LiveData<List<User>> getAllLiveData() {
  final String _sql = "SELECT * FROM users";
  final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
  return __db.getInvalidationTracker().createLiveData(new String[]{"users"}, false, new Callable<List<User>>() {
    @Override
    public List<User> call() throws Exception {
      final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
      try {
        final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
        final int _cursorIndexOfFirstName = CursorUtil.getColumnIndexOrThrow(_cursor, "first_name");
        final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "last_name");
        final List<User> _result = new ArrayList<User>(_cursor.getCount());
        while(_cursor.moveToNext()) {
          final User _item;
          final int _tmpUid;
          _tmpUid = _cursor.getInt(_cursorIndexOfUid);
          final String _tmpFirstName;
          _tmpFirstName = _cursor.getString(_cursorIndexOfFirstName);
          final String _tmpLastName;
          _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
          _item = new User(_tmpUid,_tmpFirstName,_tmpLastName);
          _result.add(_item);
        }
        return _result;
      } finally {
        _cursor.close();
      }
    }

    @Override
    protected void finalize() {
      _statement.release();
    }
  });
}

__db.getInvalidationTracker().createLiveData() 接受3个参数

  • tableNames:被观察的表
  • inTransaction:查询是否基于事务
  • computeFunction:表记录变化时的回调

computeFunctioncall中执行真正的sql查询。当Observer首次订阅LiveData时,或者表数据发生变化时,便会执行到这里。

Coroutine

为UserDao中的CURD方法添加suspend

@Dao
interface UserDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertUsers(vararg users: User)    
    @Update
    suspend fun updateUsers(vararg users: User)    
    @Delete
    suspend fun deleteUsers(vararg users: User)    
    @Query("SELECT * FROM users")
    suspend fun loadAllUsers(): Array<User>
}

@Override
public Object insertUsers(final User[] users, final Continuation<? super Unit> p1) {
  return CoroutinesRoom.execute(__db, true, new Callable<Unit>() {
    @Override
    public Unit call() throws Exception {
      __db.beginTransaction();
      try {
        __insertionAdapterOfUser.insert(users);
        __db.setTransactionSuccessful();
        return Unit.INSTANCE;
      } finally {
        __db.endTransaction();
      }
    }
  }, p1);
}

CoroutinesRoom.execute 中进行真正的sql语句,并通过Continuation将callback变为Coroutine的同步调用。对比一下普通版本的insertUsers

@Override
public void insertUsers(final User... users) {
  __db.assertNotSuspendingTransaction();
  __db.beginTransaction();
  try {
    __insertionAdapterOfUser.insert(users);
    __db.setTransactionSuccessful();
  } finally {
    __db.endTransaction();
  }
}

添加了suspend后,生成代码中会使用CoroutinesRoom.execute封装协程。