最近簡單看了下google推出的框架Jetpack,感受此框架的內容能夠對平時的開發有很大的幫助,也能夠解決不少開發中的問題,對代碼的邏輯和UI界面實現深層解耦,打造數據驅動型UI界面。java
Android Architecture組件是Android Jetpack的一部分,它們是一組庫,旨在幫助開發者設計健壯、可測試和可維護的應用程序,包含一下組件:git
上述時Android Architecture所提供的架構組件,本文主要從使用和源碼的角度分析Room組件。github
Room是Google提供的一個ORM庫。Room提供了三個主要的組件:sql
數據庫的建立數據庫
@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;
}複製代碼
定義實體數據:表示數據庫中的表bash
@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(indices = [Index("nameUser"), Index(value = ["name"])]) // 建立索引
@Entity(indices = [Index("nameUser"), Index(value = ["name"] ,unique = true)]) //惟一索引複製代碼
@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
}複製代碼
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
public interface UserDao {
@Insert // 添加數據註解
void insertAll(User... users);
@Delete // 刪除數據註解
void delete(User user);
}複製代碼
@Insert
public fun inertUser(user: User) // 單個參數能夠返回 long
@Insert
public fun insertUserList(array: Array<User>) // 參數爲集合能夠返回long[]複製代碼
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複製代碼
@Update
public fun update(user: User) // 可讓此方法返回一個int值,表示數據庫中更新的行數
val user = User()
user.id = 1
user.name = "張翼德"
address.city = "涿郡"
.....
userDao.update(user)複製代碼
@Delete
public fun delete(user: User) //能夠返回一個int值,表示從數據庫中刪除的行數
val user = User()
user.id = 1 // 要刪除的主鍵 id
userDao.delete(user)複製代碼
@Query("SELECT * FROM user")
public fun selectAll(): Array<User> // 查詢全部數據
@Query("SELECT * FROM user WHERE name = :name")
public fun selectUser(name:String): Array<User> // 條件查詢複製代碼
public class UserTuple{ // 一、根據要查詢的字段建立POJO對象
@ColumnInfo(name = "name")
public var name: String? = null
@ColumnInfo(name = "city")
public var city: String? = null
}
@Query("SELECT name ,city FROM user") // 二、查詢的結果會映射到建立的對象中
public List<UserTuple> loadFullName();
val userList = userDao.loadFullName()
for (userTuple in userList) {
stringBuilder.append(userTuple.name)
.append(" ")
.append(userTuple.city)
.append("\n")
}複製代碼
@Query("SELECT name ,street FROM user WHERE city IN (:cityArray)")
fun loadUserInCity(cityArray: Array<String>): List<UserTuple>
val userList = userDao.loadUserInCity(arrayOf("常山")) // 查詢常山,只會出現趙雲不會出現張翼德複製代碼
@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會自動更新;app
@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()
})複製代碼
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提供了在原始類型和目標類型之間進行轉換的功能,但不容許實體之間的對象引用,對於其餘類型之間的使用須要自定義轉換器ide
使用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是如何實現這些過程的,從源碼角度分析數據庫。
數據庫的建立和升級
Room數據庫實例的建立由Room.databaseBuilder(context.applicationContext,RoomTestData::class.java, "Sample.db").build()開始的,從代碼中看出時使用Builder模式建立DataBase,因此咱們先看看RoomDatabase.Builde類
@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;
}複製代碼
private static final String DB_IMPL_SUFFIX = "_Impl"
。。。。。。
T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX); // 建立DataBase實現類的實例
db.init(configuration); // 初始化數據庫複製代碼
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的註解,自動生成這兩個註解標記的實現類,系統建立類以下圖:
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 的實現類爲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是如何訪問數據庫的。
數據庫的訪問
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的實例重寫了兩個方法:
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組件的同窗有所幫助!