Android Room 使用详解

原文地址:Android Room with a view

Android推荐的架构组件

image.png

Entity: 当使用架构组件时,Entity是描述数据库表的类,这个类通常使用注解。

SQLite database: SQLite是一个数据库,存储数据。为了简单起见,忽略其他的存储工具(如web服务器等)。Room持久性库用于创建和维护数据库。

DAO:即数据访问对象。之前使用 SQLite``OpenHelper 类定义这些内容。当使用DAO时,我们可以调用方法,room做其余的操作。

Room database:SQLite数据库之上的数据库层,负责处理以前使用SQLiteOpenHelper处理的普通任务。Room数据库使用DAO查询SQLite数据库。

Repository: Repository是用于管理多个数据资源,例如数据库,网络等。

ViewModel****: 为UI提供数据。ViewModel作为Repository和UI的通信中心。ViewModel在数据配置更改后仍然存在。

LiveData****: LiveData是可以被观察到的数据持有类。它里面缓存或持有了最新的数据。当数据改变时会通知它的观察者。LiveData是可以感知生命周期的。UI组件只是观察相关数据,不会停止或恢复观察。 LiveData自动管理所有这些,因为它在观察时意识到相关的生命周期状态变化。

下面通过一个官方给的RoomWordSample例子讲解android架构组件中的Room和ViewModel是如何使用的。

RoomWordSample概述

RoomWordSample 的功能很简单,在Room数据库中存储单词列表,并将其显示在RecyclerView中。MainActivity通过RecyclerView展示单词列表。NewWordActivity 用于添加一个单词到数据库中。

image.png

官方源码地址见GitHub

添加依赖

    // Room components
    implementation "android.arch.persistence.room:runtime:$rootProject.roomVersion"
    annotationProcessor "android.arch.persistence.room:compiler:$rootProject.roomVersion"
    androidTestImplementation "android.arch.persistence.room:testing:$rootProject.roomVersion"

    // Lifecycle components
    implementation "android.arch.lifecycle:extensions:$rootProject.archLifecycleVersion"
    annotationProcessor "android.arch.lifecycle:compiler:$rootProject.archLifecycleVersion"

创建Entity

  • @Entity(tableName ="word_table")
    每个@Entity类表示一个数据库表中的实体。可以使用tableName指定表的名字。
  • @PrimaryKey
    每个entity都需要一个主键。
  • @NonNull
    表示参数,成员变量或者方法返回值从不为null
  • @ColumnInfo(name ="word")
    指定数据库表的列名。
  • This sample provides a getWord() method.存储在数据库中的每个字段都需要一个公共“getter”方法。此示例提供了getWord()方法

创建Word类,使用上述注解。

@Entity(tableName = "word_table")
public class Word {
    @PrimaryKey
    @NonNull
    @ColumnInfo(name="word")
    private String mWord;

    public Word(String word) {
        this.mWord = word;
    }

    public String getWord() {
        return this.mWord;
    }
}

创建DAO

DAO,即数据访问接口。可以将SQL查询语句与方法相关联。

DAO必须是接口或抽象类。

默认情况下,所有查询必须在独立的线程。

@Dao
public interface WordDao {
    @Insert
    void insert(Word word);

    @Query("select * from word_table order by word asc")
    List<Word> queryAll();

    @Query("delete from word_table")
    void deleteAll();
}

LiveData 类

LiveData是 lifecycle 库 中的类,主要是用于实时更新数据。类似于观察者模式。

在WordDao类中,将getWordAll的返回值改为LiveData的包装类

@Query("select * from word_table order by word asc")
    LiveData<List<Word>> getWordAll();

添加Room数据库

什么是Room数据库

Room是置于SQLite数据库上面的数据库。之前我们使用 SQLiteOpenHelper类处理一些与数据相关的任务,而Room处理之前使用 SQLiteOpenHelper类处理普通的任务。

  • Room使用DAO查询数据库
  • 默认情况下,为了提高UI性能,Room不允许在主线程中执行查询数据库操作。LiveData在后台线程上异步更新数据。
  • Room 提供SQLite语句编译时检查
  • 自定义的Room类必须是抽象类且必须继承RoomDatabase
  • 通常,在整个APP中,只需要一个Room database实例。

实现Room数据库

@Database(entities = {Word.class}, version=1)
public abstract class WordRoomDatabase extends RoomDatabase {
    private static volatile WordRoomDatabase INSTANCE;

    public static WordRoomDatabase getDatabase(Context context){
        if (INSTANCE == null) {
            synchronized (WordRoomDatabase.class) {
                if (INSTANCE == null) {
                    INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
                            WordRoomDatabase.class, "word_database").build();
                }
            }
        }
        return INSTANCE;
    }

    @NonNull
    @Override
    protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration config) {
        return null;
    }

    @NonNull
    @Override
    protected InvalidationTracker createInvalidationTracker() {
        return null;
    }

    @Override
    public void clearAllTables() {

    }

    public abstract WordDao wordDao();
}

创建Repository

Repository是什么

Repository类用于访问多个数据源。Repository并不是架构组件库的一部分,而是代码解耦和架构比较推荐的方法。Repository类处理数据操作。它为应用程序提供了一个整洁的API。

image.png

为什么用Repository

Repository管理数据的查询线程,同时,可以使用多个后端。在常规情况下,Repository主要实现从服务端拉取数据还是从本地数据库拉取数据的逻辑。

实现Repository

public class WordRepository {
    private WordDao mWordDao;
    private LiveData<List<Word>> mAllWords;

    public WordRepository(Application application) {
        WordRoomDatabase db = WordRoomDatabase.getDatabase(application);
        mWordDao = db.wordDao();
        mAllWords = mWordDao.getWordAll();
    }

    public LiveData<List<Word>> getAllWords() {
        return mAllWords;
    }

    public void insert(Word word) {
        new InsertAsyncTask(mWordDao).execute(word);
    }

    private static class InsertAsyncTask extends AsyncTask<Word, Void, Void> {
        private WordDao mAsyncDao;

        InsertAsyncTask(WordDao wordDao) {
            this.mAsyncDao = wordDao;
        }

        @Override
        protected Void doInBackground(Word... words) {
            mAsyncDao.insert(words[0]);
            return null;
        }
    }
}

创建ViewModel

ViewModel 主要扮演为UI提供数据的角色,同时,在配置更改后可以继续存在。ViewModel可以看做Repository和UI的通信中心。也可以使用ViewModel共享数据。ViewModel是lifecycle library的一部分。

image.png

为什么用ViewModel

ViewModel将UI数据和Activity和Fragment类进行分离,更符合单一职责原则:Activity和Fragment负责展示UI,而ViewModel负责持有并处理UI所需要的所有数据。

在ViewModel 中,使用LiveData更新UI的数据。因为LiveData有以下几个优点:

  • 可以监听数据,只有当数据更改时才会更新UI。
  • 通过ViewModel可以将Repository和UI完全隔离。在ViewModel中不会直接进行数据库调用,这使得代码更方便进行测试。
public class WordViewModel extends AndroidViewModel {
    private LiveData<List<Word>> mAllWord;
    private WordRepository mRepository;
    public WordViewModel(@NonNull Application application) {
        super(application);
        mRepository = new WordRepository(application);
        mAllWord = mRepository.getAllWords();
    }

    public LiveData<List<Word>> getAllWord() {
        return mAllWord;
    }

    public WordRepository getRepository() {
        return mRepository;
    }

    public void insert(Word word) {
        mRepository.insert(word);
    }
}

连接数据

从上文可知,ViewModel是UI与Repository的通信中心。即UI更新数据是通过ViewModel进行的。为了显示当前数据的内容,在ViewModel中添加一个观察者,用以监听LiveData的更改。

在MainActivity的onCreate()方法中创建ViewModel实例,并监听数据库数据的更新。

mWordViewModel = ViewModelProviders.of(this).get(WordViewModel.class);
mWordViewModel.getAllWords().observe(this, new Observer<List<Word>>() {
   @Override
   public void onChanged(@Nullable final List<Word> words) {
       // Update the cached copy of the words in the adapter.
       adapter.setWords(words);
   }
});

总结

通过ViewModel可以将UI和数据层进行分离。DAO接口WordDao会在编译时生成DAO接口的实现类,而WordRoomDatabase也会生成相对应的实现类。其本质还是使用SQLiteOpenHelper来处理数据库操作,只不过开发起来更简单。

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