Android Jetpack架构组件之 Room(使用、源码篇)

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

1、前言
最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面。

Android Architecture组件是Android Jetpack的一部分,它们是一组库,旨在帮助开发者设计健壮、可测试和可维护的应用程序,包含一下组件:

带你领略Android Jetpack组件的魅力
Android Jetpack 架构组件之 Lifecycle(使用篇)
Android Jetpack 架构组件之 Lifecycle(源码篇)
Android Jetpack 架构组件之 ViewModel (源码篇)
Android Jetpack 架构组件之 LiveData(使用、源码篇)
Android Jetpack架构组件之 Paging(使用、源码篇)
Android Jetpack 架构组件之 Room(使用、源码篇)
Android Jetpack 架构组件之Navigation
Android Jetpack架构组件之WorkManger
实战:从0搭建Jetpack版的WanAndroid客户端
上述时Android Architecture所提供的架构组件,本文主要从使用和源码的角度分析Room组件。

2、Room 简介
Room是Google提供的一个ORM库。Room提供了三个主要的组件:

@Database:@Database用来注解类,并且注解的类必须是继承自RoomDatabase的抽象类。该类主要作用是创建数据库和创建Daos(data access objects,数据访问对象)。
@Entity:@Entity用来注解实体类,@Database通过entities属性引用被@Entity注解的类,并利用该类的所有字段作为表的列名来创建表。
@Dao:@Dao用来注解一个接口或者抽象方法,该类的作用是提供访问数据库的方法。在使用@Database注解的类中必须定一个不带参数的方法,这个方法返回使用@Dao注解的类
3、Room数据库使用
数据库的创建

包含数据库持有者,并作为应用程序持久关系数据的基础连接的主要访问点,使用@Database注解,注解类应满足以下条件:
数据库必须是一个抽象类 RoomDatabase的扩展类
在注释中包括与数据库关联的实体列表
必须包含一个具有0个参数且返回带@Dao注释的类的抽象方法
通过调用 Room.databaseBuilder()或 获取实例Room.inMemoryDatabaseBuilder()创建数据库实例
使用单例实例化数据库对象
@Database(entities = {User.class}, version = 1)  // 注释
public abstract class AppDatabase extends RoomDatabase {
    public abstract UserDao userDao();  // 抽象方法
}
以单例形式对外提供RoomDataBase实例
public static UserDataBase getInstance(Context context) {
    if (userDataBase == null) {
        synchronized (UserDataBase.class) {
            if (userDataBase == null) {
                userDataBase = Room.databaseBuilder(context.getApplicationContext()
                        , UserDataBase.class, "user_data").build();
            }
        }
    }
    return userDataBase;
}
定义实体数据:表示数据库中的表

@Entity
使用@Entity注解实体类,Room会为实体中定义的每个字段创建一列,如果想避免使用@Ignore注解
Room默认使用类名作为数据库表名,要修改表名使用 @Entity 的 tableName属性
主键
 @PrimaryKey :至少定义一个字段作为主键
如果自增长ID 使用设置@PrimaryKey的 autoGenerate 属性
使用组合主键 使用@Entity 的@primaryKeys属性
Room 默认使用字段名成作为列名,要修改使用 @ColumnInfo(name = "***") 
@Entity(tableName = "userDataBase")
class User {
    @PrimaryKey(autoGenerate = true)    // 单个主键设置为自增长
    public var id = 0
    @ColumnInfo(name = "nameUser")  // 定义列名
    public var name: String? = null
}
 
@Entity(primaryKeys = ["id", "name"])  // 组合组件
添加索引@Entity
使用 @Entity 的indices 属性,列出要包含在索引或复合索引中的列的名称
@Entity(indices = [Index("nameUser"), Index(value = ["name"])])  // 创建索引
@Entity(indices = [Index("nameUser"), Index(value = ["name"] ,unique = true)]) //唯一索引
外键约束@ForeignKey
使用@ForeignKey 注释定义其与实体的 关系;ForeignKey中 entity 为要关联的父实体类;parentColumns 为关联父实体类的列名;childColumns此实体类中的列名
@Entity(foreignKeys = [ForeignKey(entity = User::class,
        parentColumns = ["id"],
        childColumns = ["user_id"])])
class Book {
    @PrimaryKey
    var bookId: Int = 0
    var title: String? = null
    @ColumnInfo(name = "user_id")
    var userId: Int = 0
}
嵌套对象@Embedded 
使用 @Embedded 注释来表示要分解到表中子字段的对象(此时数据库的列为两个类中所有的字段)
class Address {
    public var street: String? = null
    public var state: String? = null
    public var city: String? = null
    @ColumnInfo(name = "post_code")
    public var postCode = 0
}
 
// 在User实体中引入Address
@Embedded
public var address: Address? = null
访问数据库

使用@DAO注解:包含用于访问数据库的方法
@Dao                   
public interface UserDao {
    @Insert      // 添加数据注解
    void insertAll(User... users);
 
    @Delete    // 删除数据注解
    void delete(User user);
}
4、实例实战
insert:使用注解@Insert,Room会自动将所有参数在单个事物中插入数据库
@Insert
public fun inertUser(user: User)   // 单个参数可以返回 long
 
@Insert
public fun insertUserList(array: Array)  // 参数为集合可以返回long[]
数据库添加User
val user = User()
user.name = "赵云 编号 = $number"
val address = Address()
address.street = "成都接头"
address.state = "蜀汉"
address.city = "常山"
address.postCode = 10010
user.address = address
userDao.inertUser(user)   // 添加User
添加数据结果:


upadte:使用 @Update注解
@Update
public fun update(user: User)     // 可以让此方法返回一个int值,表示数据库中更新的行数  
 
 
val user = User()
user.id = 1
user.name = "张翼德"
address.city = "涿郡"
.....
userDao.update(user)
点击 Update 后再查询结果:此时的赵云已经改为张翼徳了


delete:使用@Delete注解
@Delete 
public fun delete(user: User)    //可以返回一个int值,表示从数据库中删除的行数
 
 
val user = User()
user.id = 1      // 要删除的主键 id
userDao.delete(user)
点击delete后再次查询数据:编号为1的数据已被删除


查询信息 :@Query注解对数据库执行读/写操作
@Query("SELECT * FROM user")
public fun selectAll(): Array    // 查询所有数据
 
@Query("SELECT * FROM user WHERE name = :name")
public fun selectUser(name:String): Array    // 条件查询
返回列的子集:创建子类在每个属性中使用@ColumnInfo(name = "name")标记对应数据库中的列名
public class UserTuple{                                  // 1、根据要查询的字段创建POJO对象           
    @ColumnInfo(name = "name")
    public var name: String? = null
    @ColumnInfo(name = "city")
    public var city: String? = null
}
 
@Query("SELECT name ,city FROM user")  // 2、查询的结果会映射到创建的对象中
    public List loadFullName();
 
 
val userList = userDao.loadFullName()
for (userTuple in userList) {
    stringBuilder.append(userTuple.name)
            .append("   ")
            .append(userTuple.city)
            .append("\n")
}
输出的结果:只有name和city两列


范围条件查询 :查询城市中所有用户
@Query("SELECT name ,street FROM user WHERE city IN (:cityArray)")
fun loadUserInCity(cityArray: Array): List
 
 
val userList = userDao.loadUserInCity(arrayOf("常山"))  // 查询常山,只会出现赵云不会出现张翼德


Observable查询:使用LiveData作为查询方法的返回值,注册观察者后,数据表更改时自动更新UI
@Query("SELECT name ,street FROM user WHERE city IN (:cityArray"))
fun loadUserInCityLive(cityArray: Array): LiveData>
 
 
 
private lateinit var liveData: LiveData>   // 定义一个LiveData
get() {
return userDao.loadUserInCityLive(arrayOf("常山"))
}
 
val observer = Observer> {      // 定义一个观察者
    val stringBuilder = StringBuilder()
    for (index in it!!.indices) {
        val userTuple = it[index]
        stringBuilder.append(userTuple.name)
                .append("   ")
                .append(userTuple.name)
                .append("   \n")
    }
   tv_main_show.text = stringBuilder.toString()
}
liveData.observe(this, observer)     // 注册观察者
运行结果:此时当添加数据时,UI会自动更新;

RxJava 查询 :返回Observable实例可以使用RxJava订阅观察者
@Query("SELECT * FROM user WHERE id = :id LIMIT 1")
fun loadUserRxJava(id:Int) : Flowable
 
 
userDao.loadUserRxJava(4)
        .subscribe(Consumer {
            val stringBuilder = StringBuilder()
            stringBuilder.append(it.id)
                    .append("   ")
                    .append(it.name)
                    .append("   \n")
            tv_main_show.text = stringBuilder.toString()
        })


 Cursor查询:返回Cursor对象
fun loadUserCursor(id:Int) : Cursor
多表查询:根据表的外键多表查询
@Query("SELECT user.name AS userName, pet.name AS petName "
          + "FROM user, pet "
          + "WHERE user.id = pet.user_id")
5、更新数据库
编写 Migration 的实例。每个 Migration 类指定一个startVersion和endVersion
Room运行每个 Migration 类的 migrate() 方法,使用正确的顺序将数据库迁移到更高版本
static final Migration MIGRATION_1_2 = new Migration(1, 2) { //由1升级到版本2
    @Override
    public void migrate(SupportSQLiteDatabase database) {
        database.execSQL("CREATE TABLE book (id  INTEGER , name TEXT )")
    }
};
 
static final Migration MIGRATION_2_3 = new Migration(2, 3) { //由2升级到版本3
    @Override
    public void migrate(SupportSQLiteDatabase database) {
         database.execSQL("ALTER TABLE user ADD COLUMN strength INTEGER NOT NUll DEFAULT 0")  //添加strength列
    }
};
 
 
Room.databaseBuilder(getApplicationContext(), MyDb.class, "database-name")
        .addMigrations(MIGRATION_1_2, MIGRATION_2_3).build();
升级完数据库后再次查询,结果显示数据库增加了strength列名:

6、引用复杂数据
Room提供了在原始类型和目标类型之间进行转换的功能,但不允许实体之间的对象引用,对于其他类型之间的使用需要自定义转换器

使用类型转换器
使用TypeConverter,它将自定义类转换为Room可以保留的已知类型,如:想保存Date类型,而Room无法持久化实例Date却可以实例long,因此提供和long的相互转换

public class Converters {
 
    @TypeConverter
    public static Date fromTimestamp(Long value) {
        return value == null ? null : new Date(value);
    }
 
    @TypeConverter
    public static Long dateToTimestamp(Date date) {
        return date == null ? null : date.getTime();
    }
}
在抽象数据库类中添加转换注解
@TypeConverters({Converters.class})
使用 类型转换器
@Query("SELECT * FROM user WHERE birthday BETWEEN :from AND :to")
List findUsersBornBetweenDates(Date from, Date to);
以上就是数据库Room的使用简介了,基本数据库的增删改查以及常见的设置都在其中了,下面我们来看看Room是如何实现这些过程的,从源码角度分析数据库。

7、源码分析
数据库的创建和升级

Room数据库实例的创建由Room.databaseBuilder(context.applicationContext,RoomTestData::class.java, "Sample.db").build()开始的,从代码中看出时使用Builder模式创建DataBase,所以我们先看看RoomDatabase.Builde类

RoomDatabase.Builder:除了包含Room的实现类、数据库名称的常规设置外,也包含了数据库的升级信息
@NonNull
public Builder addMigrations(@NonNull  Migration... migrations) {  // 添加数据库版本升级信息
    if (mMigrationStartAndEndVersions == null) {
        mMigrationStartAndEndVersions = new HashSet<>();
    }
    for (Migration migration: migrations) {
        mMigrationStartAndEndVersions.add(migration.startVersion);
        mMigrationStartAndEndVersions.add(migration.endVersion);
    }
 
    mMigrationContainer.addMigrations(migrations);
    return this;
}
build():创建并初始化数据库
private static final String DB_IMPL_SUFFIX = "_Impl"
。。。。。。
T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX); // 创建DataBase实现类的实例 
db.init(configuration);  // 初始化数据库
getGeneratedImplementation():反射创建DataBase的实现类
static T getGeneratedImplementation(Class klass, String suffix) {
    final String fullPackage = klass.getPackage().getName();  
    String name = klass.getCanonicalName();
    final String postPackageName = fullPackage.isEmpty()
            ? name
            : (name.substring(fullPackage.length() + 1));  // 获取类名
    final String implName = postPackageName.replace('.', '_') + suffix;  // 拼接类名
    //noinspection TryWithIdenticalCatches
    try {
 
        @SuppressWarnings("unchecked")
        final Class aClass = (Class) Class.forName(
                fullPackage.isEmpty() ? implName : fullPackage + "." + implName);   // 获取自动生成的类文件
        return aClass.newInstance();  // 创建并返回实例
    } catch (ClassNotFoundException e) {
        。。。。。。
    } 
}
此处获取到的是系统根据注解自动创建的是实现类RoomDataBase_Impl,Room采用的是注解自动生成代码方式,根据@DataBase和@Dao的注解,自动生成这两个注解标记的实现类,系统创建类如下图:

RoomTestData_Impl:系统自动生成的实现类
public class RoomTestData_Impl extends RoomTestData {
  private volatile UserDao _userDao;
......
  @Override
  public UserDao userDao() {   
    if (_userDao != null) {
      return _userDao;
    } else {
      synchronized(this) {
        if(_userDao == null) {
          _userDao = new UserDao_Impl(this);  // 创建并返回UserDao的实例
        }
        return _userDao;
      }
    }
  }
}
从上面的代码中看出,系统自动创建了RoomTestData的实现类,并重写了抽象方法userDao(),在userDao()中使用单例的方式提供UserDao的实现类UserDao_Impl,UserDao_Impl的形成和RoomTestData_Impl的生成一样,在代码中从DataBase中调用userDao返回的就是UserDao_Impl的实例;

接着分析数据库的创建,在上面的代码中有一句数据库的初始化代码db.init(),在db.init()的方法中会调用RoomDataBase中的抽象方法createOpenHelper(),这里调用的是createOpenHelper()就是RoomTestData_Impl自动实现的方法:

protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration configuration) {
 
  final SupportSQLiteOpenHelper.Callback _openCallback = new RoomOpenHelper(configuration, new RoomOpenHelper.Delegate(3) {
    @Override
    public void createAllTables(SupportSQLiteDatabase _db) {
      _db.execSQL("CREATE TABLE IF NOT EXISTS `user` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `strength` INTEGER NOT NULL, `name` TEXT, `street` TEXT, `state` TEXT, `city` TEXT, `post_code` INTEGER)");   // 创建数据库
      _db.execSQL("CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)");
      _db.execSQL("INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"8ece9a1581b767a0f460940849e9b463\")");
    }
 
    @Override
    public void dropAllTables(SupportSQLiteDatabase _db) {
      _db.execSQL("DROP TABLE IF EXISTS `user`");  // 删除数据库
    }
 
   @Override
protected void validateMigration(SupportSQLiteDatabase _db) {  // 处理数据库的版本升级
 。。。。。。
}
  }, "8ece9a1581b767a0f460940849e9b463", "061261cef54147a569851cbbb906c3be");
}
 。。。。。。
  return _helper;
}
上面的代码中执行一下操作:

创建SupportSQLiteOpenHelper.Callback 的实例并重写方法
在onCreate()中Sql语句创建user表和room_master_table表
在dropAllTables()中创建删除数据库的SQL语句
在validateMigration()中完成数据库的升级
上面SupportSQLiteOpenHelper.Callback 的实现类为RoomOpenHelper,下面一起看看RoomOpenHelper源码:

@Override
public void onCreate(SupportSQLiteDatabase db) {
    updateIdentity(db);
    mDelegate.createAllTables(db);  // mDelegate为上面创建的RoomOpenHelper.Delegate实例
    mDelegate.onCreate(db);
}
 
@Override
public void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
if (mConfiguration != null) {
    List migrations = mConfiguration.migrationContainer.findMigrationPath(
            oldVersion, newVersion);
    if (migrations != null) {
        for (Migration migration : migrations) {
            migration.migrate(db);
        }
        mDelegate.validateMigration(db);   // 调用validateMigration方法处理数据库的更新
        updateIdentity(db);
        migrated = true;
    }
}
}
 
@Override
public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
    onUpgrade(db, oldVersion, newVersion);
}
从上面代码中可以看出,在onCreate()方法中调用了mDelegate.createAllTables(db),这里的mDelegate就是上面创建RoomOpenHelper方法中第二个参数RoomOpenHelper.Delegate,所以这里就是在onCreate()中创建了数据库,在onUPgrade()中调用 mDelegate.validateMigration(db)完成数据库的升级,到这里数据库的创建和升级已经介绍完毕了,下面就一起看看Room是如何访问数据库的。

数据库的访问

@Dao数据库的实现类: UserDao_Impl
private final RoomDatabase __db;   // 传入的数据库
 
private final EntityInsertionAdapter __insertionAdapterOfUser;  // 处理insert方法
 
private final EntityDeletionOrUpdateAdapter __deletionAdapterOfUser;   // 处理delete方法
 
private final EntityDeletionOrUpdateAdapter __updateAdapterOfUser;  // 处理update方法
在UserDao_Impl的类中除了数据库RoomDataBase实例外,还有三个成员变量分别为:__insertionAdapterOfUser、__deletionAdapterOfUser、__updateAdapterOfUser,从名字上可以看出来他们三个分别对应数据库增、删、改的三个操作,我们以insert操作为例,查看insert方法:

@Override
public void inertUser(User user) {
  __db.beginTransaction();
  try {
    __insertionAdapterOfUser.insert(user);
    __db.setTransactionSuccessful();
  } finally {
    __db.endTransaction();
  }
}
insert()方法的实现是在__insertionAdapterOfUser中执行的,查看__insertionAdapterOfUser的实现

this.__insertionAdapterOfUser = new EntityInsertionAdapter(__db) {
  @Override
  public String createQuery() {  // 创建SupportSQLiteStatement时传入的Sql语句
    return "INSERT OR ABORT INTO `user`(`id`,`strength`,`name`,`street`,`state`,`city`,`post_code`) VALUES (nullif(?, 0),?,?,?,?,?,?)";
  }
 
  @Override
  public void bind(SupportSQLiteStatement stmt, User value) {
    stmt.bindLong(1, value.getId());
    stmt.bindLong(2, value.getStrength());
    if (value.getName() == null) {  // 判断此列是否为null,部位Null则设置数据
      stmt.bindNull(3);
    } else {
      stmt.bindString(3, value.getName());
    }
    final Address _tmpAddress = value.getAddress();
    if(_tmpAddress != null) {
      if (_tmpAddress.getStreet() == null) {
        stmt.bindNull(4);
      } else {
        stmt.bindString(4, _tmpAddress.getStreet());
      }
      if (_tmpAddress.getState() == null) {
        stmt.bindNull(5);
      } else {
        stmt.bindString(5, _tmpAddress.getState());
      }
      if (_tmpAddress.getCity() == null) {
        stmt.bindNull(6);
      } else {
        stmt.bindString(6, _tmpAddress.getCity());
      }
      stmt.bindLong(7, _tmpAddress.getPostCode());
    } else {
      stmt.bindNull(4);
      stmt.bindNull(5);
      stmt.bindNull(6);
      stmt.bindNull(7);
    }
  }
};
__insertionAdapterOfUser的实例重写了两个方法:

createQuery():创建数据库插入数据的sql语句
bind():绑定数据库中每个列对应的值
__insertionAdapterOfUser.insert()
insert()方法中创建SupportSQLiteStatement的实例,并调用bind()完成数据的绑定,然后执行stmt.executeInsert()插入数据

public final void insert(T entity) {
    final SupportSQLiteStatement stmt = acquire();  // 最终创建的是FrameworkSQLiteStatement的包装的SQLiteStatement实例
    try {
        bind(stmt, entity);  // 绑定要插入的数据
        stmt.executeInsert();   // 提交保存数据,执行
    } finally {
        release(stmt);
    }
}
 
 
@Override
public long executeInsert() {  // 最终执行数据库的插入操作
    return mDelegate.executeInsert();
}
查寻数据库
在UserDao_Impl中自动实现了查询的方法selectUser:

@Override
public User[] selectUser(String name) {
  final String _sql = "SELECT * FROM user WHERE name = ?";
  final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);   //   创建RoomSQLiteQuery 
  int _argIndex = 1;
  if (name == null) {
    _statement.bindNull(_argIndex);
  } else {
    _statement.bindString(_argIndex, name);
  }
  final Cursor _cursor = __db.query(_statement); //   执行查询反会Cursor
  try {
    final int _cursorIndexOfId = _cursor.getColumnIndexOrThrow("id");
    final int _cursorIndexOfStrength = _cursor.getColumnIndexOrThrow("strength");
    final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
    final int _cursorIndexOfStreet = _cursor.getColumnIndexOrThrow("street");
    final int _cursorIndexOfState = _cursor.getColumnIndexOrThrow("state");
    final int _cursorIndexOfCity = _cursor.getColumnIndexOrThrow("city");
    final int _cursorIndexOfPostCode = _cursor.getColumnIndexOrThrow("post_code");
    final User[] _result = new User[_cursor.getCount()];
    int _index = 0;
    while(_cursor.moveToNext()) {
      final User _item;
      final Address _tmpAddress;
      if (! (_cursor.isNull(_cursorIndexOfStreet) && _cursor.isNull(_cursorIndexOfState) && _cursor.isNull(_cursorIndexOfCity) && _cursor.isNull(_cursorIndexOfPostCode))) {
        _tmpAddress = new Address();
        final String _tmpStreet;
        _tmpStreet = _cursor.getString(_cursorIndexOfStreet);
        _tmpAddress.setStreet(_tmpStreet);
        final String _tmpState;
        _tmpState = _cursor.getString(_cursorIndexOfState);
        _tmpAddress.setState(_tmpState);
        final String _tmpCity;
        _tmpCity = _cursor.getString(_cursorIndexOfCity);
        _tmpAddress.setCity(_tmpCity);
        final int _tmpPostCode;
        _tmpPostCode = _cursor.getInt(_cursorIndexOfPostCode);
        _tmpAddress.setPostCode(_tmpPostCode);
      }  else  {
        _tmpAddress = null;
      }
      _item = new User();
      final int _tmpId;
      _tmpId = _cursor.getInt(_cursorIndexOfId);
      _item.setId(_tmpId);
      final int _tmpStrength;
      _tmpStrength = _cursor.getInt(_cursorIndexOfStrength);
      _item.setStrength(_tmpStrength);
      final String _tmpName;
      _tmpName = _cursor.getString(_cursorIndexOfName);
      _item.setName(_tmpName);
      _item.setAddress(_tmpAddress);
      _result[_index] = _item;
      _index ++;
    }
    return _result;
  } finally {
    _cursor.close();
    _statement.release();
  }
}
上面执行的也是数据库的正常操作,先创建了RoomSQLiteQuery的实例,在调用db。query()执行查询,查询返回Cursor实例,最终从Cursor中获取信息转换为对象并返回数据。

到此Room的使用和源码执行流程就到此结束了,本文旨在执行的流程分析,具体的如何使用SQLite数据库操作的读者可以自己点击源码查看,不过使用的SQLite的查询和添加方法和平时使用的不同,读者想分析的话就会找到了,好了,希望本篇文章对想了解和使用Room组件的同学有所帮助!

本文使用Room的Demo,欢迎Star
--------------------- 
作者:Alex@W 
来源:CSDN 
原文:https://blog.csdn.net/Alexwll/article/details/83033460 
版权声明:本文为博主原创文章,转载请附上博文链接!

转载于:https://my.oschina.net/sfshine/blog/3010959

你可能感兴趣的:(Android Jetpack架构组件之 Room(使用、源码篇))