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<User>) // 參數爲集合可以返回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<User> // 查詢所有數據
@Query("SELECT * FROM user WHERE name = :name")
public fun selectUser(name:String): Array<User> // 條件查詢
- 返回列的子集:創建子類在每個屬性中使用@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<UserTuple> 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<String>): List<UserTuple>
val userList = userDao.loadUserInCity(arrayOf("常山")) // 查詢常山,只會出現趙雲不會出現張翼德
- Observable查詢:使用LiveData作爲查詢方法的返回值,註冊觀察者後,數據表更改時自動更新UI
@Query("SELECT name ,street FROM user WHERE city IN (:cityArray"))
fun loadUserInCityLive(cityArray: Array<String>): LiveData<List<UserTuple>>
private lateinit var liveData: LiveData<Array<UserTuple>> // 定義一個LiveData
get() {
return userDao.loadUserInCityLive(arrayOf("常山"))
}
val observer = Observer<Array<UserTuple>> { // 定義一個觀察者
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<User>
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<T> 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, C> T getGeneratedImplementation(Class<C> 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<T> aClass = (Class<T>) 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<Migration> 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<User>(__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組件的同學有所幫助!