本文共 18326 字,大约阅读时间需要 61 分钟。
最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面。
Android Architecture组件是Android Jetpack的一部分,它们是一组库,旨在帮助开发者设计健壮、可测试和可维护的应用程序,包含一下组件:
本系列文章是各处copy过来的,个人感觉所有的开发者都应该尽早的熟悉Jetpack组件,相信一定会被它的魅力所吸引,最近也在完成一个使用以上所有组件实现的项目,作为对Jetpack组件的项目实践,下面来分析一下每个组件对项目开发的帮助。
Room是Google提供的一个ORM库。Room提供了三个主要的组件:
数据库的创建
@Database(entities = {User.class}, version = 1) // 注释public abstract class AppDatabase extends RoomDatabase { public abstract UserDao userDao(); // 抽象方法}
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(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 = [Index("nameUser"), Index(value = ["name"])]) // 创建索引@Entity(indices = [Index("nameUser"), Index(value = ["name"] ,unique = true)]) //唯一索引
外键约束@ForeignKey
@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
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@Embeddedpublic var address: Address? = null
访问数据库
@Dao public interface UserDao { @Insert // 添加数据注解 void insertAll(User... users); @Delete // 删除数据注解 void delete(User user);}
@Insertpublic fun inertUser(user: User) // 单个参数可以返回 long @Insertpublic fun insertUserList(array: Array) // 参数为集合可以返回long[]
val user = User()user.name = "赵云 编号 = $number"val address = Address()address.street = "成都接头"address.state = "蜀汉"address.city = "常山"address.postCode = 10010user.address = addressuserDao.inertUser(user) // 添加User
添加数据结果:
@Updatepublic fun update(user: User) // 可以让此方法返回一个int值,表示数据库中更新的行数 val user = User()user.id = 1user.name = "张翼德"address.city = "涿郡".....userDao.update(user)
点击 Update 后再查询结果:此时的赵云已经改为张翼徳了:
@Delete public fun delete(user: User) //可以返回一个int值,表示从数据库中删除的行数 val user = User()user.id = 1 // 要删除的主键 iduserDao.delete(user)
点击delete后再次查询数据:编号为1的数据已被删除。
@Query("SELECT * FROM user")public fun selectAll(): Array// 查询所有数据 @Query("SELECT * FROM user WHERE name = :name")public fun selectUser(name:String): Array // 条件查询
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 ListloadFullName(); 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("常山")) // 查询常山,只会出现赵云不会出现张翼德
@Query("SELECT name ,street FROM user WHERE city IN (:cityArray"))fun loadUserInCityLive(cityArray: Array): LiveData
> private lateinit var liveData: LiveData > // 定义一个LiveDataget() {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会自动更新:
@Query("SELECT * FROM user WHERE id = :id LIMIT 1")fun loadUserRxJava(id:Int) : FlowableuserDao.loadUserRxJava(4) .subscribe(Consumer { val stringBuilder = StringBuilder() stringBuilder.append(it.id) .append(" ") .append(it.name) .append(" \n") tv_main_show.text = stringBuilder.toString() })
fun loadUserCursor(id:Int) : Cursor
@Query("SELECT user.name AS userName, pet.name AS petName " + "FROM user, pet " + "WHERE user.id = pet.user_id")
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列名:
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类
@NonNullpublic BuilderaddMigrations(@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;}
private static final String DB_IMPL_SUFFIX = "_Impl"。。。。。。T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX); // 创建DataBase实现类的实例 db.init(configuration); // 初始化数据库
staticT 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的注解,自动生成这两个注解标记的实现类,系统创建类如下图:
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`"); // 删除数据库 } @Overrideprotected void validateMigration(SupportSQLiteDatabase _db) { // 处理数据库的版本升级 。。。。。。} }, "8ece9a1581b767a0f460940849e9b463", "061261cef54147a569851cbbb906c3be");} 。。。。。。 return _helper;}
上面的代码中执行一下操作:
上面SupportSQLiteOpenHelper.Callback 的实现类为RoomOpenHelper,下面一起看看RoomOpenHelper源码:
@Overridepublic void onCreate(SupportSQLiteDatabase db) { updateIdentity(db); mDelegate.createAllTables(db); // mDelegate为上面创建的RoomOpenHelper.Delegate实例 mDelegate.onCreate(db);} @Overridepublic void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {if (mConfiguration != null) { Listmigrations = mConfiguration.migrationContainer.findMigrationPath( oldVersion, newVersion); if (migrations != null) { for (Migration migration : migrations) { migration.migrate(db); } mDelegate.validateMigration(db); // 调用validateMigration方法处理数据库的更新 updateIdentity(db); migrated = true; }}} @Overridepublic 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是如何访问数据库的。
数据库的访问
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方法:
@Overridepublic 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的实例重写了两个方法:
__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); }} @Overridepublic long executeInsert() { // 最终执行数据库的插入操作 return mDelegate.executeInsert();}
在UserDao_Impl中自动实现了查询的方法selectUser:
@Overridepublic 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组件的同学有所帮助!