美文网首页Android进阶之路Android开发经验谈Android技术知识
Android Jetpack架构组件之Room入门及源码分析

Android Jetpack架构组件之Room入门及源码分析

作者: 斌林诚上 | 来源:发表于2019-12-15 17:56 被阅读0次

    ——你拼命挣钱的样子,虽然有些狼狈;但是你自己靠自己的样子,真的很美!

    前言

    ——这篇主要是梳理一下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_namelast_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 语句。不过,您无法针对视图运行 INSERTUPDATEDELETE 语句。

    要创建视图,请将 @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下载体验地址

    若您发现文章中存在错误或不足的地方,希望您能指出!

    相关文章

      网友评论

        本文标题:Android Jetpack架构组件之Room入门及源码分析

        本文链接:https://www.haomeiwen.com/subject/xeclnctx.html