Android Jetpack架構組件之Room入門及源碼分析

 

——你拼命掙錢的樣子,雖然有些狼狽;但是你自己靠自己的樣子,真的很美!

前言

一、簡介

(1)是什麼

(2)有什麼用

(3)有什麼優點

​二、基本使用

三、進階

四、源碼分析

五、內容推薦

六、項目參考


前言

——這篇主要是梳理一下Jetpack架構組件之一的Room,並結合樓主所學做個總結。面向那些還沒接觸Room的同學們。看完這篇可以快速瞭解它,並輕鬆使用。也想請教前輩們指點文章中的錯誤或不足的地方。本篇只描述Room,不會拓展額外的知識,若想了解更多關於Jetpack組件知識可以看樓主寫的Jetpack專欄。

一、簡介

(1)是什麼

——Room 是google推出的Jetpack架構組件之一,在SQLite上提供了一個抽象層,允許流暢地訪問數據庫,同時利用SQLite的全部功能。

Room包含3個重要組件:

  • Database:包含數據庫容器,並作爲到應用程序的持久關係數據的基礎連接的主要訪問點
  • Entity:表示數據庫中的一個表。
  • DAO:包含用於訪問數據庫的方法

Room 不同組件之間的關係:

(2)有什麼用

——這個庫可以幫助你在運行應用的設備上創建應用數據的緩存。這個緩存是應用的唯一真實來源,允許用戶查看應用內的關鍵信息的一致副本,不管用戶是否有互聯網連接

可以簡單的理解爲Room是對SQLite的一個封裝,使開發者們更容易使用SQLite。

(3)有什麼優點

  • 通過簡單的註釋,room註解處理器會幫開發者生成創建數據庫所需的代碼。
  • 使用簡潔,代碼量少
  • 結構清晰,易於維護

​二、基本使用

(1)添加依賴

    implementation "android.arch.persistence.room:runtime:1.1.0"
    annotationProcessor "android.arch.persistence.room:compiler:1.1.0"

(2)建立一個表

/**
 * 通過@Entity 註解 建立一個表
 */
@Entity
public class Student {
    @PrimaryKey(autoGenerate = true) int id;
    @ColumnInfo String name;
    @ColumnInfo String sex;
    @ColumnInfo int age;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

(3)創建數據庫

/**
 * 創建數據庫
 * 通過entities 指定數據庫中的表
 * version指定當前數據庫版本號
 */
@Database(entities = {Student.class},version = 1)
public abstract class RoomDbManager extends RoomDatabase {
    public abstract StudentDao getStudentDao();
}

(4)創建訪問數據庫的方法

/**
 * 創建訪問數據庫的方法
 */
@Dao
public interface StudentDao {

    @Insert
    void insertOne(Student student);

    @Delete
    void deleteOne(Student student);

    @Update
    void update(Student student);

    @Query("SELECT * FROM Student")
    List<Student> getAll();
}

(5)使用步驟

 private RoomDbManager roomDb;
 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //步驟一:獲取數據庫實例
        if (room_blcs == null) {
            roomDb= Room.databaseBuilder(getApplicationContext(),
                    RoomDbManager.class, "room_blcs").build();
        }
        //步驟二:獲取訪問數據庫的方法實例
        StudentDao studentDao = roomDb.getStudentDao();

        //步驟三:訪問StudentDao 方法執行數據庫操作:增刪改查
        //注:這些方法不能在主線程(UI線程)上執行,需要創建新的線程來執行這些耗時操作。
        
        //增:studentDao.insertOne(student);
        
        //刪:studentDao.deleteOne(student)

        //改:studentDao.update(student)

        //查:List<Student> all = studentDao.getAll()
    }

——通過上面例子可以簡單的使用room,不過不能滿足大部分情況。下面介紹常用方法

三、進階

(1)有關表的操作

1. @Entity

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Entity {
    /**
     * 定義表名  默認使用類名
     */
    String tableName() default "";

    /**
     * 定義索引
     */
    Index[] indices() default {};

    /**
     * 聲明是否繼承父類索引 默認false
     */
    boolean inheritSuperIndices() default false;

    /**
     * 定義主鍵
     */
    String[] primaryKeys() default {};

    /**
     * 定義外鍵
     */
    ForeignKey[] foreignKeys() default {};
}

——通過該註釋定義一張表。每一張表必須有一個主鍵。Entity屬性字段表示  參考上面註釋

@Entity(tableName = "students",
        indices = {@Index(value = {"firstName", "address"})},
        inheritSuperIndices = true,
        primaryKeys = {"id", "lastName"},
        foreignKeys = { @ForeignKey(entity = Playlist.class,
                parentColumns = "id",childColumns = "playlistId")})
public class User {
     public int id;
     public String firstName;
     public String lastName;
     public int playlistId;
}

2. @primaryKeys

——除了通過 @Entity(primaryKeys = {"firstName", "lastName"}) 聲明主鍵外,還可以使用@PrimaryKey註解字段

@Entity
public class Student {
    @PrimaryKey
    int id;
    ...
}

——autoGenerate 可以讓SQLite自動生成唯一的id, 默認爲false

@PrimaryKey(autoGenerate = true)
int id;

3. @ColumnInfo

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface ColumnInfo {
    /**
     * 定義列名 默認爲字段名
     */
    String name() default INHERIT_FIELD_NAME;
    String INHERIT_FIELD_NAME = "[field-name]";
    /**
     * 定義列的類型  默認使用 UNDEFINED
     */
    @SuppressWarnings("unused") @SQLiteTypeAffinity int typeAffinity() default UNDEFINED; 
    /**
     * 列的使用類型
     */
    int UNDEFINED = 1;
    int TEXT = 2;
    int INTEGER = 3;
    int REAL = 4;
    int BLOB = 5;
    @IntDef({UNDEFINED, TEXT, INTEGER, REAL, BLOB})
    @interface SQLiteTypeAffinity {
    }
    /**
     * 定義索引
     */
    boolean index() default false;

    /**
     * 定義列的排列順序 默認使用 UNSPECIFIED
     */
    @Collate int collate() default UNSPECIFIED;
    /**
     * 列的排列順序常量
     */
    int UNSPECIFIED = 1;
    int BINARY = 2;
    int NOCASE = 3;
    int RTRIM = 4;
    @RequiresApi(21)
    int LOCALIZED = 5;
    @RequiresApi(21)
    int UNICODE = 6;
    @IntDef({UNSPECIFIED, BINARY, NOCASE, RTRIM, LOCALIZED, UNICODE})
    @interface Collate {
    }
}

——通過該屬性定義表中的一個列,ColumnInfo屬性字段表示  參考上面註釋

@Entity
public class Student {
    @PrimaryKey(autoGenerate = true)
    int id;
    @ColumnInfo(name = "names",typeAffinity = TEXT,index = true,collate = UNICODE)
    String name;
    ...
}

4. @Ignore

——如果一個實體有您不想持久化的字段,您可以使用@Ignore註釋

@Entity
public class User {
    @PrimaryKey 
    int id; 
    @Ignore 
    String name;
}

(2)對象之間的關係

——Room如何處理對象間  嵌套對象,一對多,多對多 關係簡單介紹

1.Room中使用嵌套對象(將一類加到另一個類中)

使用@Embedded註釋 引入需要嵌套進來的對象。然後,可以像查詢其他各個列一樣查詢嵌套字段

    public class Address {
        public String street;
        public String state;
        public String city;

        @ColumnInfo(name = "post_code") public int postCode;
    }

    @Entity
    public class User {
        @PrimaryKey public int id;

        public String firstName;

        @Embedded public Address address;
    }

如果有嵌套有重複字段可通過@Embedded 攜帶的 prefix 屬性來定義唯一性

注意:嵌套字段還可以包含其他嵌套字段。

2.一對多:如下例子 表示一個用戶可以擁有多本書,使用@ForeignKey定義外鍵約束關係。使用方式如下

    @Entity(foreignKeys = @ForeignKey(entity = User.class,
                                      parentColumns = "id",
                                      childColumns = "user_id"))
    public class Book {
        @PrimaryKey 
        public int bookId;

        public String title;

        @ColumnInfo(name = "user_id") 
        public int userId;
    }
    

3.多對多:舉個例子 一個老師有多個學生,而一個學生也可以擁有多個老師。

    @Entity
    public class Teacher {
        @PrimaryKey public int id;
        public String name;
    }

    @Entity
    public class Student {
        @PrimaryKey public int id;
        public String name;
    }

——然後定義一箇中間類包含對teacher和Student的外鍵引用實體

    @Entity(primaryKeys = { "teacherId", "studentId" },
            foreignKeys = {
                    @ForeignKey(entity = Teacher.class,
                                parentColumns = "id",
                                childColumns = "teacherId"),
                    @ForeignKey(entity = Student.class,
                                parentColumns = "id",
                                childColumns = "studentId")
                    })
    public class Schools {
        public int teacherId;
        public int studentId;
    }
    

——這會生成一個多對多關係模型。可以通過 DAO查詢某個學生有哪些老師,或通過查詢某個老師有哪些學生。

    @Dao
    public interface SchoolsDao {
        @Insert
        void insert(Schools schools);

        @Query("SELECT * FROM teacher " +
               "INNER JOIN shools " +
               "ON teacher.id=schools.teacherId " +
               "WHERE schools.studentId=:studentId")
        List<Teacher> getTeachers(final int studentId);

        @Query("SELECT * FROM student " +
               "INNER JOIN schools " +
               "ON student.id=schools.studentId " +
               "WHERE schools.teacherId=:teacherId")
        List<Student> getTeachers(final int playlistId);
    }

(3)使用Dao訪問數據庫

——創建 DAO 方法並使用 @Insert 對其進行註釋時,Room 會生成一個實現,該實現在單個事務中將所有參數插入到數據庫中。

@Insert :將數據以參數形式給出的實體添加到數據庫

    @Dao
    public interface MyDao {
        @Insert(onConflict = OnConflictStrategy.REPLACE)
        public void insertUsers(User... users);

        @Insert
        public void insertBothUsers(User user1, User user2);

        @Insert
        public void insertUsersAndFriends(User user, List<User> friends);
    }

@Updata :更新/修改數據庫中以參數形式給出的一組實體

    @Dao
    public interface MyDao {
        @Update
        public void updateUsers(User... users);
    }

@Delete :從數據庫中 刪除 一組以參數形式給出的實體

    @Dao
    public interface MyDao {
        @Delete
        public void deleteUsers(User... users);
    }

@Query :根據語法從數據庫中查詢數據

    @Dao
    public interface MyDao {
        @Query("SELECT * FROM user")
        public User[] loadAllUsers();
    }
    

——1.將參數傳遞給查詢

    @Dao
    public interface MyDao {
        @Query("SELECT * FROM user WHERE age > :minAge")
        public User[] loadAllUsersOlderThan(int minAge);

        @Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge")
        public User[] loadAllUsersBetweenAges(int minAge, int maxAge);

        @Query("SELECT * FROM user WHERE first_name LIKE :search " +
               "OR last_name LIKE :search")
        public List<User> findUserWithName(String search);
    }

——2.返回列的子集:大多數情況下,我們只需要獲取實體的幾個字段,而不是全部。這樣可以節省資源、查詢更快。

可以通過重新定義返回結果的對象(裏面的字段都是從原結果中提取出來的)如:

 去掉常見的id。提取我們所需要的名字信息。

    public class NameTuple {
        @ColumnInfo(name = "first_name")
        public String firstName;

        @ColumnInfo(name = "last_name")
        @NonNull
        public String lastName;
    }

    @Dao
    public interface MyDao {
        @Query("SELECT first_name, last_name FROM user")
        public List<NameTuple> loadFullName();
    }

Room 知道該查詢會返回 first_name 和 last_name 列的值,並且這些值會映射到 NameTuple 類的字段。

——3.傳遞參數的集合:部分查詢可能要求您傳入數量不定的參數。

    @Dao
    public interface MyDao {
        @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
        public List<NameTuple> loadUsersFromRegions(List<String> regions);
    }
    

——4.可觀察查詢:執行查詢時,數據發生變化時自動更新UI。使用 LiveData 類型的返回值。

    @Dao
    public interface MyDao {
        @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
        public LiveData<List<User>> loadUsersFromRegionsSync(List<String> regions);
    }

——5.使用 RxJava 進行響應式查詢

Room 爲 RxJava2 類型的返回值提供了以下支持:

  • @Query 方法:Room 支持 Publisher、Flowable 和 Observable 類型的返回值。
  • @Insert@Update 和 @Delete 方法:Room 2.1.0 及更高版本支持 Completable 、Single<T> 和 Maybe<T> 類型的返回值。

  在 app/build.gradle 中添加相關依賴

    dependencies {
        def room_version = "2.1.0"
        implementation 'androidx.room:room-rxjava2:$room_version'
    }

使用方式:

    @Dao
    public interface MyDao {
        @Query("SELECT * from user where id = :id LIMIT 1")
        public Flowable<User> loadUserById(int id);

        // Emits the number of users added to the database.
        @Insert
        public Maybe<Integer> insertLargeNumberOfUsers(List<User> users);

        // Makes sure that the operation finishes successfully.
        @Insert
        public Completable insertLargeNumberOfUsers(User... users);

        /* Emits the number of users removed from the database. Always emits at
           least one user. */
        @Delete
        public Single<Integer> deleteUsers(List<User> users);
    }

——6.直接光標訪問:如果應用的邏輯需要直接訪問返回行,您可以從查詢返回 Cursor 對象

注意:強烈建議您不要使用 Cursor API,因爲它無法保證行是否存在或者行包含哪些值。只有當您已具有需要光標且無法輕鬆重構的代碼時,才使用此功能。

    @Dao
    public interface MyDao {
        @Query("SELECT * FROM user WHERE age > :minAge LIMIT 5")
        public Cursor loadRawUsersOlderThan(int minAge);
    }

——7.查詢多個表格:

以下代碼段展示瞭如何執行表格聯接來整合兩個表格的信息:一個表格包含當前借閱圖書的用戶,另一個表格包含當前處於已被借閱狀態的圖書的數據。

    @Dao
    public interface MyDao {
        @Query("SELECT * FROM book " +
               "INNER JOIN loan ON loan.book_id = book.id " +
               "INNER JOIN user ON user.id = loan.user_id " +
               "WHERE user.name LIKE :userName")
       public List<Book> findBooksBorrowedByNameSync(String userName);
    }

——8.使用 Kotlin 協程編寫異步方法

可以將 suspend Kotlin 關鍵字添加到 DAO 方法,以使用 Kotlin 協程功能使這些方法成爲異步方法。這樣可確保不會在主線程上執行這些方法

    @Dao
    interface MyDao {
        @Insert(onConflict = OnConflictStrategy.REPLACE)
        suspend fun insertUsers(vararg users: User)

        @Update
        suspend fun updateUsers(vararg users: User)

        @Delete
        suspend fun deleteUsers(vararg users: User)

        @Query("SELECT * FROM user")
        suspend fun loadAllUsers(): Array<User>
    }

(4)創建視圖

2.1.0 及更高版本的 Room 持久性庫爲 SQLite 數據庫視圖提供了支持,從而允許您將查詢封裝到類中。Room 將這些查詢支持的類稱爲視圖。

注意:與實體類似,您可以針對視圖運行 SELECT 語句。不過,您無法針對視圖運行 INSERTUPDATE 或 DELETE 語句。

要創建視圖,請將 @DatabaseView 註釋添加到類中。將註釋的值設爲類應該表示的查詢

    @DatabaseView("SELECT user.id, user.name, user.departmentId," +
                  "department.name AS departmentName FROM user " +
                  "INNER JOIN department ON user.departmentId = department.id")
    public class UserDetail {
        public long id;
        public String name;
        public long departmentId;
        public String departmentName;
    }

    //將視圖與數據庫相關聯
    @Database(entities = {User.class}, views = {UserDetail.class}, version = 1)
    public abstract class AppDatabase extends RoomDatabase {
        public abstract UserDao userDao();
    }

(5)遷移 Room 數據庫 / 數據庫升級處理

  • 當開發者添加和修改數據庫後,用戶更新到應用的最新版本時,不想讓他們丟失所有現有數據。可以編寫 Migration 類,以這種方式保留用戶數據。
  • 每個 Migration 類均指定一個 startVersion 和 endVersion。在運行時,Room 會運行每個 Migration 類的 migrate() 方法,以按照正確的順序將數據庫遷移到更高版本。
    static final Migration MIGRATION_1_2 = new Migration(1, 2) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            database.execSQL("CREATE TABLE `Fruit` (`id` INTEGER, "
                    + "`name` TEXT, PRIMARY KEY(`id`))");
        }
    };

    static final Migration MIGRATION_2_3 = new Migration(2, 3) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            database.execSQL("ALTER TABLE Book "
                    + " ADD COLUMN pub_year INTEGER");
        }
    };

    Room.databaseBuilder(getApplicationContext(), MyDb.class, "database-name")
            .addMigrations(MIGRATION_1_2, MIGRATION_2_3).build();

以上常用功能官網都有,這裏只簡單總結 歸納介紹。詳情請看官網。

四、源碼分析

從使用方式一步步分析源碼

(1)創建數據庫實例

Room.databaseBuilder(activity.getApplicationContext(),RoomDbManager.class, "room_blcs").build();

1.Room.databaseBuilder()

/**
 * 創建 RoomDatabase.Builder
 */
public static <T extends RoomDatabase> RoomDatabase.Builder<T> databaseBuilder(
		@NonNull Context context, @NonNull Class<T> klass, @NonNull String name) {
	//當沒有傳入數據庫名字時拋出異常
	if (name == null || name.trim().length() == 0) {
		throw new IllegalArgumentException("Cannot build a database with null or empty name."
				+ " If you are trying to create an in memory database, use Room"
				+ ".inMemoryDatabaseBuilder");
	}
	//分析——> 2
	return new RoomDatabase.Builder<>(context, klass, name);
}

2.RoomDatabase.Builder()

/**
 * 初始化Builder屬性,並創建了Migration容器
 */
Builder(@NonNull Context context, @NonNull Class<T> klass, @Nullable String name) {
	mContext = context;
	//擴展RoomDatabase的抽象類
	mDatabaseClass = klass;
	//數據庫名稱
	mName = name;
	//數據庫日誌模式
	mJournalMode = JournalMode.AUTOMATIC;
	//是否更新數據庫
	mRequireMigration = true;
	//分析——> 3
	mMigrationContainer = new MigrationContainer();
}

3.MigrationContainer

/**
 * Migration容器:用於保存Migration,允許查詢Migration兩個版本之間的內容
 * 該實例用於數據庫版本升級時起作用,這裏就不詳細分析數據庫升級源碼,
 * 大致實現方式:
 * 1.當數據庫發生變化對版本進行升級時,開發者需要通過addMigration方法添加Migration實例,對升級進行處理,避免數據丟失。
 * 2.當數據庫升級後,會調用onUpgrade()方法,該方法通過findMigrationPath()找到Migration實例,執行數據庫升級處理。
 * 3.若沒有添加Migration實例對數據庫處理,則room會執行刪除所有表格,再新建所有表格。則就會造成數據丟失
 */
public static class MigrationContainer {
	//Migration 容器
	private SparseArrayCompat<SparseArrayCompat<Migration>> mMigrations =
			new SparseArrayCompat<>();

	/**
	 * 添加一組Migration到容器中
	 */
	public void addMigrations(@NonNull Migration... migrations) {
		for (Migration migration : migrations) {
			addMigration(migration);
		}
	}
	/**
	 * 添加單個Migration到容器中,如果已經存在則覆蓋
	 */
	private void addMigration(Migration migration) {
		final int start = migration.startVersion;
		final int end = migration.endVersion;
		SparseArrayCompat<Migration> targetMap = mMigrations.get(start);
		if (targetMap == null) {
			targetMap = new SparseArrayCompat<>();
			mMigrations.put(start, targetMap);
		}
		Migration existing = targetMap.get(end);
		if (existing != null) {
			Log.w(Room.LOG_TAG, "Overriding migration " + existing + " with " + migration);
		}
		targetMap.append(end, migration);
	}

	/**
	 * 獲取兩個版本之間的Migration列表
	 */
	@SuppressWarnings("WeakerAccess")
	@Nullable
	public List<Migration> findMigrationPath(int start, int end) {
		if (start == end) {
			return Collections.emptyList();
		}
		boolean migrateUp = end > start;
		List<Migration> result = new ArrayList<>();
		return findUpMigrationPath(result, migrateUp, start, end);
	}

	...
}

4.RoomDatabase.Builder.build()

/**
 * 創建數據庫實例並初始化
 * 返回一個繼承RoomDbManager實例 ,根據Demo這裏生成的是RoomDbManager_Impl.class
 */
@NonNull
public T build() {
	//這邊省略一些判斷條件,僅貼出核心代碼
	...
	//創建FrameworkSQLiteOpenHelperFactory  分析——> 5
	//該實例實現了SupportSQLiteOpenHelper.Factory的create方法。對數據庫進行封裝
	//create方法在->8 會調用到
	if (mFactory == null) {
		mFactory = new FrameworkSQLiteOpenHelperFactory();
	}
	//創建數據庫配置類並初始化其屬性
	DatabaseConfiguration configuration =
			new DatabaseConfiguration(mContext, mName, mFactory, mMigrationContainer,
					mCallbacks, mAllowMainThreadQueries,
					mJournalMode.resolve(mContext),
					mRequireMigration, mMigrationsNotRequiredFrom);
	//創建數據庫實例  分析——> 6
	T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX);
	//初始化數據庫  分析——>7
	db.init(configuration);
	return db;
}

5.FrameworkSQLiteOpenHelperFactory

/**
 * 實現SupportSQLiteOpenHelper.Factory 並重寫了create()方法
 */
public final class FrameworkSQLiteOpenHelperFactory implements SupportSQLiteOpenHelper.Factory {

	@Override
	public SupportSQLiteOpenHelper create(SupportSQLiteOpenHelper.Configuration configuration) {
		//到第 8 點纔回執行到該方法 可以先跳過 執行到在回來分析
		//創建了FrameworkSQLiteOpenHelper對象,該對象持有數據庫實例
		//分析——> 9
		return new FrameworkSQLiteOpenHelper(
				configuration.context, configuration.name, configuration.callback);
	}
}

6.Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX)

/**
 * 利用反射機制 創建一個繼承RoomDbManager.class 的實例 
 */
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;
	try {

		@SuppressWarnings("unchecked")
		//加載指定名稱的類  這裏加載的是:RoomDbManager_Impl.class  該類由APT(Android註解處理器)生成 
		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());
	}
}

7.db.init(configuration);

/**
 * 初始化RoomDatabase 屬性
 */
public void init(@NonNull DatabaseConfiguration configuration) {
	//分析——>8   RoomDbManager_Impl類實現了該方法
	//該方法獲取了FrameworkSQLiteOpenHelper對象並持有數據庫實例,
	//完成了數據庫的創建與配置。
	mOpenHelper = createOpenHelper(configuration);
	...
}

8.createOpenHelper(configuration) 

@Override
protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration configuration) {
	//首先 創建RoomOpenHelper.Delegate實例,該實例實現了封裝了RoomOpenHelper方法的一些實現
	//又創建了RoomOpenHelper實例,該實例持有RoomOpenHelper.Delegate,並調用Delegate方法完成數據庫的創建  由 分析——>11 得出
	final SupportSQLiteOpenHelper.Callback _openCallback = new RoomOpenHelper(configuration, new RoomOpenHelper.Delegate(1) {
	  // 僅貼出部分源碼
	  @Override
	  public void createAllTables(SupportSQLiteDatabase _db) {
		_db.execSQL("CREATE TABLE IF NOT EXISTS `Student` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `sex` TEXT, `age` INTEGER NOT NULL)");
		_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, \"3022583cc4e29bfa9733f59fc1573949\")");
	  }
		...
	}, "3022583cc4e29bfa9733f59fc1573949", "16c81d90557b0b886cda3cb098388f2c");
	//創建SupportSQLiteOpenHelper.Configuration類 持有該 RoomOpenHelper 對象
	final SupportSQLiteOpenHelper.Configuration _sqliteConfig = SupportSQLiteOpenHelper.Configuration.builder(configuration.context)
		.name(configuration.name)
		.callback(_openCallback)
		.build();

	//這邊 通過DatabaseConfiguration 對象執行了 FrameworkSQLiteOpenHelperFactory的create方法 
	//將數據庫的配置信息傳給了SupportSQLiteOpenHelper   分析——>5 
	//通過分析5 這邊 _helper 其實就是FrameworkSQLiteOpenHelper對象 ,該對象實現了SupportSQLiteOpenHelper接口
	final SupportSQLiteOpenHelper _helper = configuration.sqliteOpenHelperFactory.create(_sqliteConfig);
	return _helper;
}

9.FrameworkSQLiteOpenHelper 

/**
 * 該構造方法裏面 執行了createDelegate()創建了數據庫實例
 * 也就是FrameworkSQLiteOpenHelper持有數據庫OpenHelper 的引用
 */
FrameworkSQLiteOpenHelper(Context context, String name,
		Callback callback) {
	//分析——>10
	mDelegate = createDelegate(context, name, callback);
}

10.createDelegate

/**
 * 該方法主要是創建了創建了一個數據庫實例OpenHelper,
 * 並將數據庫的操作方法封裝在FrameworkSQLiteDatabase對象中
 * 數據庫的建表及其他初始化 交給RoomOpenHelper對象去實現。
 */
private OpenHelper createDelegate(Context context, String name, Callback callback) {
	//創建了一個FrameworkSQLiteDatabase數組
	final FrameworkSQLiteDatabase[] dbRef = new FrameworkSQLiteDatabase[1];
	//創建了數據庫實例   分析——>11
	return new OpenHelper(context, name, dbRef, callback);
}

11.OpenHelper 

/**
 * OpenHelper繼承了SQLiteOpenHelper,這個就是開發者常見的創建數據庫方式。
 * 通過創建該實例就可以操控數據庫了。  這裏僅貼出部分方法介紹
 */
static class OpenHelper extends SQLiteOpenHelper {
	OpenHelper(Context context, String name, final FrameworkSQLiteDatabase[] dbRef,
			final Callback callback) {
		super(context, name, null, callback.version,
				new DatabaseErrorHandler() {
					@Override
					public void onCorruption(SQLiteDatabase dbObj) {
						FrameworkSQLiteDatabase db = dbRef[0];
						if (db != null) {
							callback.onCorruption(db);
						}
					}
				});
		//通過 分析8和5 得出這裏callback其實就是RoomOpenHelper對象
		mCallback = callback;
		//FrameworkSQLiteDatabase數組
		mDbRef = dbRef;
	}
	/**
	 * 創建FrameworkSQLiteDatabase實例,該實例是對SQLiteDatabase對象的所有操作進行封裝
	 * 通過調用該實例就可以對數據庫進行操作
	 */
	FrameworkSQLiteDatabase getWrappedDb(SQLiteDatabase sqLiteDatabase) {
		FrameworkSQLiteDatabase dbRef = mDbRef[0];
		//判斷該對象是否已經存在
		if (dbRef == null) {
			dbRef = new FrameworkSQLiteDatabase(sqLiteDatabase);
			mDbRef[0] = dbRef;
		}
		return mDbRef[0];
	}
	/**
	 * 把建表的操作都交給了mCallback  也就是RoomOpenHelper實例
	 * RoomOpenHelper相當於一個代理類,把操作都交給了RoomOpenHelper來實現。
	 */
	@Override
	public void onCreate(SQLiteDatabase sqLiteDatabase) {
		mCallback.onCreate(getWrappedDb(sqLiteDatabase));
	}
	
	@Override
	public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
		mMigrated = true;
		mCallback.onUpgrade(getWrappedDb(sqLiteDatabase), oldVersion, newVersion);
	}
}

總結:

  1. 先調用了Room.databaseBuilder()傳入數據庫的版本信息與名稱,創建 RoomDatabase.Builder對象並建立Migration容器。
  2. 再調用了Builder.build()方法,先是創建FrameworkSQLiteOpenHelperFactory對象實現了create方法。create方法內部對數據庫進行封裝。
  3. build()方法內通過反射機制創建了RoomDatabase的子類(RoomDbManager_Impl.class)。該子類由APT生成。
  4. build()方法內又創建了數據庫配置類,給RoomDatabase的子類配置信息。
  5. 配置過程調用了RoomDatabase子類的createOpenHelper()方法,該方法創建了RoomOpenHelper實例,實現數據庫的建表語句及其他數據庫操作語句。
  6. 最終createOpenHelper()方法將RoomOpenHelper實例傳入到FrameworkSQLiteOpenHelperFactory對象的create方法完成數據庫的創建於封裝。

(2)操作表的方法

RoomDbManager room_blcs = Room.databaseBuilder(activity.getApplicationContext(),
			RoomDbManager.class, "room_blcs").build();
room_blcs.getStudentDao()	

通過(1)源碼分析。build()方法返回的是RoomDbManager的子類RoomDbManager_Impl
而room_blcs.getStudentDao()也就是執行了RoomDbManager_Impl.getStudentDao()

1.RoomDbManager_Impl.getStudentDao()

/**
 * 創建了StudentDao_Impl實例 該實例由APT生成
 */
@Override
public StudentDao getStudentDao() {
if (_studentDao != null) {
		return _studentDao;
	} else {
	  synchronized(this) {
		if(_studentDao == null) {
			//分析 ——>2
		  _studentDao = new StudentDao_Impl(this);
		}
		return _studentDao;
	  }
	}
}

2.StudentDao_Impl

/**
 * 該類實現了StudentDao接口的所有方法,通過調用這些方法就可以操作數據庫
 */
public class StudentDao_Impl implements StudentDao {
  private final RoomDatabase __db;

  private final EntityInsertionAdapter __insertionAdapterOfStudent;
  ...
  /**
   * 在構造函數中創建增 刪 改 適配器 來完成插入刪除更新操作
   */
  public StudentDao_Impl(RoomDatabase __db) {
    this.__db = __db;
	//分析 ——>3
    this.__insertionAdapterOfStudent = new EntityInsertionAdapter<Student>(__db) {
      @Override
      public String createQuery() {
		//由APT生成 交給 EntityInsertionAdapter 執行
        return "INSERT OR ABORT INTO `Student`(`id`,`name`,`sex`,`age`) VALUES (nullif(?, 0),?,?,?)";
      }
    ...
  } 

  @Override
  public void insertOne(Student student) {
    __db.beginTransaction();
    try {
	//分析 ——> 4
      __insertionAdapterOfStudent.insert(student);
      __db.setTransactionSuccessful();
    } finally {
      __db.endTransaction();
    }
  }
	...
	這裏省略其他相同的實現方法

/**
 * 查詢方法直接幫我們生成查詢語句 並進行數據的解析處理。
 */
  @Override
  public List<Student> getAll() {
    final String _sql = "SELECT * FROM Student";
    final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
    final Cursor _cursor = __db.query(_statement);
    try {
      final int _cursorIndexOfId = _cursor.getColumnIndexOrThrow("id");
      final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
      final int _cursorIndexOfSex = _cursor.getColumnIndexOrThrow("sex");
      final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("age");
      final List<Student> _result = new ArrayList<Student>(_cursor.getCount());
      while(_cursor.moveToNext()) {
        final Student _item;
        _item = new Student();
        final int _tmpId;
        _tmpId = _cursor.getInt(_cursorIndexOfId);
        _item.setId(_tmpId);
        final String _tmpName;
        _tmpName = _cursor.getString(_cursorIndexOfName);
        _item.setName(_tmpName);
        final String _tmpSex;
        _tmpSex = _cursor.getString(_cursorIndexOfSex);
        _item.setSex(_tmpSex);
        final int _tmpAge;
        _tmpAge = _cursor.getInt(_cursorIndexOfAge);
        _item.setAge(_tmpAge);
        _result.add(_item);
      }
      return _result;
    } finally {
      _cursor.close();
      _statement.release();
    }
  }
}

3.EntityInsertionAdapter

/**
 * 創建了EntityInsertionAdapter 實例 並持有RoomDatabase的引用
 */
public abstract class EntityInsertionAdapter<T> extends SharedSQLiteStatement {
	...
    public EntityInsertionAdapter(RoomDatabase database) {
        super(database);
    }
	...
}
public abstract class SharedSQLiteStatement {
	public SharedSQLiteStatement(RoomDatabase database) {
        mDatabase = database;
    }
}

4.EntityInsertionAdapter.insert(T entity)

public final void insert(T entity) {
	//分析 ——> 5 
	final SupportSQLiteStatement stmt = acquire();
	try {
		bind(stmt, entity);
		stmt.executeInsert();
	} finally {
		release(stmt);
	}
}

5.acquire()

public SupportSQLiteStatement acquire() {
	//是否允許在主線程上執行 默認爲false
	assertNotMainThread();
	// 分析——> 6 
	return getStmt(mLock.compareAndSet(false, true));
}

6.getStmt(boolean canUseCached)
/**
* 創建並執行數據語句
*/
private SupportSQLiteStatement getStmt(boolean canUseCached) {
	final SupportSQLiteStatement stmt;
	if (canUseCached) {
		if (mStmt == null) {
			// 分析——> 7 
			mStmt = createNewStatement();
		}
		stmt = mStmt;
	} else {
		stmt = createNewStatement();
	}
	return stmt;
}
	
7.createNewStatement()
/**
* 獲取數據庫語句 並執行該語句操作
*/
private SupportSQLiteStatement createNewStatement() {
	//EntityInsertionAdapter實現了該方法 獲取到數據庫語句
	String query = createQuery();
	// 分析——> 8 
	return mDatabase.compileStatement(query);
}

8.compileStatement()
/**
* 調用數據庫實例執行語句 
*/
public SupportSQLiteStatement compileStatement(@NonNull String sql) {
	assertNotMainThread();
	return mOpenHelper.getWritableDatabase().compileStatement(sql);
}

這部分比較簡單就不做太多解釋

總結:

  1. 通過APT生成了StudentDao_Impl實現了StudentDao接口的所有關於數據庫操作的方法。
  2. StudentDao_Impl持有對RoomDatabase的引用。而RoomDatabase在分析(1)中已持有數據庫實例
  3. StudentDao_Impl通過StudentDao裏面的方法通過註解生成數據庫語句。並調用數據庫實例執行語句。

五、內容推薦

六、項目參考

使用方式Demo放在下面項目  “其他”->"Jetpack架構組件"->"Room"

Github / apk下載體驗地址/掃碼下載體驗 

若您發現文章中存在錯誤或不足的地方,希望您能指出!

發佈了37 篇原創文章 · 獲贊 41 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章