ROOM源码解析

1,520 阅读5分钟

ROOM简介


背景

为什么需要数据库的封装

  • 容易在主线程使用数据库api
  • SQL语法的正确性无法保证,必须等到运行时才能发现
  • 表中的数据转化对象步骤繁琐,这也是ORM的作用
  • 升级数据库时候字段变化容易出错

ROOM简介

Room是Android Jetpack包的一部分,为应用提供数据库支持。Room持久性库提供了一个基于SQLite的抽象层,以便在利用SQLite的全部功能的同时,实现更强大的数据库访问能力。

首先看下整体框架:

ROOM主要分为三部分组件: 1、负责操作数据库提供接口的DAO类 2、数据库表的类Entities 3、负责管理数据库以及实现接口访问的RoomDataBase

我们看下简单使用:

User:

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

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

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

UserDao:

 @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 first_name LIKE :first AND " +
               "last_name LIKE :last LIMIT 1")
        User findByName(String first, String last);

        @Insert
        void insertAll(User... users);

        @Delete
        void delete(User user);
    }

AppDatabase:

 @Database(entities = {User.class}, version = 1)
    public abstract class AppDatabase extends RoomDatabase {
        public abstract UserDao userDao();
    }
    

接下来我们只要调用创建数据库就能正常存取数据了:

  AppDatabase db = Room.databaseBuilder(getApplicationContext(),
            AppDatabase.class, "database-name").build();

使用起来还是很简单的,后续我们具体分析下试下原理

框架简介

在正式介绍流程之前,我们简单介绍下ROOM的源码大概框架:

我们简单分四个层面,最下面的是Android提供的基础接口,Androidx Sqlite是Androidx抽象出来的接口,接口基本都是映射系统提供的接口,Androidx Sqlite-FrameWork是针对其抽象的实现,这样做的好处是彻底与系统库解偶,一旦你出现更换系统接口封装接口可不受影响,虽然这概率不大。

刚刚我们主要梳理的是ROOM对系统接口的封装,那么ROOM业务层的框架又是什么样子的呢?

我们来看下整体的引用关系:

有点复杂,我们先梳理下流程

创建数据库

首先上图,创建数据库流程:

其实创建数据库主要就是一个初始化SupportSQLiteOpenHelper的过程,后续所有的数据库操作都需要依赖它

看代码,创建的入口是:

 _INSTANCE = Room.databaseBuilder(context, TestDataBase.class, TABLE_NAME).build();

创建的逻辑主要就是RoomDatabase的创建,builder模式中的build主要执行以下两个方法:

 T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX);
db.init(configuration);

getGeneratedImplementation中,就是反射调用实例化Database_impl的过程,init中的主要作用就是构建SupportSQLiteOpenHelper。

 mOpenHelper = createOpenHelper(configuration);

SupportSQLiteOpenHelper是Room与SQLite的唯一交互接口,ROOM将SQL连接的生命周期事件和真正的数据库操作分为了两部分,前者是SupportSQLiteOpenHelper.Callback(实现类是RoomOpenHelper),后者是FrameworkSQLiteOpenHelper本身逻辑,RoomOpenHelper有两个比较重要的方法,onCreate和onUpgrade,一个是数据库创建,一个是数据库的升级,还有些是数据库其他的生命周期事件通知。

数据库查询

再次之前先了解下RoomDatabase这个类,是ROOM三大组件中的操作数据库入口,我们之前提到SupportSQLiteOpenHelper是Room与SQLite的唯一交互接口,不难猜出RoomDatabase肯定持有了SupportSQLiteOpenHelper,不仅如此,还持有了SupportSQLiteDatabase,所以RoomDatabase不仅仅是替代了系统SQLiteDatabase,还扩展了SQLiteOpenHelper,这样搞业务层的分工更明确,这种组合模式让业务层更加灵活。

接下来咱们看下查询的流程,查询入口:

public User getUser(final int userId) {
	//构建查询的SQL语句
    final String _sql = "SELECT * FROM user where uid LIKE ?";
    //获取对用的查询statement对象
    final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
    ...
    //执行查询
    final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
    ...
  }

说到了RoomSQLiteQuery,这里简单介绍下,RoomSQLiteQuery和SimpleSQLiteQuery是两个实现了数据绑定的实现类,这两者的区别在于RoomSQLiteQuery有缓存特定查询的RoomSQLiteQuery对象缓存,而SimpleSQLiteQuery没有。另外一个特点是RoomSQLiteQuery在绑定数据中并不需要拆装箱,所有的查询参数类型是事先预设,而SimpleSQLiteQuery是根据传入的object[]数据进行拆装箱进行绑定。

RoomSQLiteQuery的封装考虑了性能以及查询复用。

继续查询流程,流程图如下:

从RoomDataBase的查询接口,最终走到SQLiteDatabase.rawQueryWithFactory

public Cursor query(final SupportSQLiteQuery supportQuery,
            CancellationSignal cancellationSignal) {
        return mDelegate.rawQueryWithFactory(new SQLiteDatabase.CursorFactory() {
            @Override
            public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery,
                    String editTable, SQLiteQuery query) {
                supportQuery.bindTo(new FrameworkSQLiteProgram(query));
                return new SQLiteCursor(masterQuery, editTable, query);
            }
        }, supportQuery.getSql(), EMPTY_STRING_ARRAY, null, cancellationSignal);
    }

数据库查询的步骤基本有两步,一个是SQL语句,还有一个是参数绑定,但是在执行查询的时候不是直接传入参数列表,而是在查询前一步通过RoomSQLiteQuery来绑定:

android.database.sqlite.SQLiteDirectCursorDriver#query

数据库增删改

ROOM的增删改,基本沿用了系统的接口,但是鉴于增删改的操作重复性,中间做了一层缓存管理,针对增删改设计了EntityInsertionAdapter、EntityDeletionOrUpdateAdapter两个类,前者用来实现增加,或者用来实现删除或者修改。

以insert为例:

 public final void insert(T entity) {
        final SupportSQLiteStatement stmt = acquire();
        try {
            bind(stmt, entity);
            stmt.executeInsert();
        } finally {
            release(stmt);
        }
    }

与系统接口流程一致,先获取操作数据库的Statement,绑定参数,最后执行插入。

SharedSQLiteStatement的缓存是如何实现的呢,首先我们看下

 private SupportSQLiteStatement getStmt(boolean canUseCached) {
        final SupportSQLiteStatement stmt;
        if (canUseCached) {
            if (mStmt == null) {
                mStmt = createNewStatement();
            }
            stmt = mStmt;
        } else {
            // it is in use, create a one off statement
            stmt = createNewStatement();
        }
        return stmt;
    }

获取Stmt时候可选择不使用缓存,如果首次的话会直接创建新的

private SupportSQLiteStatement createNewStatement() {
        String query = createQuery();
        return mDatabase.compileStatement(query);
    }

这里创建的SQL语句针对不同的增删改有一定的复用性,比如insert:

 public String createQuery() {
        return "INSERT OR ABORT INTO `user` (`uid`,`name`,`age`,`gender`,`score`,`height`,`url`) VALUES (?,?,?,?,?,?,?)";
      }

insert的流程图如下:

删改的流程与插入类似,不在此赘述。

除了以上的内容其实还有更新以及数据流的部分还没有提及,这个后续继续更新