Android Room框架源码解析(一)

Room框架是Google开发,简化我们数据库操作的代码编写的框架。
在这里插入图片描述

下面我们先展示一个范例。这个需求是班次历史记录,搜索过的线路的集合。一条线路包含出发站和到达站的信息。

TrainLineDbBean是我们的业务bean也就是线路信息。 在room 框架中会添加注解使之成为一张表的抽象。Entity声明了表名为 “TRAIN_LINE”。新建索引名为"line"取{“startStationCode”,“endStationCode”}两个字段,且唯一。ColumnInfo可以命名该变量对应表中的字段名。声明一个id主键字段自增。

@Entity(tableName = "TRAIN_LINE", indices = {@Index(name = "line", value = {"startStationCode","endStationCode"}, unique = true)})
public class TrainLineDbBean {

    @PrimaryKey(autoGenerate = true)
    private int id;
    @ColumnInfo(name = "startStationCode")
    private String startStationCode;

    @ColumnInfo(name = "startStationName")
    private String startStationName;

    @ColumnInfo(name = "startStationType")
    private String startStationType;

    @ColumnInfo(name = "endStationCode")
    private String endStationCode;

    @ColumnInfo(name = "endStationName")
    private String endStationName;

    @ColumnInfo(name = "endStationType")
    private String endStationType;

    public int getId() {
        return id;
    }

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

    public String getStartStationCode() {
        return startStationCode;
    }

    public void setStartStationCode(String startStationCode) {
        this.startStationCode = startStationCode;
    }

    public String getStartStationName() {
        return startStationName;
    }

    public void setStartStationName(String startStationName) {
        this.startStationName = startStationName;
    }

    public String getStartStationType() {
        return startStationType;
    }

    public void setStartStationType(String startStationType) {
        this.startStationType = startStationType;
    }

    public String getEndStationCode() {
        return endStationCode;
    }

    public void setEndStationCode(String endStationCode) {
        this.endStationCode = endStationCode;
    }

    public String getEndStationName() {
        return endStationName;
    }

    public void setEndStationName(String endStationName) {
        this.endStationName = endStationName;
    }

    public String getEndStationType() {
        return endStationType;
    }

    public void setEndStationType(String endStationType) {
        this.endStationType = endStationType;
    }
}

有了表,当然还要有对应的CURD,新建一个接口类用@Dao注解。以后我们知道这个注解会被annotationProcessor生成一个实现类。这个接口主要声明操作,每个方法上都包含对应着增删改查的SQL语句。delete没有应该是框架根据已有的信息帮我们做默认操作。

@Dao
public interface TrainLineDao {


    @Query("SELECT * FROM TRAIN_LINE ORDER BY id DESC LIMIT 5")
    List<TrainLineDbBean> getAll();

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    void insertAll(TrainLineDbBean... trainLineDbBeans);


    @Delete
    void delete(TrainLineDbBean... trainLineDbBeans);


    @Query("delete from TRAIN_LINE")
    int deleteAll();
}

抽象类AppDatabase 继承于RoomDatabase类,这个类建议包装成单例使用。@Database声明了数据库包含的表类。版本号,以及是否导出Schema,就是那些建表语句。Room.databaseBuilder三个参数,Context,RoomDatabase子类,数据库名。allowMainThreadQueries允许主线程操作。

@Database(entities = {TrainCityDbBean.class,TrainLineDbBean.class},version = 1,exportSchema = false)
public abstract class AppDatabase extends RoomDatabase {

    public static final String  DataBase = "DataBase";
    public abstract TrainCityDao trainCityDao();

    public abstract TrainLineDao trainLineDao();


    private static class MYHOLder{

        public static final AppDatabase appDatabase =Room.databaseBuilder(CommonData.getAppContext(),
        AppDatabase.class, DataBase).allowMainThreadQueries().build();

    }


    public static AppDatabase getInstance(){
        return MYHOLder.appDatabase;
    }


}

在build->generated->source->apt->中出现了三个生成类
在这里插入图片描述

三个类就完成一个数据库的创建,表操作。 如再加表,只需要写一个表类加一个Dao,在AppDatabase中配置一下,非常方便,下面我们来看看Google如何封装复杂,提供给我们方便。

首先我们来看RoomDatabase类。里面一个JournalMode日志类型枚举,Builder模式类。MigrationContainer迁移容器类。
在这里插入图片描述

在数据类创建和打开的时候调用

 public abstract static class Callback {

        public void onCreate(@NonNull SupportSQLiteDatabase db) {
        }
        public void onOpen(@NonNull SupportSQLiteDatabase db) {
        }
    }

。。。
Builder
 public Builder<T> addCallback(@NonNull Callback callback) {
            if (mCallbacks == null) {
                mCallbacks = new ArrayList<>();
            }
            mCallbacks.add(callback);
            return this;
        }

数据库升级操作,从startVersion到endVersion,在抽象方法中migrate用户自己实现。

public abstract class Migration {
    public final int startVersion;
    public final int endVersion;

    /**
     * Creates a new migration between {@code startVersion} and {@code endVersion}.
     *
     * @param startVersion The start version of the database.
     * @param endVersion The end version of the database after this migration is applied.
     */
    public Migration(int startVersion, int endVersion) {
        this.startVersion = startVersion;
        this.endVersion = endVersion;
    }

    /**
     * Should run the necessary migrations.
     * <p>
     * This class cannot access any generated Dao in this method.
     * <p>
     * This method is already called inside a transaction and that transaction might actually be a
     * composite transaction of all necessary {@code Migration}s.
     *
     * @param database The database instance
     */
    public abstract void migrate(@NonNull SupportSQLiteDatabase database);
}

Builder有个MigrationContainer对象,通过addMigrations方法,添加版本变迁的操作,操作支持,升级,降级,比如。start(3) ->end(5) , start(5)->end(3). Migration上面分析过就是升降级要做的操作,升级时优先匹配start, end. 即MigrationContainer中包含start(3)->end(5).不会使用start(3)->end(4) & start(4)->end(5).

    public static class Builder<T extends RoomDatabase> {
    	
        private final MigrationContainer mMigrationContainer;

  @NonNull
        public Builder<T> addMigrations(@NonNull  Migration... migrations) {
            if (mMigrationStartAndEndVersions == null) {
                mMigrationStartAndEndVersions = new HashSet<>();
            }
            for (Migration migration: migrations) {
                mMigrationStartAndEndVersions.add(migration.startVersion);
                mMigrationStartAndEndVersions.add(migration.endVersion);
            }

            mMigrationContainer.addMigrations(migrations);
            return this;
        }

MigrationContainer类中的容器是两层SparseArrayCompat嵌套。SparseArrayCompat是以int为key的容器。比如,用start(3) 去寻找,可以找到一个以 3 为startVersion的操作集合,可能有3->4,3->5…

public static class MigrationContainer {
        private SparseArrayCompat<SparseArrayCompat<Migration>> mMigrations =
                new SparseArrayCompat<>();

        /**
         * Adds the given migrations to the list of available migrations. If 2 migrations have the
         * same start-end versions, the latter migration overrides the previous one.
         *
         * @param migrations List of available migrations.
         */
        public void addMigrations(@NonNull Migration... migrations) {
            for (Migration migration : migrations) {
                addMigration(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);
        }

当触发数据库版本变更时,会去查找对应的操作。变更版本相同,返回空操作。findUpMigrationPath这个函数根据start和end的大小来区分升级还是降级。如果是跨级操作,那优先选择一步到位,找到后就退出了while循环,否则会找到逐级变更的操作。

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

        private List<Migration> findUpMigrationPath(List<Migration> result, boolean upgrade,
                int start, int end) {
            final int searchDirection = upgrade ? -1 : 1;
            while (upgrade ? start < end : start > end) {
                SparseArrayCompat<Migration> targetNodes = mMigrations.get(start);
                if (targetNodes == null) {
                    return null;
                }
                // keys are ordered so we can start searching from one end of them.
                final int size = targetNodes.size();
                final int firstIndex;
                final int lastIndex;

                if (upgrade) {
                    firstIndex = size - 1;
                    lastIndex = -1;
                } else {
                    firstIndex = 0;
                    lastIndex = size;
                }
                boolean found = false;
                for (int i = firstIndex; i != lastIndex; i += searchDirection) {
                    final int targetVersion = targetNodes.keyAt(i);
                    final boolean shouldAddToPath;
                    if (upgrade) {
                        shouldAddToPath = targetVersion <= end && targetVersion > start;
                    } else {
                        shouldAddToPath = targetVersion >= end && targetVersion < start;
                    }
                    if (shouldAddToPath) {
                        result.add(targetNodes.valueAt(i));
                        start = targetVersion;
                        found = true;
                        break;
                    }
                }
                if (!found) {
                    return null;
                }
            }
            return result;
        }

再来看看Builder.build方法,判断mContext是否存在,是否有RoomDatabase继承类,是否mMigrationStartAndEndVersions中不包含不需要变更的起始或结束版本。DatabaseConfiguration结合builder的设置,Room.getGeneratedImplementation得到db调用init方法。初始化完成返回。

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

T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX);就是用类名去点转下划线+"_Impl",Class.forName取出注解处理器生成的类。利用反射aClass.newInstance() 得出实例。

private static final String DB_IMPL_SUFFIX = "_Impl";
public class Room {
 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());
        }
    }

AppDatabase_Impl 生成类可以看出来实现了那些我们操作数据库繁杂的代码。

public class AppDatabase_Impl extends AppDatabase {
  private volatile TrainCityDao _trainCityDao;

  private volatile TrainLineDao _trainLineDao;

  @Override
  protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration configuration) {
    final SupportSQLiteOpenHelper.Callback _openCallback = new RoomOpenHelper(configuration, new RoomOpenHelper.Delegate(1) {
      @Override
      public void createAllTables(SupportSQLiteDatabase _db) {
        _db.execSQL("CREATE TABLE IF NOT EXISTS `TRAIN_CITY` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `stationCode` TEXT, `stationName` TEXT, `stationType` TEXT)");
        _db.execSQL("CREATE UNIQUE INDEX `name` ON `TRAIN_CITY` (`stationCode`)");
        _db.execSQL("CREATE TABLE IF NOT EXISTS `TRAIN_LINE` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `startStationCode` TEXT, `startStationName` TEXT, `startStationType` TEXT, `endStationCode` TEXT, `endStationName` TEXT, `endStationType` TEXT)");
        _db.execSQL("CREATE UNIQUE INDEX `line` ON `TRAIN_LINE` (`startStationCode`, `endStationCode`)");
        _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, \"d86bfdaa164f5dff4c0952c306310ca8\")");
      }

      @Override
      public void dropAllTables(SupportSQLiteDatabase _db) {
        _db.execSQL("DROP TABLE IF EXISTS `TRAIN_CITY`");
        _db.execSQL("DROP TABLE IF EXISTS `TRAIN_LINE`");
      }

      @Override
      protected void onCreate(SupportSQLiteDatabase _db) {
        if (mCallbacks != null) {
          for (int _i = 0, _size = mCallbacks.size(); _i < _size; _i++) {
            mCallbacks.get(_i).onCreate(_db);
          }
        }
      }

      @Override
      public void onOpen(SupportSQLiteDatabase _db) {
        mDatabase = _db;
        internalInitInvalidationTracker(_db);
        if (mCallbacks != null) {
          for (int _i = 0, _size = mCallbacks.size(); _i < _size; _i++) {
            mCallbacks.get(_i).onOpen(_db);
          }
        }
      }

      @Override
      protected void validateMigration(SupportSQLiteDatabase _db) {
        final HashMap<String, TableInfo.Column> _columnsTRAINCITY = new HashMap<String, TableInfo.Column>(4);
        _columnsTRAINCITY.put("id", new TableInfo.Column("id", "INTEGER", true, 1));
        _columnsTRAINCITY.put("stationCode", new TableInfo.Column("stationCode", "TEXT", false, 0));
        _columnsTRAINCITY.put("stationName", new TableInfo.Column("stationName", "TEXT", false, 0));
        _columnsTRAINCITY.put("stationType", new TableInfo.Column("stationType", "TEXT", false, 0));
        final HashSet<TableInfo.ForeignKey> _foreignKeysTRAINCITY = new HashSet<TableInfo.ForeignKey>(0);
        final HashSet<TableInfo.Index> _indicesTRAINCITY = new HashSet<TableInfo.Index>(1);
        _indicesTRAINCITY.add(new TableInfo.Index("name", true, Arrays.asList("stationCode")));
        final TableInfo _infoTRAINCITY = new TableInfo("TRAIN_CITY", _columnsTRAINCITY, _foreignKeysTRAINCITY, _indicesTRAINCITY);
        final TableInfo _existingTRAINCITY = TableInfo.read(_db, "TRAIN_CITY");
        if (! _infoTRAINCITY.equals(_existingTRAINCITY)) {
          throw new IllegalStateException("Migration didn't properly handle TRAIN_CITY(com.tts.trip.train.db.TrainCityDbBean).\n"
                  + " Expected:\n" + _infoTRAINCITY + "\n"
                  + " Found:\n" + _existingTRAINCITY);
        }
        final HashMap<String, TableInfo.Column> _columnsTRAINLINE = new HashMap<String, TableInfo.Column>(7);
        _columnsTRAINLINE.put("id", new TableInfo.Column("id", "INTEGER", true, 1));
        _columnsTRAINLINE.put("startStationCode", new TableInfo.Column("startStationCode", "TEXT", false, 0));
        _columnsTRAINLINE.put("startStationName", new TableInfo.Column("startStationName", "TEXT", false, 0));
        _columnsTRAINLINE.put("startStationType", new TableInfo.Column("startStationType", "TEXT", false, 0));
        _columnsTRAINLINE.put("endStationCode", new TableInfo.Column("endStationCode", "TEXT", false, 0));
        _columnsTRAINLINE.put("endStationName", new TableInfo.Column("endStationName", "TEXT", false, 0));
        _columnsTRAINLINE.put("endStationType", new TableInfo.Column("endStationType", "TEXT", false, 0));
        final HashSet<TableInfo.ForeignKey> _foreignKeysTRAINLINE = new HashSet<TableInfo.ForeignKey>(0);
        final HashSet<TableInfo.Index> _indicesTRAINLINE = new HashSet<TableInfo.Index>(1);
        _indicesTRAINLINE.add(new TableInfo.Index("line", true, Arrays.asList("startStationCode","endStationCode")));
        final TableInfo _infoTRAINLINE = new TableInfo("TRAIN_LINE", _columnsTRAINLINE, _foreignKeysTRAINLINE, _indicesTRAINLINE);
        final TableInfo _existingTRAINLINE = TableInfo.read(_db, "TRAIN_LINE");
        if (! _infoTRAINLINE.equals(_existingTRAINLINE)) {
          throw new IllegalStateException("Migration didn't properly handle TRAIN_LINE(com.tts.trip.train.db.TrainLineDbBean).\n"
                  + " Expected:\n" + _infoTRAINLINE + "\n"
                  + " Found:\n" + _existingTRAINLINE);
        }
      }
    }, "d86bfdaa164f5dff4c0952c306310ca8", "7b3ff187eec53ad4a24ee9e6339a0057");
    final SupportSQLiteOpenHelper.Configuration _sqliteConfig = SupportSQLiteOpenHelper.Configuration.builder(configuration.context)
        .name(configuration.name)
        .callback(_openCallback)
        .build();
    final SupportSQLiteOpenHelper _helper = configuration.sqliteOpenHelperFactory.create(_sqliteConfig);
    return _helper;
  }

  @Override
  protected InvalidationTracker createInvalidationTracker() {
    return new InvalidationTracker(this, "TRAIN_CITY","TRAIN_LINE");
  }

  @Override
  public void clearAllTables() {
    super.assertNotMainThread();
    final SupportSQLiteDatabase _db = super.getOpenHelper().getWritableDatabase();
    try {
      super.beginTransaction();
      _db.execSQL("DELETE FROM `TRAIN_CITY`");
      _db.execSQL("DELETE FROM `TRAIN_LINE`");
      super.setTransactionSuccessful();
    } finally {
      super.endTransaction();
      _db.query("PRAGMA wal_checkpoint(FULL)").close();
      if (!_db.inTransaction()) {
        _db.execSQL("VACUUM");
      }
    }
  }

  @Override
  public TrainCityDao trainCityDao() {
    if (_trainCityDao != null) {
      return _trainCityDao;
    } else {
      synchronized(this) {
        if(_trainCityDao == null) {
          _trainCityDao = new TrainCityDao_Impl(this);
        }
        return _trainCityDao;
      }
    }
  }

  @Override
  public TrainLineDao trainLineDao() {
    if (_trainLineDao != null) {
      return _trainLineDao;
    } else {
      synchronized(this) {
        if(_trainLineDao == null) {
          _trainLineDao = new TrainLineDao_Impl(this);
        }
        return _trainLineDao;
      }
    }
  }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值