Jetpack是Android官方提供的一套開發組件的結合,它出現的目的是爲了提升咱們的開發效率,消除模板代碼,構建高質量的強大應用。下面是準備分享的計劃:java
Room是Jetpack組件中一個ORM庫,它對Android原生的Sqlite Api作了一層抽象,從而簡化開發者對數據庫的操做。相似於市面上流程的ORM庫GreenDao, Realm等。 Room不只是Android在Jetpack中推薦使用的組件,更重要的是它在性能方面也有必定的優點。android
本文關於Room會介紹主要是3個方面:數據庫
def room_version = "1.1.1"
implementation "android.arch.persistence.room:runtime:$room_version"
annotationProcessor "android.arch.persistence.room:compiler:$room_version" // For Kotlin use kapt instead of annotationProcessor
// optional - RxJava support for Room
implementation "android.arch.persistence.room:rxjava2:$room_version"
複製代碼
在使用數據的時候,須要主要涉及到Room三個部分:json
下面一一介紹bash
@Database(entities = {Video.class, User.class}, version = 1)
//@TypeConverters({Converters.class})
public abstract class AppDatabase extends RoomDatabase {
private static AppDatabase INSTANCE;
private static final Object sLock = new Object();
public abstract UserDao userDao();
public abstract VideoDao videoDao();
public static AppDatabase getInstance(Context context) {
synchronized (sLock) {
if (INSTANCE == null) {
INSTANCE =
Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "video.db")
.allowMainThreadQueries() //room默認數據庫的查詢是不能在主線程中執行的,除非這樣設置
.build();
}
return INSTANCE;
}
}
}
複製代碼
自定義一個抽象類,繼承RoomDatabase。這裏有幾個問題須要注意一下:ide
entity是表對應的實體,須要在該類上面添加@Entity註解post
@Entity()
public class User {
@PrimaryKey
public int uid;
@ColumnInfo(name = "name")
public String name;
@ColumnInfo(name = "video_id")
public int videoId;
@Ignore
public List<Video> videos;
}
複製代碼
這裏介紹幾個Entity用到的註解:性能
標明是表的映射類,能夠自定義表名稱gradle
@Entity(tableName = "Video")
複製代碼
定義主鍵ui
@PrimaryKey
public int uid;
複製代碼
自定義數據庫中的字段名稱
@ColumnInfo(name = "name")
public String name;
複製代碼
定義外鍵約束
假如咱們類中的某個字段不想在表中建立可使用這個註解忽略。
@Entity(tableName = "Video", foreignKeys = @ForeignKey(entity = User.class,
parentColumns = "uid", childColumns = "uid"))//能夠指定表名,默認是類名
public class Video {
@PrimaryKey
public int vid;
@ColumnInfo(name = "name")
public String name;
@ColumnInfo(name = "last_name")
public String length;
public int uid;
@Ignore
public String ignore;
}
複製代碼
Dao中提供了對數據庫操做的方法。
@Dao
public interface UserDao {
@Query("SELECT * FROM User")
List<User> getAll();
@Query("SELECT * FROM User WHERE uid IN (:userIds)")
List<User> loadAllByIds(int[] userIds);
@Query("SELECT * FROM User WHERE name LIKE :name ")
User findByName(String name);
@Insert
void insertAll(User... users);
@Delete
void delete(User user);
@Transaction //事務查詢
@Query("SELECT uid, name from User")
List<UserAllVideos> loadUsers();
}
複製代碼
定義一個接口文件,添加@Dao註解,固然了它的實現也是由Room幫助咱們完成的。各個方法根據業務需求去實現增刪改查,這裏也比較簡單。
User user = new User();
user.uid = 1;
user.name = "love";
AppDatabase.getInstance(this).userDao().insertAll(user);
複製代碼
在數據庫中插入一條User數據
Video video = new Video();
video.vid = 1;
video.name = "love";
video.length = "123";
video.ignore = "ignore";
video.uid = 1;
AppDatabase.getInstance(this).videoDao().insertAll(video);
複製代碼
插入一條Video數據。
數據都插入了,如今咱們獲取一下數據。正常的單個表的數據應該是沒什麼問題的,就不演示了。這裏只介紹一種狀況就是關聯查詢,前面的內容咱們經過外鍵的形式關聯了User和Video。那咱們怎麼作才能在查詢User的時候把關聯的Video也同時查詢出來呢。Room的作法以下:
建立一個UserAllVideos類,注意他不是Entity類,沒有@Entity註解
public class UserAllVideos {
@Embedded
public User user;
@Relation(parentColumn = "uid", entityColumn = "uid")
public List<Video> videos;
}
複製代碼
這裏涉及到兩個註解解釋一下:
他的做用是將User中的字段都引入
他的做用相似於ForeignKey,就是關聯了User和Video,用於咱們的聯合查詢
以後在UserDao中添加一個查詢的方法loadUsers,這樣就能把User中的信息以及其關聯的Video的信息所有的查詢出來。
List<UserAllVideos> userAllVideos = AppDatabase.getInstance(this).userDao().loadUsers();
Log.d(TAG, userAllVideos.get(0).videos.get(0).name);
複製代碼
@TypeConverter的做用是轉換,好比Room中不容許對象的引用出現,可是咱們可使用TypeConverter去轉換,好比上面的User Entity中若是要添加List
public class Converters {
@TypeConverter
public static List<Video> revert(String str) {
// 使用Gson方法把json格式的string轉成List
try {
return new Gson().fromJson(str, new TypeToken<List<Video>>(){}.getType());
} catch (Exception e) {
e.printStackTrace();
}finally {
return null;
}
}
@TypeConverter
public static String converter(List<Video> videos) {
// 使用Gson方法把List轉成json格式的string,便於咱們用的解析
return new Gson().toJson(videos);
}
}
複製代碼
在Database中配置TypeConverters
@Database(entities = {Video.class, User.class}, version = 1)
@TypeConverters({Converters.class})
public abstract class AppDatabase extends RoomDatabase {
複製代碼
建立一個Migration,須要一個起始的版本和升級後的版本。migrage方法中是數據庫升級的具體操做,而後在Database中配置Migration.若是涉及到多個版本的升級,也能夠配置多個Migration
static final Migration MIGRATION_1_2 = new Migration(1, 2) { //由1升級到版本2
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE Topic (id INTEGER , name TEXT )");
}
};
Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "video.db")
.allowMainThreadQueries() //room默認數據庫的查詢是不能在主線程中執行的,除非這樣設置
.addMigrations(MIGRATION_1_2)
.build();
複製代碼
Room支持RxJava2,返回的結果能夠封裝在Flowable中.
上面就是Room的基本使用,這個文檔只是個入門,涉及到更詳細的使用請再查詢別的文檔。下面簡單的介紹一下Room的源碼。
gradle中依賴room,只能看到部分的源碼,若是看完整的Room源碼請點擊這裏。
Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "video.db")
.allowMainThreadQueries() //room默認數據庫的查詢是不能在主線程中執行的,除非這樣設置
.addMigrations(MIGRATION_1_2)
.build();
複製代碼
這裏一看應該就明白,Room採用了建造者模式,添加不少的配置,咱們直接看build方法
@NonNull
public T build() {
//noinspection ConstantConditions
if (mContext == null) {
throw new IllegalArgumentException("Cannot provide null context for the database.");
}
//noinspection ConstantConditions
if (mDatabaseClass == null) {
throw new IllegalArgumentException("Must provide an abstract class that"
+ " extends RoomDatabase");
}
if (mMigrationStartAndEndVersions != null && mMigrationsNotRequiredFrom != null) {
for (Integer version : mMigrationStartAndEndVersions) {
if (mMigrationsNotRequiredFrom.contains(version)) {
throw new IllegalArgumentException(
"Inconsistency detected. A Migration was supplied to "
+ "addMigration(Migration... migrations) that has a start "
+ "or end version equal to a start version supplied to "
+ "fallbackToDestructiveMigrationFrom(int... "
+ "startVersions). Start version: "
+ version);
}
}
}
if (mFactory == null) {
mFactory = new FrameworkSQLiteOpenHelperFactory();
}
DatabaseConfiguration configuration =
new DatabaseConfiguration(mContext, mName, mFactory, mMigrationContainer,
mCallbacks, mAllowMainThreadQueries,
mJournalMode.resolve(mContext),
mRequireMigration, mMigrationsNotRequiredFrom);
T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX);
db.init(configuration);
return db;
}
複製代碼
這個方法的主要做用是建立數據庫實例,而後初始化配置。在前面的使用部分,咱們注意到AppDatabase是個抽象類,那他的真正實現其實在編譯期已經生成了,位置在
build/generared/source/apt/debug/com.xray.sample.room/AppDatabase_Impl
複製代碼
他的實例化則是經過Room.getGeneratedImplementation方法完成,其實採用的是反射。
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) {
throw new RuntimeException("cannot find implementation for "
+ klass.getCanonicalName() + ". " + implName + " does not exist");
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access the constructor"
+ klass.getCanonicalName());
} catch (InstantiationException e) {
throw new RuntimeException("Failed to create an instance of "
+ klass.getCanonicalName());
}
}
複製代碼
而真正數據庫的建立是在init方法中
@CallSuper
public void init(@NonNull DatabaseConfiguration configuration) {
mOpenHelper = createOpenHelper(configuration);
boolean wal = false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
wal = configuration.journalMode == JournalMode.WRITE_AHEAD_LOGGING;
mOpenHelper.setWriteAheadLoggingEnabled(wal);
}
mCallbacks = configuration.callbacks;
mAllowMainThreadQueries = configuration.allowMainThreadQueries;
mWriteAheadLoggingEnabled = wal;
}
複製代碼
createOpenHelper這個方法,他的實如今AppDatabase_Impl中,代碼不少,主要就是數據庫的建立,表的建立,升級處理等。
仍是在AppDatabase_Impl這個生成類中,咱們找到了兩個Dao實例化的地方,這裏發現Dao的實現類也是在編譯的時候生成的,UserDao_Impl和VideoDao_Impl
@Override
public UserDao userDao() {
if (_userDao != null) {
return _userDao;
} else {
synchronized(this) {
if(_userDao == null) {
_userDao = new UserDao_Impl(this);
}
return _userDao;
}
}
}
@Override
public VideoDao videoDao() {
if (_videoDao != null) {
return _videoDao;
} else {
synchronized(this) {
if(_videoDao == null) {
_videoDao = new VideoDao_Impl(this);
}
return _videoDao;
}
}
}
複製代碼
關於Dao的實現類你們本身去看吧,都是編譯生成的模板代碼。
Room模板代碼的生成是經過註解處理器實現的,具體的源碼在這裏,思路雖然簡單,可是要理清裏面的細節仍是比較麻煩的。