从Room源码看抽象与封装——SQLite的抽象

目录

源码解析目录
从Room源码看抽象与封装——SQLite的抽象
从Room源码看抽象与封装——数据库的创建
从Room源码看抽象与封装——数据库的升降级
从Room源码看抽象与封装——Dao
从Room源码看抽象与封装——数据流

前言

什么是Room?Room是Android Jetpack包的一部分,为应用提供数据库支持。

The Room persistence library provides an abstraction layer over SQLite to allow for more robust database access while harnessing the full power of SQLite.

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

如官方定义所说,Room是对SQLite的抽象,让我们可以更加方便且安全地使用SQLite数据库。
抽象与封装可以说是软件开发的灵魂,由此也诞生出了非常多的理论来指导我们的实践,譬如,设计原则和设计模式。诸多大神们为提升开发效率,构建健壮、可维护的代码以及保住大家的发量可谓操碎了心,奈何,我们也是力不从心啊,有时候,我们也意识到了应该对某些代码进行良好的抽象与封装,但是,臣妾做不到啊。在这一点上,Room提供了良好的范本,首先,数据库之于应用还是非常常见的需求,大家对此也不陌生。其次,Room对于SQLite的抽象也不算复杂,具有非常好的学习价值。本文从源码出发,来看看Room是如何对SQLite进行抽象与封装的。 (源码版本androidx.room:room-runtime:2.1.0)

1. SQLite的基本使用

今时不同往日,在Android平台上直接使用SQLite的情况越来越少了,我们一般都会使用各种ORM框架来帮助我们完成数据库的操作,以至于现在让我不用ORM框架,我都不知道Android平台原生的SQLite是如何操作的。但是,要想知道Room是如何封装SQLite的,就必须了解SQLite原来是怎么使用的。好歹,这一切并不复杂。不复杂,我们就直接看代码。

//表名、列名
const val TABLE_NAME = "entry"
const val COLUMN_NAME_ID = "id"
const val COLUMN_NAME_TITLE = "title"
const val COLUMN_NAME_SUBTITLE = "subtitle"

//建表 SQL,是不是很容易犯错,如果使用Java就更容易犯错了
//即使你说这些我信手拈来,但是依然很繁琐
private const val SQL_CREATE_ENTRIES =
        "CREATE TABLE $TABLE_NAME (" +
                "$ID INTEGER PRIMARY KEY," +
                "$COLUMN_NAME_TITLE TEXT," +
                "$COLUMN_NAME_SUBTITLE TEXT)"

class ReaderDbHelper(context: Context) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {
    companion object {
        const val DATABASE_VERSION = 1
        const val DATABASE_NAME = "FeedReader.db"
    }
    
    override fun onCreate(db: SQLiteDatabase) {
        db.execSQL(SQL_CREATE_ENTRIES)
    }
    
    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
        //根据oldVersion和newVersion编写相应的SQL语句对数据库进行升级
        //在实际应用中,这是比较难处理的一部分,因为缺少必要的验证,极容易出错
    }
    
    //基类中包含了两个重要的方法 getWritableDatabase() 和 getReadableDatabase()
    //这是我们操作数据库的入口
}

以上就是SQLite的基本使用方式,首先要继承SQLiteOpenHelper类,SQLiteOpenHelper包含了操作数据库的一系列API,是Android平台对SQLite的原生封装;其次就是在SQLiteOpenHelperonCreateonUpgrade方法中通过SQL语句来建表和升级数据库;最后通过getWritableDatabase 或者 getReadableDatabase对数据库进行增删改查(CRUD)。增删改查都有对应的方法,分别是insertdeleteupdatequery,其使用都是一些套路似的步骤,在此略过。
使用SQLiteOpenHelper的方式来操作数据库有诸多的问题:

  1. 极容易在主线程对数据库进行操作。无论是getWritableDatabase()抑或是增删改查等方法的调用,都应该放在子线程中,但是,由于没有什么方法来阻止我们这么做,所以你不自觉的就会在主线程中这么干。法无禁止皆可为,不抛出来个异常,你是不会在意的。
  2. SQL语句的正确性及安全性都没有保证,问题被延迟到了运行时才能被发现。
  3. 从数据库数据到我们所需要的类数据之间的转换繁琐不堪,这也是ORM(Object Relational Mapping,对象关系映射)框架的意义所在。
  4. 升(降)级数据库缺乏必要验证,极容易出错。导致的结果就是,有时候为了避免数据库升级,甚至在建表的时候增加诸多冗余字段,丑陋不堪。

2. Room的基本使用

简单介绍一下Room的基本使用方式。Room的使用基本上就是加各种各样的注解。
数据库表用@Entity注解,表中的其它信息有各种各样的注解,最主要的两个注解是@PrimaryKey@ColumnInfo,这两个注解的作用也是一目了然:

@Entity
data class User(
    //PrimaryKey包含一个autoGenerate属性,默认false,设置为true表示自增
    @PrimaryKey val uid: Int,
    //如果全部使用ColumnInfo的默认设置,@ColumnInfo注解可以省略
    @ColumnInfo val firstName: String?,
    //最主要的可以修改属性是 列名
    @ColumnInfo(name = "last_name") val lastName: String?
)

数据库操作用@Dao注解,CRUD有各自的注解:

@Dao
interface UserDao {
    @Query("SELECT * FROM user")
    fun getAll(): List<User>

    //参数可以用 :参数 这种形式获取
    @Query("SELECT * FROM user WHERE uid IN (:userIds)")
    fun loadAllByIds(userIds: IntArray): List<User>
    
    @Query("SELECT * FROM user WHERE uid = :userId")
    fun getUserById(userId: Int): User?

    @Query("SELECT * FROM user WHERE firstName LIKE :first AND " +
           "lastName LIKE :last LIMIT 1")
    fun findByName(first: String, last: String): User?

    @Insert
    fun insert(user: User)

    @Delete
    fun delete(user: User)
    
    @Update
    fun update(user: User)
}

多说一句,不要被SQL语句吓住,ORM框架的优劣与是否需要编写SQL语句没有直接关系。Room作为ORM框架的后起之秀,必然研究了之前种种ORM框架,最后选择了这种实现方式,这肯定是有它的道理的。退一步讲,我们平常应用中使用到的绝大部分SQL语句真的都非常简单。并且,Room还提供了良好的代码提示和编译时检查,写起来不费劲也不容易出错。

最后还少不了数据库本身,需要继承RoomDatabase并用@Database注解:

@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao
}

//创建数据库,注意保持单例
val db = Room.databaseBuilder(
            applicationContext,
            AppDatabase::class.java, "database-name"
        ).build()
        
//使用时通过Dao来操作数据库
db.userDao().getAll()

以上是对Room最基本使用方法的介绍,更多内容可以查看官方文档

3. 抽象从接口隔离开始

接口隔离是抽象的良好开端,对于复杂系统的抽象更是如此。接口隔离首先需要定义接口,定义接口的过程其实就是抽象的过程。接口中的方法是对职责的明晰,明确了我们需要暴露出哪些API,屏蔽掉哪些底层的细节;接口之间的关联是对被抽象系统关系的梳理,明确被抽象系统的关系有助于我们抽象出更加简洁、合理的关联系统。
是时候展现真正的技术了,以上所说其实可以概括为设计原则中的接口隔离原则(保持瘦接口)和依赖倒置原则(抽象不依赖于具体,具体依赖于抽象),还可以加上单一职责原则。

上文所说的接口隔离和接口隔离原则中的“接口隔离”不是同一个概念,我所表达的接口隔离的意思是,通过接口的方式来把抽象系统和被抽象系统隔离开来,使得我们的抽象系统建立在我们的抽象接口之上,不依赖与被抽象系统的具体细节。

3.1 SQL语句类的抽象

数据库增删改查四种基本操作其实可以分为两类,增删改是一类,查是一类。“增删改”可以返回哪一行受了影响或者总共多少行受影响之类的信息(int或long),而“查”则返回的是Cursor。如前所述,可以调用WritableDatabase的insertdeleteupdate方法分别进行增删改,这其实是Android对SQLite进行的基础封装,最终的实现都是通过一个叫SQLiteStatement类来完成的,SQLiteStatement顾名思义就是SQLite声明的意思,SQLiteStatement可以在数据库上执行,并且可以返回一个值(对应着增删改这种情形,这个值是row ID或者多少行受到了影响)。SQLiteStatement又继承自SQLiteProgram这个类,SQLiteProgram的主要作用是编译SQL语句,翻译成大白话就是对SQL语句中的占位符(?)进行bindNull,bindLong,bindString等绑定工作。

Room对增删改的支持也是通过SQLiteStatement。所以Room对SQLiteProgramSQLiteStatement进行了封装,以一种几乎完全映射的方式定义了SupportSQLiteProgramSupportSQLiteStatement两个接口。并且使用委托的形式又把相应的职责委托给了原本的SQLiteProgramSQLiteStatement

class FrameworkSQLiteProgram implements SupportSQLiteProgram {
    private final SQLiteProgram mDelegate;

    FrameworkSQLiteProgram(SQLiteProgram delegate) {
        mDelegate = delegate;
    }

    @Override
    public void bindNull(int index) {
        mDelegate.bindNull(index);
    }
    
    //其它方法省略,都只是简单的委托
}

class FrameworkSQLiteStatement extends FrameworkSQLiteProgram implements SupportSQLiteStatement {
    private final SQLiteStatement mDelegate;

    FrameworkSQLiteStatement(SQLiteStatement delegate) {
        super(delegate);
        mDelegate = delegate;
    }

    @Override
    public void execute() {
        mDelegate.execute();
    }

    //其它方法省略,都只是简单的委托
}

以上是Room对增删改SQL的抽象,还是非常接近于原生的SQLite的,这种抽象更多的是为了做隔离。


看完增删改,再来看看最重要的查询。其实查询SQL与增删改并没有太多不同,只是我们必须通过WritableDatabase/ReadableDatabase的query方法来执行,以便获取Cursor。Room将查询SQL抽象为了SupportSQLiteQuery

public interface SupportSQLiteQuery {
    /**
     * 查询SQL语句,可以包含若干占位符(?)以便之后进行参数绑定
     */
    String getSql();

    /**
     * 参数绑定的回调,意思就是调用这个方法会把参数绑定到占位符(?)
     */
    void bindTo(SupportSQLiteProgram statement);

    /**
     * 查询SQL中有几个参数,也就是有几个占位符(?)
     */
    int getArgCount();
}

还包含一个简单的实现SimpleSQLiteQuery

public final class SimpleSQLiteQuery implements SupportSQLiteQuery {
    private final String mQuery;
    @Nullable
    private final Object[] mBindArgs;

    public SimpleSQLiteQuery(String query, @Nullable Object[] bindArgs) {
        mQuery = query;
        mBindArgs = bindArgs;
    }

    public SimpleSQLiteQuery(String query) {
        this(query, null);
    }

    @Override
    public String getSql() {
        return mQuery;
    }

    @Override
    public void bindTo(SupportSQLiteProgram statement) {
        bind(statement, mBindArgs);
    }

    @Override
    public int getArgCount() {
        return mBindArgs == null ? 0 : mBindArgs.length;
    }

    public static void bind(SupportSQLiteProgram statement, Object[] bindArgs) {
        if (bindArgs == null) {
            return;
        }
        final int limit = bindArgs.length;
        for (int i = 0; i < limit; i++) {
            final Object arg = bindArgs[i];
            bind(statement, i + 1, arg);
        }
    }

    private static void bind(SupportSQLiteProgram statement, int index, Object arg) {
        // extracted from android.database.sqlite.SQLiteConnection
        if (arg == null) {
            statement.bindNull(index);
        } else if (arg instanceof byte[]) {
            statement.bindBlob(index, (byte[]) arg);
        } else if (arg instanceof Float) {
            statement.bindDouble(index, (Float) arg);
        } else if (arg instanceof Double) {
            statement.bindDouble(index, (Double) arg);
        } else if (arg instanceof Long) {
            statement.bindLong(index, (Long) arg);
        } else if (arg instanceof Integer) {
            statement.bindLong(index, (Integer) arg);
        } else if (arg instanceof Short) {
            statement.bindLong(index, (Short) arg);
        } else if (arg instanceof Byte) {
            statement.bindLong(index, (Byte) arg);
        } else if (arg instanceof String) {
            statement.bindString(index, (String) arg);
        } else if (arg instanceof Boolean) {
            statement.bindLong(index, ((Boolean) arg) ? 1 : 0);
        } else {
            throw new IllegalArgumentException("Cannot bind " + arg + " at index " + index
                    + " Supported types: null, byte[], float, double, long, int, short, byte,"
                    + " string");
        }
    }
}

真是什么都没干,只是简单的绑定了一下。


总结一下Room对于SQL语句类的抽象,概括起来就是映射SQLiteStatement的行为;将Query抽象为SQL语句和绑定,并且是延迟绑定,调用bindTo方法时才进行绑定(这么做是有它的意义的,后文会看到)。其实,SQL语句在执行之前仅仅是个语句而已,如果有占位符?,在执行之前都需要进行绑定,不分什么增删改查。但是由于我们想要获取的数据不同,这导致我们选择的执行方式不同,增删改、查两类不同的SQL对应着两种不同的执行方式,也对应着两种不同的抽象方式。

3.2 数据库的抽象

这里的数据库指的就是通过SQLiteOpenHelpergetWritableDatabase或者getReadableDatabase返回的数据库对象,它是一个SQLiteDatabase类的对象。
数据库的抽象分为两部分:

  1. 对于数据库本身的SQLiteDatabase的抽象。
  2. 对于数据库打开方式SQLiteOpenHelper的抽象。

3.2.1 SQLiteDatabase的抽象

Room对于SQLiteDatabase的抽象比较简单,几乎完全模仿了SQLiteDatabase的行为,只是在某些方法上做了一些“收紧”。

public interface SupportSQLiteDatabase extends Closeable {
    Cursor query(String query);

    Cursor query(String query, Object[] bindArgs);

    /**
     * Runs the given query on the database.
     * <p>
     * This class allows using type safe sql program bindings while running queries.
     *
     * 最主要的“收紧”体现在这个方法上,以“类型安全”的方式执行查询
     */
    Cursor query(SupportSQLiteQuery query);

    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    Cursor query(SupportSQLiteQuery query, CancellationSignal cancellationSignal);
    
    //其它方法省略
}


//SupportSQLiteDatabase的实现
class FrameworkSQLiteDatabase implements SupportSQLiteDatabase {
    private final SQLiteDatabase mDelegate;

    FrameworkSQLiteDatabase(SQLiteDatabase delegate) {
        mDelegate = delegate;
    }
    
    @Override
    public Cursor query(String query) {
        return query(new SimpleSQLiteQuery(query));
    }

    @Override
    public Cursor query(String query, Object[] bindArgs) {
        return query(new SimpleSQLiteQuery(query, bindArgs));
    }


    @Override
    public Cursor query(final SupportSQLiteQuery supportQuery) {
        //所有的查询都是以一致的方式进行的,使用SQLiteDatabase比较底层的方法 rawQueryWithFactory
        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);
    }

    @Override
    @androidx.annotation.RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    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);
    }

    //其它方法的实现几乎都是简单的委托
}

SupportSQLiteDatabase最主要的“收紧”体现在query方法上。SQLiteDatabase上的query方法的重载有很多个,虽然给了你很多种方式,但是我们依然不喜欢使用,主要原因在于这些方法要传的参数实在是太多了,最少的一个重载方法要传7个参数,多的要传9个参数,这并不是说Android对于SQLite的封装有问题,问题在于如果你不想写查询SQL语句的话,就只能通过这种方式来帮你拼接出来真正的SQL语句,而查询SQL包含的情况实在是太多了,必须需要这么多参数才能明确。
说了这么多,原来是怪我喽,那我写SQL还不行吗?!嗯,其实,也不太行。

大兄弟,不要急,这就叫按下葫芦起了瓢。
想使用查询SQL语句,SQLiteDatabase提供了rawQuery(String sql, String[] selectionArgs)方法,很明显需要我们提供查询SQL语句(可以包含占位符?)以及顶替占位符的参数。这就会有如下问题:

  1. SQL语句的正确性无从保证,只有到运行时才能检验,哪怕你只是手误敲错了一个字符,也只能等到运行时抛出异常才能发现,实在是不友好。
  2. 稍微复杂一点的查询SQL语句几乎都需要绑定一些参数,比如说WHERE语句啥的,如果我们使用字符串拼接的方式,把参数直接拼接到SQL中,不使用selectionArgs,这既丑陋又容易犯错;如果我们使用占位符+selectionArgs,那匹配占位符和selectionArgs也不是件容易的事,有个五六个占位符就够你对照一会了,况且selectionArgs还必须是String的,其它类型的参数还必须转成String类型。

Room解决这些问题的方式就是,使用注解+注解处理器的方式在编译时就验证SQL语句的正确性,尽早发现可能的问题,并且帮我们生成最终的SQL语句,保证占位符与绑定参数的对应。这些之后都会看到,体现在SupportSQLiteDatabase上就是大幅简化查询方法(减少到只有两个种类,4个方法),内部通过SQLiteDatabaserawQueryWithFactory方法去真正地执行查询(其实SQLiteDatabase中那么多查询方法最终调用的也是这个rawQueryWithFactory方法)。

3.2.2 SQLiteOpenHelper的抽象

说一千道一万,你最终打开数据库的方式还得是通过SQLiteOpenHelper。我们想想SQLiteOpenHelper都包含哪些功能。

  1. 创建数据库,onCreate方法。
  2. 升级/降级数据库,onUpgrade/onDowngrade方法。
  3. 打开数据库,onOpen方法。
  4. 获取数据库,getWritableDatabase/getReadableDatabase方法。

我们经常使用的就是这四种。这四种方法可以分为两个阶段,前三种方法属于数据库打开之前的配置阶段,最后一种方法属于数据库已经打开后的使用阶段。Room也以这样的标准对SQLiteOpenHelper的功能进行了拆分,拆分之后各自的职责更加明晰。

public interface SupportSQLiteOpenHelper {
    String getDatabaseName();

    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    void setWriteAheadLoggingEnabled(boolean enabled);

    SupportSQLiteDatabase getWritableDatabase();

    SupportSQLiteDatabase getReadableDatabase();

    void close();
}

这就是抽象后SupportSQLiteOpenHelper,清晰明确了许多。SupportSQLiteOpenHelper是对使用阶段SQLiteOpenHelper职责的抽象。SQLiteOpenHelper配置阶段的职责通过回调的形式来实现:

public interface SupportSQLiteOpenHelper {
    //...
    
    abstract class Callback {
        public final int version;

        public Callback(int version) {
            this.version = version;
        }

        /**
         * Called when the database connection is being configured, to enable features such as
         * write-ahead logging or foreign key support.
         */
        public void onConfigure(SupportSQLiteDatabase db) {

        }

        public abstract void onCreate(SupportSQLiteDatabase db);

        public abstract void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion);

        public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
            throw new SQLiteException("Can't downgrade database from version "
                    + oldVersion + " to " + newVersion);
        }

        public void onOpen(SupportSQLiteDatabase db) {

        }

        public void onCorruption(SupportSQLiteDatabase db) {
            
        }
    }
}

类似于SQLiteOpenHelper,除了onCreateonUpgrade之外,别的方法都不是必须实现的。
SupportSQLiteOpenHelper除了包括以上所说的数据库配置和使用的职责之外,还有一些辅助的功能。为了创建SupportSQLiteOpenHelper定义了一个工厂方法,为了工厂方法方便创建SupportSQLiteOpenHelper定义了一个配置类Configuration,配置类Configuration又采用了Builder模式:

public interface SupportSQLiteOpenHelper {
    //...
    
    class Configuration {
        @NonNull
        public final Context context;
        
        @Nullable
        public final String name;
        
        @NonNull
        public final SupportSQLiteOpenHelper.Callback callback;

        Configuration(@NonNull Context context, @Nullable String name, @NonNull Callback callback) {
            this.context = context;
            this.name = name;
            this.callback = callback;
        }

        public static Builder builder(Context context) {
            return new Builder(context);
        }

        /**
         * Builder class for {@link Configuration}.
         */
        public static class Builder {
            //标准Builder模式,从略
        }
    }

    interface Factory {
        SupportSQLiteOpenHelper create(Configuration configuration);
    }
}

略显繁琐,但都是一些简单设计模式的合理使用。
SupportSQLiteOpenHelper把职责拆分成SupportSQLiteOpenHelper本身以及SupportSQLiteOpenHelper.Callback回调之后,就必须在创建SupportSQLiteOpenHelper的时候传入SupportSQLiteOpenHelper.Callback的实现,所以才有了SupportSQLiteOpenHelper.FactorySupportSQLiteOpenHelper.Configuration。不过不用担心,这些都不用我们自己去实现,当我们使用Room时,注解处理器会帮我们生成对应的实现代码。


SupportSQLiteOpenHelper接口已经定义好了,接下来再来看看其实现类FrameworkSQLiteOpenHelper

class FrameworkSQLiteOpenHelper implements SupportSQLiteOpenHelper {
    //这是一个 SQLiteOpenHelper,具体实现在下方
    private final OpenHelper mDelegate;

    FrameworkSQLiteOpenHelper(Context context, String name, Callback callback) {
        mDelegate = createDelegate(context, name, callback);
    }

    private OpenHelper createDelegate(Context context, String name, Callback callback) {
        final FrameworkSQLiteDatabase[] dbRef = new FrameworkSQLiteDatabase[1];
        return new OpenHelper(context, name, dbRef, callback);
    }

    @Override
    public String getDatabaseName() {
        return mDelegate.getDatabaseName();
    }

    @Override
    @androidx.annotation.RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    public void setWriteAheadLoggingEnabled(boolean enabled) {
        mDelegate.setWriteAheadLoggingEnabled(enabled);
    }

    @Override
    public SupportSQLiteDatabase getWritableDatabase() {
        return mDelegate.getWritableSupportDatabase();
    }

    @Override
    public SupportSQLiteDatabase getReadableDatabase() {
        return mDelegate.getReadableSupportDatabase();
    }

    @Override
    public void close() {
        mDelegate.close();
    }

    //数据库的打开必然还是要通过 SQLiteOpenHelper 的
    static class OpenHelper extends SQLiteOpenHelper {
        /**
         * 我们常用的技巧,使用final对象数组来保存对象引用,进而在匿名内部类中可以引用
         */
        final FrameworkSQLiteDatabase[] mDbRef;
        final Callback mCallback;
        private boolean mMigrated;

        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) {
                            //回调
                            callback.onCorruption(getWrappedDb(dbRef, dbObj));
                        }
                    });
            mCallback = callback;
            mDbRef = dbRef;
        }

        synchronized SupportSQLiteDatabase getWritableSupportDatabase() {
            mMigrated = false;
            SQLiteDatabase db = super.getWritableDatabase();
            if (mMigrated) {
                // there might be a connection w/ stale structure, we should re-open.
                close();
                return getWritableSupportDatabase();
            }
            return getWrappedDb(db);
        }

        synchronized SupportSQLiteDatabase getReadableSupportDatabase() {
            mMigrated = false;
            SQLiteDatabase db = super.getReadableDatabase();
            if (mMigrated) {
                // there might be a connection w/ stale structure, we should re-open.
                close();
                return getReadableSupportDatabase();
            }
            return getWrappedDb(db);
        }

        FrameworkSQLiteDatabase getWrappedDb(SQLiteDatabase sqLiteDatabase) {
            return getWrappedDb(mDbRef, sqLiteDatabase);
        }

        @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);
        }

        @Override
        public void onConfigure(SQLiteDatabase db) {
            //回调
            mCallback.onConfigure(getWrappedDb(db));
        }

        @Override
        public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            mMigrated = true;
            //回调
            mCallback.onDowngrade(getWrappedDb(db), oldVersion, newVersion);
        }

        @Override
        public void onOpen(SQLiteDatabase db) {
            if (!mMigrated) {
                // if we've migrated, we'll re-open the db so we should not call the callback.
                //回调
                mCallback.onOpen(getWrappedDb(db));
            }
        }

        @Override
        public synchronized void close() {
            super.close();
            mDbRef[0] = null;
        }

        //把 SQLiteDatabase包装成 FrameworkSQLiteDatabase
        static FrameworkSQLiteDatabase getWrappedDb(FrameworkSQLiteDatabase[] refHolder,
                SQLiteDatabase sqLiteDatabase) {
            FrameworkSQLiteDatabase dbRef = refHolder[0];
            if (dbRef == null || !dbRef.isDelegate(sqLiteDatabase)) {
                refHolder[0] = new FrameworkSQLiteDatabase(sqLiteDatabase);
            }
            return refHolder[0];
        }
    }
}

无论如何,想打开数据库必须扩展SQLiteOpenHelper类,而扩展SQLiteOpenHelper类又必须覆盖onCreateonUpgrade两个方法。Room通过抽象已经把这部分职责从SupportSQLiteOpenHelper中剥离出来了,所以要以回调类SupportSQLiteOpenHelper.Callback的形式来完成这部分职责。
FrameworkSQLiteOpenHelper是接口SupportSQLiteOpenHelper的实现类,FrameworkSQLiteOpenHelper其实是通过工厂SupportSQLiteOpenHelper.Factory来创建的,而工厂SupportSQLiteOpenHelper.Factory又需要SupportSQLiteOpenHelper.Configuration参数,最终,SupportSQLiteOpenHelper.Configuration是由Room的注解处理器替我们生成出来的,这样一切就关联了起来。

4. 总结

这篇文章主要介绍了Room是如何通过接口的方式来抽象SQLite的,其中有不少接口的定义采用了映射原有系统对应类的方式,然后以简单委托的形式去实现,这并非多此一举。把一切建立在自我构建的抽象系统之上,通过特定的方式建立与原系统的联系(对于SQLite而言就是SQLiteOpenHelper),这是一种非常好的抽象与封装的方式。这种方式隔离了与原系统的直接联系,方便抽象系统的独立发展,也便于测试。
Room有些地方的抽象让人不明所以,但是当你深入理解之后,你会发现这是一种更好的实现方式。比如,对于增删改SQL的抽象,我们平常可能会想着直接使用对应的insert等方法,如果你这么想你必然会对insert等方法进行抽象,而非Room采用的SupportSQLiteStatement的方式;再比如,对于查询SQL,Room使用了参数延迟绑定+rawQueryWithFactory这种更加底层的方式。这说明抽象与封装有时候也取决于我们对于原有系统理解的深度。一看Google工程师们对于SQLite的理解就非常深入,所以我们在很多地方可以看出,Room的抽象使用了更多SQLite较底层的方法,抽象的方式也更加的彻底。
Room的源码分三个层次,最底层是抽象接口层,包含了各种以Support开头的接口;中间层是接口实现层,包含了各种以Framework开头的实现了Support接口的类;最上层才是Room实现层,这一层建立在下面两层的基础之上,包含了注解处理器生成的代码等,实现了数据库的具体逻辑。这篇文章主要介绍了前面两个层次,最后一个层次会在下一篇文章中介绍。

Room抽象的前两个层次
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,050评论 4 370
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,538评论 1 306
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,673评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,622评论 0 218
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,047评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,974评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,129评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,893评论 0 209
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,654评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,828评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,297评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,619评论 3 262
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,326评论 3 243
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,176评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,975评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,118评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,909评论 2 278

推荐阅读更多精彩内容