Jetpack 架構組件 Room 數據庫 ORM MD

Markdown版本筆記 個人GitHub首頁 個人博客 個人微信 個人郵箱
MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina.com

目錄

簡介

官方文檔
查看最新版本
參考文章html

Room是什麼?java

Room是一個持久性數據庫。react

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

Room持久性數據庫提供了SQLite的抽象層,以便在充分利用SQLite的同時容許流暢的數據庫訪問。git

Room 的優勢
在Android領域,目前已有很多優秀的開源的數據庫給你們使用,如SQLite、XUtils、greenDao、Realm,那爲何咱們還要去學習使用這個庫呢?由於Room有下面幾個優勢:github

  • SQL查詢在編譯時就會驗證 - 在編譯時檢查每一個@Query和@Entity等,而且它不只檢查語法問題,還會檢查是否有該表,這就意味着沒有任何運行時錯誤的風險可能會致使應用程序崩潰
  • LiveDatarxjava集成,特別是與LiveData等架構組件配合使用後,能產生讓人驚歎的效果
  • 較少的模板代碼

Room 的組成
Room中有三個主要組件組成:sql

  • Database: 用這個組件建立一個數據庫。註解定義了一系列entities,而且類中提供一系列Dao的抽象方法,也是下層主要鏈接的訪問點。註解的類應該是一個繼承 RoomDatabase 的抽象類。在運行時,你能經過調用Room.databaseBuilder()或者 Room.inMemoryDatabaseBuilder()得到一個實例
  • Entity: 用這個組件建立表,Database類中的entities數組經過引用這些entity類建立數據庫表。每一個entity中的字段都會被持久化到數據庫中,除非用@Ignore註解
  • DAO: 這個組件表明了一個用來操做表增刪改查的dao。Dao 是Room中的主要組件,負責定義訪問數據庫的方法。被註解@Database的類必須包含一個沒有參數的且返回註解爲@Dao的類的抽象方法。在編譯時,Room建立一個這個類的實現。

Entity 相關注解

當一個類被註解爲@Entity而且引用到帶有@Database註解的entities屬性,Room爲這個數據庫引用的entity建立一個數據表。 數據庫

Entity類可以有一個空的構造函數(若是dao類可以訪問每一個持久化的字段)或者一個參數帶有匹配entity中的字段的類型和名稱的構造函數json

Entity的字段必須爲public或提供setter或者getter方法。數組

Ignore:忽略
默認狀況下,Room爲每一個定義在entity中的字段建立一個列。若是一個entity的一些字段不想持久化,可使用@Ignore註解它們

@Ignore Bitmap picture;

PrimaryKey:主鍵

每一個entity必須定義至少一個字段做爲主鍵,即便這裏只有一個字段,仍然須要使用@PrimaryKey註解這個字段。而且,若是想Room動態給entity分配自增主鍵,能夠設置@PrimaryKey的autoGenerate屬性爲true。若是entity有個組合的主鍵,你可使用@Entity註解的primaryKeys屬性:

@PrimaryKey(autoGenerate = true) public int id // 自增主鍵

組合主鍵:

@Entity(primaryKeys = {"firstName", "lastName"})
class User {}

tableName:數據庫的表名
默認狀況下,Room使用類名做爲數據庫的表名。若是但願表有一個不一樣的名稱,設置@Entity註解的tableName屬性,以下所示:

@Entity(tableName = "users")
class User {}

注意: SQLite中的表名是大小寫敏感的。

列名稱:ColumnInfo
Room使用字段名稱做爲列名稱。若是你但願一個列有不一樣的名稱,爲字段增長@ColumnInfo註解

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

indices:索引
數據庫索引能夠加速數據庫查詢,@Entity的indices屬性能夠用於添加索引。在索引或者組合索引中列出你但願包含的列的名稱:

@Entity(indices = {@Index("name"), @Index({"first_name", "last_name"}),})
public class User {}

有時,表中的某個字段或字段組合須要確保惟一性,能夠設置@Entity的@Index註解的unique屬性爲true。

@Entity(indices = {@Index(value = {"first_name", "last_name"}, unique = true)})
public class User {}

foreignKeys:外鍵約束
例如:若是有一個entity叫book,你能夠經過使用@ForeignKey註解定義它和user的關係:

@Entity(foreignKeys = @ForeignKey(entity = User.class, parentColumns = "id", childColumns = "user_id"))
class Book {}

外鍵是十分強大的,由於它們容許你指明當引用的entity被更新後作什麼。
例如,若是相應的user實例被刪除了,你能夠經過包含@ForeignKey註解的onDelete=CASCADE屬性讓SQLite爲這個user刪除全部的書籍。

SQLite處理@Insert(OnConflict=REPLACE)做爲一個REMOVE和REPLACE操做而不是單獨的UPDATE操做。這個替換衝突值的方法可以影響你的外鍵約束。

Embedded:嵌入式字段
有時,但願entity中包含一個具備多個字段的對象做爲字段。在這種狀況下,可使用@Embedded註解去表明一個但願分解成一個表中的次級字段的對象。接着你就能夠像其餘單獨的字段那樣查詢嵌入字段:

@Embedded public Address address;
class Address {
    @ColumnInfo(name = "post_code") public int postCode; //嵌入式字段還能夠包含其餘嵌入式字段
}

若是一個實體具備相同類型的多個內嵌字段,則能夠經過設置前綴屬性(prefix)使每一個列保持唯一。而後將所提供的值添加到嵌入對象中每一個列名的開頭

@Embedded(prefix = "foo_") Coordinates coordinates;

對象引用
SQLite是個關係型數據庫,可以指明兩個對象的關係。大多數ORM庫支持entity對象引用其餘的。Room明確的禁止這樣。更多細節請參考Understand why Room doesn’t allow object references

DAO:增刪改查

Room的三大組件之一Dao,以一種乾淨的方式去訪問數據庫。

Room 不容許在主線程中訪問數據庫。除非在建造器中調用allowMainThreadQueries(),這可能會形成長時間的鎖住UI。

注意,異步查詢API(返回LiveData或者RxJava流的查詢)從這個規則中豁免,由於它們異步的在後臺線程中進行查詢。

Insert

在Dao中建立一個方法而且使用@Insert註解它,Room會爲其生成一個實現,此實現會在單獨事務中插入全部參數到數據庫中:

@Insert
long insertUser(User user); //參數至少有一個,有一個時能夠返回long(表明新插入item的rowId)或不返回

@Insert
long[] insertAll(User... users); //參數也能夠是集合或者數組,此時能夠返回long[]或者List<Long>或不返回

@Insert(onConflict = OnConflictStrategy.REPLACE)
void insertAll(List<User> users);//能夠執行事務操做
long id = db.userDao().insertUser(new User("哎"));
List<Long> ids = db.userDao().insertAll(new User("哎哎" + new Random().nextInt(100)));
db.userDao().insertAll(Arrays.asList(new User("哎啊"), new User("啊哎")));

@Insert方法必須至少有一個參數,若是參數只有一個,它能夠返回一個新插入item的rowId的long值(或不返回)
若是參數是一個集合或數組,它能夠返回long[]或者List<Long>(或不返回)

@Insert,@Update均可以執行事務操做,定義在OnConflictStrategy註解類中:

@Retention(SOURCE)
public @interface OnConflictStrategy {
    int REPLACE = 1; //替換舊數據
    int ROLLBACK = 2; //回滾事務
    int ABORT = 3; //就退出事務
    int FAIL = 4; //使事務失敗 
    int IGNORE = 5; //忽略衝突
}

Delete

@Delete是一個從數據庫中刪除一系列給定參數的entities的慣例方法。它使用主鍵找到要刪除的entities:

@Delete
int delete(User user);//根據主鍵刪除entities,能夠沒有返回值或者返回int,返回int值時表示刪除的數量

@Delete
int deleteAll(List<User> users); //參數至少有一個,也能夠是集合或者數組,參數必須是使用@Entity註解標註的類,且不能爲null
User user = db.userDao().findUserByName("啊哎"); //若是沒找到的話返回null
if (user != null) Log.i("bqt", "【delete數量】" + db.userDao().delete(user)); //不能傳入空,不然崩潰
Log.i("bqt", "【deleteAll數量】" + db.userDao().deleteAll(db.userDao().getAll()));

儘管一般不是必須的,你可以擁有這個方法返回int值指示數據庫中刪除的數量。
沒有提供根據主鍵等方式刪除(包括更新)數據的方式,參數必須是使用@Entity註解標註的類,不能是字符串!

Update

@Update 是更新一系列entities集合、給定參數的慣例方法。它使用query來匹配每一個entity的主鍵:

@Update
int updateAll(User... users);//規則和delete同樣

儘管一般不是必須的,你可以擁有這個方法返回int值指示數據庫中更新的數量。

Query

@Query 是DAO類中使用的主要註解,每一個@Query方法在編譯時(而不是運行時)被校驗,若是查詢包含語法錯誤,或者若是用戶表不存在,Room會報出合適的錯誤消息:

  • 若是僅有一些字段匹配會警告
  • 若是沒有字段匹配會報錯
@Query("SELECT * FROM table_user")
List<User> getAll();//返回值爲集合或數組的話,返回找到的全部數據,沒找到時返回長度爲0的集合或數組

查詢時傳入參數
若是你須要傳入參數到查詢語句中去過濾操做,你可使用方法參數

@Query("SELECT * FROM user WHERE age > :minAge") User[] loadAllUsersOlderThan(int minAge);
@Query("SELECT * FROM user WHERE uid IN (:userIds)") List<User> loadAllByIds(int[] userIds);

當這個查詢在編譯期被處理,Room會匹配:minAge綁定的方法參數。Room經過使用參數名稱執行匹配,若是沒有匹配到,在你的app編譯期將會報錯。

查詢時能夠傳入多個參數或者屢次引用同一個參數。

@Query("SELECT * FROM user WHERE first_name LIKE :first AND last_name LIKE :last LIMIT 1")
User findByName(String first, String last);

返回列中的子集
多數時候,咱們僅須要獲取一個entity中的部分字段。例如,你的UI可能只展現user第一個和最後一個名稱,而不是全部關於用戶的細節。經過獲取展現在UI的有效數據列可使查詢完成的更快

只要查詢中列結果集可以被映射到返回的對象中,Room容許你返回任何java對象。例如,經過建立以下POJO拿取用戶的姓和名:

@Query("SELECT first_name, last_name FROM user") List<NameTuple> loadFullName();
public class NameTuple {
    @ColumnInfo(name="first_name") public String firstName;
    @ColumnInfo(name="last_name") public String lastName;
}

Room理解查詢返回first_name和last_name的列值被映射到NameTuple類中。所以,Room可以生成合適的代碼。若是查詢返回太多columns,或者一個列不存在,Room將會報警。

返回LiveData對象
使用返回值類型爲LiveData實現數據庫更新時ui數據自動更新:

db.userDao().loadUsersByName2("%哎%") //能夠在主線程調用
    .observe(this, users -> tvTitle.setText("【最新查詢結果】" + new Gson().toJson(users)));

返回RxJava對象
Room也能返回RxJava2的Publisher和Flowable對象,需添加android.arch.persistence.room:rxjava2依賴:

@Query("SELECT * from user where uid = :uid") Flowable<User> findUserById(int uid);

直接遊標訪問
若是你的應用邏輯直接訪問返回的行,你能夠從你的查詢當中返回一個Cursor對象:

@Query("SELECT * FROM user WHERE uid > :uid LIMIT 5") Cursor biggerId(int uid);//不推薦

注意:很是不建議使用Cursor API 由於它不能保證行是否存在或者行包含什麼值。
使用這個功能僅僅是由於你已經有一個返回cursor的代碼,而且你不能輕易的重構。

多表查詢
在SQLite數據庫中,咱們能夠指定對象之間的關係,所以咱們能夠將一個或多個對象與一個或多個其餘對象綁定。這就是所謂的一對多和多對多的關係。

一些查詢可能訪問多個表去查詢結果,Room容許你寫任何查詢,因此你也能鏈接表。

以下代碼段展現如何執行一個根據借書人姓名模糊查詢借的書的相關信息。

@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")
List<Book> findBooksBorrowedByNameSync(String userName);

從這些查詢當中也能返回POJOs,例如,能夠寫一個POJO去裝載userName和petName,以下:

@Query("SELECT user.name AS userName, pet.name AS petName FROM user, pet WHERE user.id = pet.user_id")
LiveData<List<UserPet>> loadUserAndPetNames();
class UserPet {
   public String userName;
   public String petName;
}

其餘

類型轉換

Room爲原始類型和可選的裝箱類型提供嵌入支持。然而,有時你可能使用一個單獨存入數據庫的自定義數據類型。爲了添加這種類型的支持,你能夠提供一個TypeConverter把自定義類轉化爲一個Room可以持久化的已知類型。

例如:若是咱們想持久化Date的實例,咱們能夠寫以下TypeConverter在數據庫中去存儲相等的Unix時間戳:

public Date birthday;
public class Converters {
    @TypeConverter
    public static Date fromTimestamp(Long value) {
        return value == null ? null : new Date(value);
    }

    @TypeConverter
    public static Long dateToTimestamp(Date date) {
        return date == null ? null : date.getTime();
    }
}

接着,你增長@TypeConverters註解到RoomDatabase子類:

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

使用這些轉換器後,你就能夠像使用的原始類型同樣使用自定義類型了:

@Query("SELECT * FROM user WHERE birthday BETWEEN :from AND :to")
List<User> findUsersBornBetweenDates(Date from, Date to);

注意:除了將 @TypeConverters 放在RoomDatabase裏面影響全局外,還能夠將 @TypeConverter 限制在不一樣的範圍內,包含單獨的entity、Dao和Dao中的 methods,具體查看官方文檔。

模糊匹配

參考

一般,咱們使用SQL語句模糊查詢時是這樣的:

select * from user where name like '%包_天%'; //%表明任意個字符,_表明一個字符

可是在Dao中不能簡單地這麼寫,任你各類拼接啊、轉義啊,都不能把那個%給它弄進去。

這裏不得不說一個Room的一個好處,就是你在寫sql語句的時候它會檢查,格式不對,裏面會報紅線,根本就編譯不經過。

皇天不負有心人,最終在Stack Overflow上面搜了一下,找到了最終答案,應該是這樣的

@Query("SELECT * from user where last_name LIKE '%' || :name || '%'")
LiveData<List<User>> loadUsersByName(String name);

原來是用雙豎槓去拼接,而不是加號,欲哭無淚啊。而後我就去搜了一下sql語句拼接,找到這樣一段話:

在SQL中的SELECT語句中,可以使用一個特殊的操做符來拼接兩個列。根據你所使用的DBMS,此操做符可用加號(+)或兩個豎槓(||)表示。
在MySQL和MariaDB中,必須使用特殊的函數。
Access和SQL Server使用 + 號。
SQLite、DB二、Oracle、PostgreSQL、Open Office Base使用 ||。

其實除了我上面用雙豎槓拼接的方式外,還有一種方法,就是在傳參的時候,把%_%給拼接好,這種方式我也是測試了一下,也是能夠的,由於最終都是轉換成sql的標準查詢語句。

db.userDao().loadUsersByName("%包_天%")

並且,我感受這種方式寫起來更優雅,應該也是Google變相推薦的方式。

總結

  • 方式一,在傳給Dao方法以前自行拼接:如db.userDao().loadUsersByName("%包_天%")
  • 方式二:在Dao方法中的方法參數先後使用||拼接,如:@Query("SELECT * from user where name LIKE '_%' || :name || '%'")

輸出模式

在編譯時,將數據庫的模式信息導出到JSON文件中,這樣可有利於咱們更好的調試和排錯(DataBase的exportSchema = true)

module中的build.gradle:

android {
    defaultConfig {
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]//room的數據庫概要、記錄
            }
        }
    }
}

配置完畢以後,咱們在構建項目以後會在對應路徑生成schemas文件夾。
文件夾內的1.json等文件分別是數據庫版本一、二、3等版本對應的概要、記錄。

數據庫升級

若是我們刪除了entity中的一個字段,運行程序後,就會出現下面這個問題。

java.lang.IllegalStateException: Room cannot verify the data integrity. Looks like you've changed schema but forgot to update the version number. You can simply fix this by increasing the version number.

大體的意思是:你修改了數據庫,可是沒有升級數據庫的版本

這時候我們根據錯誤提示增長版本號,但沒有提供migration,APP同樣會crash。

java.lang.IllegalStateException: A migration from 1 to 2 was required but not found. Please provide the necessary Migration path via RoomDatabase.Builder.addMigration(Migration ...) or allow for destructive migrations via one of the RoomDatabase.Builder.fallbackToDestructiveMigration* methods.

大體的意思是:讓咱們添加一個addMigration或者調用fallbackToDestructiveMigration完成遷移

接下來,我們增長版本號並使用fallbackToDestructiveMigration(),雖然可使用了,可是咱們會發現,數據庫的內容都被咱們清空了,顯然這種方式是不友好的。若是不想清空數據庫,就須要提供一個實現了的migration。

Room容許使用Migration類保留用戶數據。每一個Migration類在運行時指明一個開始版本和一個結束版本,Room執行每一個Migration類的migrate()方法,使用正確的順序去遷移數據庫到一個最近版本。
若是不提供必需的Migrations類,Room會重建數據庫,這意味着你將丟失數據庫中的全部數據。

正常升級

好比我們要在Department中添加phoneNum

public class Department {
    @PrimaryKey(autoGenerate = true) int id;
    String dept;
    @ColumnInfo(name = "emp_id") int empId;
    @ColumnInfo(name = "phone_num") String phoneNum;
}

一、把版本號自增

@Database(entities = {Department.class, Company.class}, version = 2, exportSchema = false)
@TypeConverters(DateConverter.class)
public abstract class DepartmentDatabase extends RoomDatabase {}

二、添加一個version:1->2的migration

static final Migration MIGRATION_1_2 = new Migration(1, 2) {
    @Override
    public void migrate(SupportSQLiteDatabase database) {
        database.execSQL("ALTER TABLE department ADD COLUMN phone_num TEXT");
    }
};

三、把migration 添加到 databaseBuilder

Room.databaseBuilder(context, DepartmentDatabase.class, DB_NAME)
    .addMigrations(MIGRATION_1_2)
    .build();

再次運行APP就會發現,數據庫表更新了,而且舊數據也保留了。

跳躍升級

在平時的開發時,數據庫的升級並不老是循序漸進的從 version: 1->2,2->3,3->4。老是會出現 version:1->3,或 2->4 的狀況。這時候咱們又該怎麼辦呢?

方法很簡單。當用戶升級 APP 時,咱們替用戶升級數據庫版本。

具體作法:
version:1->2

database.execSQL("CREATE TABLE `Fruit` (`id` INTEGER, `name` TEXT, PRIMARY KEY(`id`))"); //建立表

version:2->3

database.execSQL("ALTER TABLE Book ADD COLUMN pub_year INTEGER"); //添加字段

version:3->4

//字段類型修改
db.execSQL("CREATE TABLE db_new (_id TEXT, _name TEXT, _phone INTEGER, PRIMARY KEY(_id))");  //建立表
db.execSQL("INSERT INTO db_new (_id, _name, _phone) SELECT _id, _name, _phone FROM db_old"); //複製表
db.execSQL("DROP TABLE db_old");  //刪除表
db.execSQL("ALTER TABLE db_new RENAME TO students");  //修改表名稱

而後把 migration 添加到 Room database builder:

Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "database-name")
    .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4)
    .build();

這樣,要是用戶剛下載的 APP,目前咱們定義了migrations:version 1 到 2, version 2 到 3, version 3 到 4, 因此 Room 會一個接一個的觸發全部 migration。

其實 Room 能夠處理大於 1 的版本增量:咱們能夠一次性定義一個從 1 到 4 的 migration,以提高遷移的速度。

.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_1_4)

使用案例

添加依賴

implementation "android.arch.persistence.room:runtime:1.1.1"  //Room
annotationProcessor "android.arch.persistence.room:compiler:1.1.1"
testImplementation "android.arch.persistence.room:testing:1.1.1" //Test helpers for Room
implementation "android.arch.persistence.room:rxjava2:1.1.1" //爲room添加rxjava支持庫
implementation "android.arch.lifecycle:reactivestreams:1.1.1" //和LiveData一塊兒使用

定義Database

@Database(entities = {User.class}, version = 1)
@TypeConverters({MyConverters.class})
public abstract class AppDatabase extends RoomDatabase {
    public abstract UserDao userDao(); //沒有參數的抽象方法,返回值所表明的類必須用@Dao註解
}

Build後會自動生成的AppDatabase的實現類:

定義實體

@Entity(tableName = "table_user") //自定義表名稱
public class User {
    @PrimaryKey(autoGenerate = true) public int uid;//自增主鍵
    @ColumnInfo(name = "user_name") public String name; //自定義列名稱
    public Date birthday; //類型轉換
    @Ignore public String tips = "忽略的字段"; //忽略
    //省略了get、set、構造方法等代碼
}

定義Dao

@Dao
public interface UserDao {

    @Insert
    long insertUser(User user); //參數至少有一個,有一個時能夠返回long(表明新插入item的rowId)或不返回

    @Insert
    List<Long> insertAll(User... users); //參數也能夠是集合或者數組,此時能夠返回long[]或者List<Long>或不返回

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    void insertAll(List<User> users);//能夠執行事務操做

    @Delete
    int delete(User user);//根據主鍵刪除entities,能夠沒有返回值或者返回int,返回int值時表示刪除的數量

    @Delete
    int deleteAll(List<User> users); //參數至少有一個,也能夠是集合或者數組,參數必須是使用@Entity註解標註的類,且不能爲null

    @Query("SELECT * FROM table_user")
    List<User> getAll();//返回值爲集合或數組的話,返回找到的全部數據,沒找到時返回長度爲0的集合或數組

    @Query("SELECT * FROM table_user WHERE user_name LIKE :name LIMIT 1")
    User findUserByName(String name); //返回值爲User的話,只返回找到的第一條數據,沒找到時返回null

    @Query("SELECT * FROM table_user WHERE uid IN (:userIds)")
    User[] loadAllByIds(int[] userIds); //沒找到時返回長度爲0的集合或數組

    @Query("SELECT * FROM table_user WHERE uid > :uid LIMIT 5")
    Cursor biggerId(int uid);//不推薦

    @Query("SELECT * from table_user where uid = :uid")
    Flowable<User> findUserById(int uid); //和rxjava結合使用

    @Query("SELECT * from table_user where user_name LIKE :name ")
    LiveData<List<User>> loadUsersByName(String name); //和LiveData結合使用

    @Query("SELECT * from table_user where user_name LIKE '_%' || :name || '%'")
    LiveData<List<User>> loadUsers(String name);//模糊搜索
}

Build後自動生成的UserDao的實現類:

代碼中使用

public class MainActivity extends FragmentActivity {
    private TextView tvTitle, tvWeixin, tvFriend, tvContact, tvSetting;
    private String[] titles = {"微信", "通信錄", "發現", "我"};
    private MyViewModel mModel;
    private AppDatabase db;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        mModel = ViewModelProviders.of(this).get(MyViewModel.class);
        db = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "dbname").build();
        db.userDao().loadUsersByName("%哎_%") //返回LiveData<List<User>>,實際數據庫操做工做在子線程
            .observe(this, users -> {
                Log.i("bqt", "【是否在主線程中】" + (Looper.getMainLooper() == Looper.myLooper())); //true
                String newUsers = GsonUtils.toJson(users);
                Log.i("bqt", "【數據庫中查詢到的數據發生變化】" + newUsers);
                mModel.getMutableLiveData().setValue(newUsers); //這裏是主線程,能夠直接使用setValue
            });
    }

    private void initView() {
        tvWeixin = findViewById(R.id.tv_tab_bottom_weixin);
        tvFriend = findViewById(R.id.tv_tab_bottom_friend);
        tvContact = findViewById(R.id.tv_tab_bottom_contact);
        tvSetting = findViewById(R.id.tv_tab_bottom_setting);

        tvWeixin.setTag(0);
        tvFriend.setTag(1);
        tvContact.setTag(2);
        tvSetting.setTag(3);

        tvWeixin.setOnClickListener(v -> onClickTab((Integer) v.getTag()));
        tvFriend.setOnClickListener(v -> onClickTab((Integer) v.getTag()));
        tvContact.setOnClickListener(v -> onClickTab((Integer) v.getTag()));
        tvSetting.setOnClickListener(v -> onClickTab((Integer) v.getTag()));

        tvTitle = findViewById(R.id.tv_title);
        tvTitle.setOnClickListener(v -> {
            new Thread(() -> {
                List<User> allUsers = db.userDao().getAll();//必須在子線程訪問數據庫
                mModel.getMutableLiveData().postValue("所有數據\n" + GsonUtils.toJson(allUsers)); //在子線程必須用postValue
            }).start();
            tvTitle.setBackgroundColor(0xFF000000 + new Random().nextInt(0xFFFFFF));
        });
        getSupportFragmentManager().beginTransaction().add(R.id.id_container, MyFragment.newInstance("MyFragment")).commit();
    }

    private void onClickTab(int position) {
        tvWeixin.setSelected(position == 0);
        tvFriend.setSelected(position == 1);
        tvContact.setSelected(position == 2);
        tvSetting.setSelected(position == 3);
        tvTitle.setText(titles[position]);

        switch (position) {
            case 0:
                new Thread(() -> {
                    long id = db.userDao().insertUser(new User("哎"));
                    List<Long> ids = db.userDao().insertAll(new User("哎哎" + new Random().nextInt(100)));
                    db.userDao().insertAll(Arrays.asList(new User("哎啊"), new User("啊哎")));
                    Log.i("bqt", "【id】" + id + "【ids】" + ids);
                }).start(); //不能在主線程訪問數據庫,不然報IllegalStateException
                break;
            case 1:
                new Thread(() -> {
                    User user = db.userDao().findUserByName("啊哎"); //若是沒找到的話返回null
                    if (user != null) Log.i("bqt", "【delete數量】" + db.userDao().delete(user)); //不能傳入空,不然崩潰
                    User[] users = db.userDao().loadAllByIds(new int[]{1, 2, 3, 4, 5, 6}); //沒找到時返回長度爲0的集合或數組
                    Log.i("bqt", "【deleteAll數量】" + db.userDao().deleteAll(Arrays.asList(users)));
                }).start();
                break;
            case 2:
                new Thread(() -> {
                    User user = db.userDao().findUserByName("哎%"); //若是沒找到的話返回null
                    user.name = "更新name";
                    Log.i("bqt", "【updateAll數量】" + db.userDao().updateAll(user));
                }).start();
                break;
            case 3:
                db.userDao().findUserById(1) //返回Flowable<User>
                    .map(GsonUtils::toJson) //數組類型轉換
                    .subscribeOn(Schedulers.io()) //必須切換到子線程中訪問數據庫
                    //.observeOn(AndroidSchedulers.mainThread()) //能夠在子線程中使用postValue,也能夠切換到主線程中使用setValue
                    .subscribe(s -> mModel.getMutableLiveData().postValue(s));
            default:
                break;
        }
    }
}

2019-3-29

相關文章
相關標籤/搜索