Jetpack組件room庫介紹

Jetpack介紹

Jetpack是Android官方提供的一套開發組件的結合,它出現的目的是爲了提升咱們的開發效率,消除模板代碼,構建高質量的強大應用。下面是準備分享的計劃:java

Room介紹

Room是Jetpack組件中一個ORM庫,它對Android原生的Sqlite Api作了一層抽象,從而簡化開發者對數據庫的操做。相似於市面上流程的ORM庫GreenDao, Realm等。 Room不只是Android在Jetpack中推薦使用的組件,更重要的是它在性能方面也有必定的優點。android

本文關於Room會介紹主要是3個方面:數據庫

  1. Room的配置
  2. Room的使用
  3. Room的源碼解讀

Room的使用

Room配置

def room_version = "1.1.1"

    implementation "android.arch.persistence.room:runtime:$room_version"
    annotationProcessor "android.arch.persistence.room:compiler:$room_version" // For Kotlin use kapt instead of annotationProcessor

    // optional - RxJava support for Room
    implementation "android.arch.persistence.room:rxjava2:$room_version"


複製代碼

Room使用

在使用數據的時候,須要主要涉及到Room三個部分:json

  1. DataBase: 建立數據庫實例
  2. Entity: 數據庫中表對應的實體
  3. Dao: 操做數據庫的方法

下面一一介紹bash

建立Database實例

@Database(entities = {Video.class, User.class}, version = 1)
//@TypeConverters({Converters.class})
public abstract class AppDatabase extends RoomDatabase {

    private static AppDatabase INSTANCE;
    private static final Object sLock = new Object();

    public abstract UserDao userDao();

    public abstract VideoDao videoDao();


    public static AppDatabase getInstance(Context context) {
        synchronized (sLock) {
            if (INSTANCE == null) {
                INSTANCE =
                        Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "video.db")
                                .allowMainThreadQueries() //room默認數據庫的查詢是不能在主線程中執行的,除非這樣設置
                                .build();
            }
            return INSTANCE;
        }
    }
}


複製代碼

自定義一個抽象類,繼承RoomDatabase。這裏有幾個問題須要注意一下:ide

  1. 這個類是個抽象類,由於Room會給出其真正的實現。
  2. 數據庫實例的生成比較耗時,因此這個地方建議使用單例
  3. @Database註解中 entity是要建立的表。
  4. 須要提供獲取dao的方法。

Entity

entity是表對應的實體,須要在該類上面添加@Entity註解post

@Entity()
public class User {

    @PrimaryKey
    public int uid;

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

    @ColumnInfo(name = "video_id")
    public int videoId;

    @Ignore
    public List<Video>  videos;

}

複製代碼

這裏介紹幾個Entity用到的註解:性能

  1. @Entity

標明是表的映射類,能夠自定義表名稱gradle

@Entity(tableName = "Video")


複製代碼
  1. @PrimaryKey

定義主鍵ui

@PrimaryKey
    public int uid;

複製代碼
  1. @ColumnInfo

自定義數據庫中的字段名稱

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

複製代碼
  1. @ForeignKey

定義外鍵約束

  1. @Ignore

假如咱們類中的某個字段不想在表中建立可使用這個註解忽略。

@Entity(tableName = "Video", foreignKeys = @ForeignKey(entity = User.class,
        parentColumns = "uid", childColumns = "uid"))//能夠指定表名,默認是類名
public class Video {

    @PrimaryKey
    public int vid;

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

    @ColumnInfo(name = "last_name")
    public String length;

    public int uid;

    @Ignore
    public String ignore;

}

複製代碼

Dao

Dao中提供了對數據庫操做的方法。

@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 name LIKE :name ")
    User findByName(String name);

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

    @Delete
    void delete(User user);

    @Transaction //事務查詢
    @Query("SELECT uid, name from User")
    List<UserAllVideos> loadUsers();




}


複製代碼

定義一個接口文件,添加@Dao註解,固然了它的實現也是由Room幫助咱們完成的。各個方法根據業務需求去實現增刪改查,這裏也比較簡單。

場景類

User user = new User();
        user.uid = 1;
        user.name = "love";
    
        AppDatabase.getInstance(this).userDao().insertAll(user);



複製代碼

在數據庫中插入一條User數據

Video video = new Video();
        video.vid = 1;
        video.name = "love";
        video.length = "123";
        video.ignore = "ignore";
        video.uid = 1;
        AppDatabase.getInstance(this).videoDao().insertAll(video);


複製代碼

插入一條Video數據。

數據都插入了,如今咱們獲取一下數據。正常的單個表的數據應該是沒什麼問題的,就不演示了。這裏只介紹一種狀況就是關聯查詢,前面的內容咱們經過外鍵的形式關聯了User和Video。那咱們怎麼作才能在查詢User的時候把關聯的Video也同時查詢出來呢。Room的作法以下:

建立一個UserAllVideos類,注意他不是Entity類,沒有@Entity註解

public class UserAllVideos {

    @Embedded
    public User user;
    @Relation(parentColumn = "uid", entityColumn = "uid")
    public List<Video> videos;
}


複製代碼

這裏涉及到兩個註解解釋一下:

  1. @Embedded

他的做用是將User中的字段都引入

  1. @Relation

他的做用相似於ForeignKey,就是關聯了User和Video,用於咱們的聯合查詢

以後在UserDao中添加一個查詢的方法loadUsers,這樣就能把User中的信息以及其關聯的Video的信息所有的查詢出來。

List<UserAllVideos> userAllVideos = AppDatabase.getInstance(this).userDao().loadUsers();
        Log.d(TAG, userAllVideos.get(0).videos.get(0).name);


複製代碼

TypeConverter

@TypeConverter的做用是轉換,好比Room中不容許對象的引用出現,可是咱們可使用TypeConverter去轉換,好比上面的User Entity中若是要添加List

public class Converters {

    @TypeConverter
    public static List<Video> revert(String str) {
        // 使用Gson方法把json格式的string轉成List
        try {
            return new Gson().fromJson(str, new TypeToken<List<Video>>(){}.getType());
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            return null;
        }

    }

    @TypeConverter
    public static String converter(List<Video> videos) {
        // 使用Gson方法把List轉成json格式的string,便於咱們用的解析
        return new Gson().toJson(videos);
    }
}


複製代碼

在Database中配置TypeConverters

@Database(entities = {Video.class, User.class}, version = 1)
@TypeConverters({Converters.class})
public abstract class AppDatabase extends RoomDatabase {


複製代碼

數據庫升級

建立一個Migration,須要一個起始的版本和升級後的版本。migrage方法中是數據庫升級的具體操做,而後在Database中配置Migration.若是涉及到多個版本的升級,也能夠配置多個Migration

static final Migration MIGRATION_1_2 = new Migration(1, 2) { //由1升級到版本2
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            database.execSQL("CREATE TABLE Topic (id INTEGER , name TEXT )");
        }
    };


Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "video.db")
                                .allowMainThreadQueries() //room默認數據庫的查詢是不能在主線程中執行的,除非這樣設置
                                .addMigrations(MIGRATION_1_2)
                                .build();

複製代碼

集成RxJava2

Room支持RxJava2,返回的結果能夠封裝在Flowable中.

上面就是Room的基本使用,這個文檔只是個入門,涉及到更詳細的使用請再查詢別的文檔。下面簡單的介紹一下Room的源碼。

Room源碼介紹

gradle中依賴room,只能看到部分的源碼,若是看完整的Room源碼請點擊這裏

數據庫實例建立

Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "video.db")
                                .allowMainThreadQueries() //room默認數據庫的查詢是不能在主線程中執行的,除非這樣設置
                                .addMigrations(MIGRATION_1_2)
                                .build();


複製代碼

這裏一看應該就明白,Room採用了建造者模式,添加不少的配置,咱們直接看build方法

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

複製代碼

這個方法的主要做用是建立數據庫實例,而後初始化配置。在前面的使用部分,咱們注意到AppDatabase是個抽象類,那他的真正實現其實在編譯期已經生成了,位置在

build/generared/source/apt/debug/com.xray.sample.room/AppDatabase_Impl

複製代碼

他的實例化則是經過Room.getGeneratedImplementation方法完成,其實採用的是反射。

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

複製代碼

而真正數據庫的建立是在init方法中

@CallSuper
    public void init(@NonNull DatabaseConfiguration configuration) {
        mOpenHelper = createOpenHelper(configuration);
        boolean wal = false;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            wal = configuration.journalMode == JournalMode.WRITE_AHEAD_LOGGING;
            mOpenHelper.setWriteAheadLoggingEnabled(wal);
        }
        mCallbacks = configuration.callbacks;
        mAllowMainThreadQueries = configuration.allowMainThreadQueries;
        mWriteAheadLoggingEnabled = wal;
    }



複製代碼

createOpenHelper這個方法,他的實如今AppDatabase_Impl中,代碼不少,主要就是數據庫的建立,表的建立,升級處理等。

Dao方法調用

仍是在AppDatabase_Impl這個生成類中,咱們找到了兩個Dao實例化的地方,這裏發現Dao的實現類也是在編譯的時候生成的,UserDao_Impl和VideoDao_Impl

@Override
  public UserDao userDao() {
    if (_userDao != null) {
      return _userDao;
    } else {
      synchronized(this) {
        if(_userDao == null) {
          _userDao = new UserDao_Impl(this);
        }
        return _userDao;
      }
    }
  }

  @Override
  public VideoDao videoDao() {
    if (_videoDao != null) {
      return _videoDao;
    } else {
      synchronized(this) {
        if(_videoDao == null) {
          _videoDao = new VideoDao_Impl(this);
        }
        return _videoDao;
      }
    }
  }


複製代碼

關於Dao的實現類你們本身去看吧,都是編譯生成的模板代碼。

模板代碼生成

Room模板代碼的生成是經過註解處理器實現的,具體的源碼在這裏,思路雖然簡單,可是要理清裏面的細節仍是比較麻煩的。

思考

  1. 爲何Room不容許在實體中存在對象對象引用 連接

參考文檔

  1. room源碼
  2. room外鍵
  3. room存儲複雜類型
  4. room
相關文章
相關標籤/搜索