第五届世界互联网大会昨日开幕,来自76个国家的1500余位嘉宾出席大会。腾讯公司董事会主席兼首席执行官马化腾在大会开幕式演讲中表示,全球产业都在进行数字化,在此期间机遇挑战并存,产业互联网机会巨大。
作者简介
本篇来自 walker不抽烟的投稿,给大家介绍一下 Android JetPack架构相关知识。一起来看看!希望大家喜欢。
walker不抽烟的博客地址:
https://blog.youkuaiyun.com/itismelzp
前言
本文翻译自:Android Room with a View - Java,地址如下:
https://codelabs.developers.google.com/codelabs/android-room-with-a-view/#0
架构组件的目的是提供对应用程序体系结构的指导,并为诸如生命周期管理和数据持久化等常见任务提供开发库。
架构组件帮你构造一个鲁棒、易测试、可维护和少模板代码的应用。
架构组件是什么?
为了介绍相关术语,这里有简短的介绍一下各架构组件以及它们之前如何协作。注意这个代码库包含一部分架构组件,它们是:LiveData、ViewModel和Room。每个组件会在使用的时候做解释。下图是基本的架构形式。
- Entity: 当注解的类,用于描述数据库表。
- SQLite database: SQLite数据库。
- DAO: 数据访问对象。SQL查询到方法的映射。
- Room database: 在SQLite数据库之上的数据库层。
- Repository: 数据仓库,用于管理数据源。
- ViewModel: 提供数据给UI。是Repository与UI的连接中心。
你要构建什么
这个Demo用于在Room中存储words(单词)列表,并显示在RecyclerView中。这是个简单的示例,但也足以为它来作为开发应用的模板。
- Demo的功能:
- 获取与保存"单词";
- 单词显示在MainActivity的RecyclerView中;
- 通过悬浮按钮调起另一个activity,用于输入单词。
RoomWordSample架构预览
下图展示了应用的各个部分。除了SQLite database,其他部分都用在自己创建的类中封装。
你会学到什么
学会如何使用架构组件库和生命周期库设计和构建应用程序。
这里有许多步骤去使用架构组件和推荐的框架。最重要的是学会模型创建的作用、理解各部分组合与数据流向。通过这个Demo,你不单只是简单的复制和粘贴本文的代码,还要理解其内部原理。
你需要掌握什么
- Android Studio 3.0或更高版本的使用。
- 一台Android设备或模拟器。
你必须熟悉Java编程,面向对象设计,Android开发基础。尤其:
- RecyclerView 及其适配器adapters
- SQLite数据库及SQLite查询语言
- 线程与AsyncTask
- 了解一些数据与UI分离的构架概念,如MVP、MVC
本Demo着重于Android架构组件,非主要代码可行自行复制与粘贴。
Android Studio的优势
创建应用
打开Android Studio创建应用:
- 新建应用RoomWordSample,目标sdk为26+
- 不选include Kotlin support和include C++ support
- 下一步,选Phone & Tablet,minimum SDK选API 26
- 下一步,选择Basic Activity
- 下一步,完成。
更新gradle文件
添加组件库到gradle。在Module:app的build.gradle中dependencies末尾加入:
// 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"
中Project:RoomWordSample的build.gradle中加入版本信息:
ext{
roomVersion= '1.1.1'
archLifecycleVersion = '1.1.1'
}
创建实体
本demo的数据是“单词”,因此首先创建一个Word类,并为其创建构造函数与必要的get方法。这样Room才可以实例化对象。
下面是Word类:
publicclassWord{
privateString mWord;
publicWord( @NonNullString word) { this.mWord = word;}
publicString getWord(){ returnthis.mWord;}
}
为了使Word类对Room库有意义,我们需要为它加注解。注解用了将实体与数据库相关联,Room根据相应的注解信息去生成对应的代码。
- @Entity(tableName = "word_table") 每一个@Entity类代表数据库中的一张表。tableName为生成表的表名。
- @PrimaryKey 每个实体需要一个主键。
- @NonNull 表示参数、字段或返回值不能为null。
- @ColumnInfo(name = "word") 指定与成员变量对应的列名。
- 为一个字段需要是public的或提供get方法。
添加注解的Word类:
@Entity(tableName = "word_table")
publicclassWord{
@PrimaryKey
@NonNull
@ColumnInfo(name = "word")
privateString mWord;
publicWord(String word) { this.mWord = word;}
publicString getWord(){ returnthis.mWord;}
}
创建DAO(数据访问对象)
- 什么是DAO
DAO即数据访问对象,你可以指定SQL查询语句,并将它与方法关联起来。编译器会对常规SQL注解进行编译检查,如@Insert。
DAO对象必须是个接口或抽象类。
默认情况下,所有的查询必须在单独线程中执行。
Room将通过DAO对象去创建相应的接口。
- DAO的写法
DAO是代码的基础,它用于提供word的增、删、改、查。
- 创建一个名为WordDao的接口。
- 为WordDao添加@Dao注解
- 声明一个插入方法void insert(Word word);
- 为上述方法添加@Insert注解,并且不需要为其提供SQL语句!(同样的用法还有@Delete and @Update)
- 声明方法void deleteAll();
- 这里没有方便的注解可以用于删除多个实体,因此需要用@Query注解
- 还需要为@Query注解提供SQL语句@Query("DELETE FROM word_table")
- 创建方法List<Word> getAllWords();
- 为其添加注解与SQL@Query("SELECT * from word_table ORDER BY word ASC")
下面是其完整的代码:
@Dao
publicinterfaceWordDao{
@Insert
voidinsert(Word word);
@Query( "DELETE FROM word_table")
voiddeleteAll();
@Query( "SELECT * from word_table ORDER BY word ASC")
List<Word> getAllWords();
}
LiveData类
当数据被改变后,通常你需要作一些操作,例如将更新的数据展示在UI上。这就意味着你必须去观察这些数据,以便于当数据改变时你能做出反应。根据数据不同的存储方式,这可能会很棘手。观察贯串多个组件中数据的变化,你必须要编写一个显式、严格依赖的调用链。这使得测试和调试变得非常困难。
LiveData是 lifecycle library 中,用于数据观察的类,可用于解决上述难题。在你的方法中使用LiveData为返回值。这样Room将会为你生成所有必须的代码,当数据库更新时,自动去更新LiveData。
使用LiveData的目的是为了管理数据的更新。但是LiveData类并没有提供公有的更新数据的方法。我们应当使用MutableLiveData,它有两个公有方法(setValue(T)、postValue(T))用于存储数据。通常,MutableLiveData是在ViewModel中使用,然后ViewModel只向观察者暴露不可变的LiveData对象。
在WordDao中,改变getAllWords()方法的返回值:
@Query(" SELECT* fromword_table ORDERBYword ASC")
LiveData<List<Word>> getAllWords();
后面我们会在MainActivity的onCreate()方法中创建一个Observer对象,并覆盖其onChanged()方法。当LiveData改变时,观察者会被通知然后onChanged()会被回调。这时你可以更新适配器中的缓存数据,然后在适配器中更新UI。
添加Room数据库
- 什么是Room数据库?
Room是在SQLite之上的数据库层。Room用于处理我们曾经用SQLiteOpenHelper来处理任务。
- Room通过DAO向数据库发送查询
- 默认情况下,为了避免降低UI线程的性能,Room不允许在主线程中执行数据库操作
- Room提供了编译时的SQL语句检查
- 创建的Room类必须是抽象的,并且继承RoomDatabase
- 通常,在整体应用中只需要一个Room数据库实例,即单例。
- 实现Room数据库
- 创建一个public abstract类WordRoomDatabase,并继承RoomDatabase。即public abstract class WordRoomDatabase extends RoomDatabase {}
- 标注其为一个Room数据库,@Database(entities = {Word.class}, version = 1),声明其在数据库中的实体,并指定版本号。实体可以声明多个,声明的实体将在数据库中创建对应的表。
- 定义使用数据库的DAO。给每一个@Dao提供get方法。public abstract WordDao wordDao();
完整代码如下:
@Database(entities = {Word.class}, version = 1)
publicabstractclassWordRoomDatabaseextendsRoomDatabase{
publicabstractWordDao wordDao();
}
- 使WordRoomDatabase作为单例。
privatestaticvolatileWordRoomDatabase INSTANCE;
staticWordRoomDatabase getDatabase(finalContext context){
if(INSTANCE == null) {
synchronized(WordRoomDatabase.class) {
if(INSTANCE == null) {
// Create database here
}
}
}
returnINSTANCE;
}
- 实例化RoomDatabase对象:使用Room的databaseBuilder,从WordRoomDatabase类的应用上下文context中创建RoomDatabase对象,并将数据库全名为"word_database"。
// Createdatabasehere
INSTANCE= Room.databaseBuilder(context.getApplicationContext(),
WordRoomDatabase.class, "word_database")
.build();
下面是完整代码:
@Database(entities = {Word.class}, version = 1)
publicabstractclassWordRoomDatabaseextendsRoomDatabase{
publicabstractWordDao wordDao();
privatestaticvolatileWordRoomDatabase INSTANCE;
staticWordRoomDatabase getDatabase(finalContext context){
if(INSTANCE == null) {
synchronized(WordRoomDatabase.class) {
if(INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
WordRoomDatabase.class, "word_database")
.build();
}
}
}
returnINSTANCE;
}
}
创建Repository(数据仓库)
- 什么是Repository?
Repository是一个可访问多数据源的类。它并非构架组件库中的一部分,但它是代码分离和体系结构的最佳实践建议。Repository用于处理数据操作,它为应用提供数据访问接口。
- 为什么要使用Repository?
Repository管理查询线程,并允许您使用多个后端。在最常见的示例中,Repository实现了决定是从网络获取数据还是从本地缓存中获取结果的逻辑。
- Repository的实现
- 创建一个公共类WordRepository
- 添加两个成员变量
privateWordDao mWordDao;
privateLiveData< List<Word>> mAllWords;
- 添加一个构造函数,该构造函数获取数据库的句柄并初始化成员变量。
WordRepository(Application application) {
WordRoomDatabase db = WordRoomDatabase.getDatabase(application);
mWordDao = db.wordDao();
mAllWords = mWordDao.getAllWords();
}
- 为getAllWords()添加一个包装器。Room在单独的线程上执行所有查询。观察到LiveData数据更改时,将通知观察者。
LiveData< List<Word>> getAllWords() {
returnmAllWords;
}
- 为insert()方法添加一个包装器。使用AsyncTask来执行,确保其是在非UI线程中执行。
publicvoidinsert(Word word){
newInsertAsyncTask(mWordDao).execute(word);
}
- InsertAsyncTask的实现
privatestaticclassinsertAsyncTaskextendsAsyncTask<Word, Void, Void> {
privateWordDao mAsyncTaskDao;
insertAsyncTask(WordDao dao) {
mAsyncTaskDao = dao;
}
@Override
protectedVoid doInBackground(finalWord... params){
mAsyncTaskDao.insert(params[ 0]);
returnnull;
}
}
下面是完整代码:
publicclassWordRepository{
privateWordDao mWordDao;
privateLiveData<List<Word>> mAllWords;
WordRepository(Application application) {
WordRoomDatabase db = WordRoomDatabase.getDatabase(application);
mWordDao = db.wordDao();
mAllWords = mWordDao.getAllWords();
}
LiveData<List<Word>> getAllWords() {
returnmAllWords;
}
publicvoidinsert(Word word){
newinsertAsyncTask(mWordDao).execute(word);
}
privatestaticclassinsertAsyncTaskextendsAsyncTask<Word, Void, Void> {
privateWordDao mAsyncTaskDao;
insertAsyncTask(WordDao dao) {
mAsyncTaskDao = dao;
}
@Override
protectedVoid doInBackground(finalWord... params){
mAsyncTaskDao.insert(params[ 0]);
returnnull;
}
}
}
创建ViewModel
- 什么是ViewModel?
ViewModel的作用是向UI提供数据,并保存配置更改。ViewModel充当Repository 和UI之间的通信中心。还可以使用ViewModel在fragments之间共享数据。ViewModel是 lifecycle library 库的一部分。
- 为什么使用ViewModel?
当配置被更改时,ViewModel以一种有生命周期感知的方式保存应用的UI数据。将应用程序的UI数据与Activity和Fragment类分离,可以让你更好地遵循单一责任原则:你的activities和fragments负责将数据绘制到屏幕上,而ViewModel则负责保存和处理UI所需的所有数据。
在ViewModel中,对于UI将使用或显示的可变数据,请使用LiveData。使用LiveData有几个好处:
- 您可以在数据上放置一个观察者(而不是轮询更改),并且只在数据实际更改时更新UI。
- Repository和UI由ViewModel完全分离。没有来自ViewModel的数据库调用,使得代码更易于测试。
- ViewModel的实现
- 创建WordViewModel类,使其继承AndroidViewModel。
publicclassWordViewModelextendsAndroidViewModel{}
- 添加一个私有成员变量来保存对存储库的引用。
privateWordRepository mRepository;
- 添加一个私有LiveData成员变量来缓存单词列表。
privateLiveData< List<Word>> mAllWords;
- 添加一个构造函数,该构造函数获取对存储库的引用,并从存储库获取单词列表。
publicWordViewModel(Application application){
super(application);
mRepository = newWordRepository(application);
mAllWords = mRepository.getAllWords();
}
- 为所有单词添加一个get方法。这完全隐藏了对UI的实现。
LiveData< List<Word>> getAllWords() { returnmAllWords; }
- 创建一个调用Repository的insert()方法的包装器insert()方法。这样,insert()的实现对于UI就完全透明了。
publicvoidinsert(Word word){ mRepository.insert(word); }
下面是WordViewModel的实现:
publicclassWordViewModelextendsAndroidViewModel{
privateWordRepository mRepository;
privateLiveData<List<Word>> mAllWords;
publicWordViewModel(Application application){
super(application);
mRepository = newWordRepository(application);
mAllWords = mRepository.getAllWords();
}
LiveData<List<Word>> getAllWords() { returnmAllWords; }
publicvoidinsert(Word word){ mRepository.insert(word); }
}
警告:不要将context传递到ViewModel实例中。不要在ViewModel中存储活动、片段或视图实例或它们的context。
添加XML布局
在value/styes.xml中添加列表项的样式:
<!-- The default font for RecyclerView items is too small.
The margin is a simple delimiter between the words. -->
<stylename="word_title">
<itemname="android:layout_width">match_parent</item>
<itemname="android:layout_height">26dp</item>
<itemname="android:textSize">24sp</item>
<itemname="android:textStyle">bold</item>
<itemname="android:layout_marginBottom">6dp</item>
<itemname="android:paddingLeft">8dp</item>
</style>
添加一个layout/recyclerview_item.xml布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/textView"
style="@style/word_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/holo_orange_light"/>
</LinearLayout>
在Layout/Content_main.xml中,将TextView替换为ReccyclerView:
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/darker_gray"
tools:listitem="@layout/recyclerview_item"/>
浮动动作按钮(FAB)应与可用动作相对应。在Layout/Activitymain.xml文件中,给FloatingActionButton一个+符号图标:
- 在Layout/Activitymain.xml文件中,选择File>New>VectorAsset。
- 选择Material Icon。
- 点击Android机器人图标:点field, 然后选 + (“add”) 资源。
- 按以下方式更改布局文件代码。
android:src="@drawable/ic_add_black_24dp"
添加RecycleView
您将在RecycleView中显示数据,这比将数据抛到TextView中要好一些。注意,适配器中的mWord变量缓存数据。在下一个任务中,添加自动更新数据的代码。还请注意,getItemCount()方法需要优雅地考虑数据尚未准备好且mWord仍然为空的可能性。
添加一个类WordListAdapter,它扩展了ReccyclerView.Adapter。
这是代码:
publicclassWordListAdapterextendsRecyclerView.Adapter<WordListAdapter.WordViewHolder> {
classWordViewHolderextendsRecyclerView.ViewHolder{
privatefinalTextView wordItemView;
privateWordViewHolder(View itemView){
super(itemView);
wordItemView = itemView.findViewById(R.id.textView);
}
}
privatefinalLayoutInflater mInflater;
privateList<Word> mWords; // Cached copy of words
WordListAdapter(Context context) { mInflater = LayoutInflater.from(context); }
@Override
publicWordViewHolder onCreateViewHolder(ViewGroup parent, intviewType){
View itemView = mInflater.inflate(R.layout.recyclerview_item, parent, false);
returnnewWordViewHolder(itemView);
}
@Override
publicvoidonBindViewHolder(WordViewHolder holder, intposition){
if(mWords != null) {
Word current = mWords.get(position);
holder.wordItemView.setText(current.getWord());
} else{
// Covers the case of data not being ready yet.
holder.wordItemView.setText( "No Word");
}
}
voidsetWords(List<Word> words){
mWords = words;
notifyDataSetChanged();
}
// getItemCount() is called many times, and when it is first called,
// mWords has not been updated (means initially, it's null, and we can't return null).
@Override
publicintgetItemCount(){
if(mWords != null)
returnmWords.size();
elsereturn0;
}
}
在MainActivity的onCreate()方法中添加ReccyclerView。在onCreate()方法中:
RecyclerView recyclerView = findViewById(R.id.recyclerview);
finalWordListAdapter adapter = newWordListAdapter( this);
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager( newLinearLayoutManager( this));
运行你的应用程序,以确保一切正常。没有项目,因为您还没有连接到数据,所以应用程序应该显示灰色背景,没有任何列表项目。
填充数据库
要删除所有内容并在应用程序启动时重新填充数据库,您可以创建一个RoomDatabase.Callback并覆盖onOpen()。由于不能对UI线程执行Room数据库操作,因此onOpen()创建并执行AsyncTask来向数据库添加内容。
下面是在WordRoomDatabase类中创建回调的代码:
privatestaticRoomDatabase.Callback sRoomDatabaseCallback =
newRoomDatabase.Callback(){
@Override
publicvoidonOpen(@NonNull SupportSQLiteDatabase db){
super.onOpen(db);
newPopulateDbAsync(INSTANCE).execute();
}
};
下面是AsyncTask的代码,它删除数据库的内容,然后用两个单词“Hello”和“World”填充数据库。欢迎加入更多的单词!
privatestaticclassPopulateDbAsyncextendsAsyncTask<Void, Void, Void> {
privatefinalWordDao mDao;
PopulateDbAsync(WordRoomDatabase db) {
mDao = db.wordDao();
}
@Override
protectedVoid doInBackground(finalVoid... params){
mDao.deleteAll();
Word word = newWord( "Hello");
mDao.insert(word);
word = newWord( "World");
mDao.insert(word);
returnnull;
}
}
最后,在调用.build()之前,将回调添加到数据库构建序列。
.addCallback(sRoomDatabaseCallback)
添加NewWordActivity
将这些字符串资源添加到values/strings.xml中:
<stringname="hint_word">Word... </string>
<stringname="button_save">Save </string>
<stringname="empty_not_saved">Word not saved because it is empty. </string>
在value/color s.xml中添加此颜色资源:
<colorname="buttonLabel">#d3d3d3 </color>
将这些维度资源添加到values/dimens.xml:
<dimenname="small_padding">6dp </dimen>
<dimenname="big_padding">16dp </dimen>
在布局文件夹中创建Activity_new_word.xml文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/edit_word"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-light"
android:hint="@string/hint_word"
android:inputType="textAutoComplete"
android:padding="@dimen/small_padding"
android:layout_marginBottom="@dimen/big_padding"
android:layout_marginTop="@dimen/big_padding"
android:textSize="18sp"/>
<Button
android:id="@+id/button_save"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:text="@string/button_save"
android:textColor="@color/buttonLabel"/>
</LinearLayout>
使用空活动模板创建一个新活动,NewWordActivity。验证活动是否已添加到AndroidManifest中!
<activityandroid:name=".NewWordActivity"></activity>
下面是该activity的代码:
publicclassNewWordActivityextendsAppCompatActivity{
publicstaticfinalString EXTRA_REPLY = "com.example.android.wordlistsql.REPLY";
privateEditText mEditWordView;
@Override
publicvoidonCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_new_word);
mEditWordView = findViewById(R.id.edit_word);
finalButton button = findViewById(R.id.button_save);
button.setOnClickListener( newView.OnClickListener() {
publicvoidonClick(View view){
Intent replyIntent = newIntent();
if(TextUtils.isEmpty(mEditWordView.getText())) {
setResult(RESULT_CANCELED, replyIntent);
} else{
String word = mEditWordView.getText().toString();
replyIntent.putExtra(EXTRA_REPLY, word);
setResult(RESULT_OK, replyIntent);
}
finish();
}
});
}
}
连接数据
最后一步是通过保存用户输入的新单词并在RecyclerView中显示Word数据库的当前内容,将UI连接到数据库。
要显示数据库的当前内容,添加一个观察者来观察ViewModel中的LiveData。每当数据更改时,都会调用onchange()回调,该回调调用适配器的setWord()方法,以更新适配器的缓存数据并刷新显示的列表。
在MainActivity中,为ViewModel创建一个成员变量:
privateWordViewModel mWordViewModel;
使用ViewModelProviders将ViewModel与UI控制器关联起来。当应用程序第一次启动时,ViewModelProviders将创建ViewModel。当activity 被销毁时,例如通过配置更改,ViewModel就会持续存在。重新创建activity 时,ViewModelProviders将返回现有的ViewModel。
在onCreate()中,从ViewModelProvider获取一个ViewModel。
mWordViewModel = ViewModelProviders.of( this). get(WordViewModel. class);
同样在onCreate()中,为getAllWords()返回的LiveData添加一个观察者。当观察到的数据发生变化且activity 位于前台时,onchange()方法就会调用。
mWordViewModel.getAllWords().observe( this, newObserver< List<Word>>() {
@Override
public voidonChanged( @NullablefinalList<Word> words) {
// Update the cached copy of the words in the adapter.
adapter.setWords(words);
}
});
在MainActivity中,为NewWordActivity添加onActivityResult()代码。如果activity返回RESULT_OK,则通过调用WordViewModel的insert()方法将返回的单词插入数据库。
publicvoidonActivityResult(intrequestCode, intresultCode, Intent data){
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == NEW_WORD_ACTIVITY_REQUEST_CODE && resultCode == RESULT_OK) {
Word word = newWord(data.getStringExtra(NewWordActivity.EXTRA_REPLY));
mWordViewModel.insert(word);
} else{
Toast.makeText(
getApplicationContext(),
R. string.empty_not_saved,
Toast.LENGTH_LONG).show();
}
}
定义缺少的请求代码:
publicstaticfinalintNEW_WORD_ACTIVITY_REQUEST_CODE = 1;
在MainActivity中,当用户点击Fab时启动NewWordActivity。用以下代码替换Fab的onclick()单击处理程序中的代码:
Intent intent = newIntent(MainActivity. this, NewWordActivity. class);
startActivityForResult(intent, NEW_WORD_ACTIVITY_REQUEST_CODE);
运行你的APP!!!
当您在NewWordActivity中向数据库添加一个单词时,UI将自动更新。
总结
现在你有了一个实用的应用程序,让我们回顾一下你已经构建了什么。这是最开发的应用程序的结构。您有一个在列表中显示单词的应用程序(MainActivity、ReccyclerView、WordListAdapter)。您可以向列表中添加单词(NewWordActivity)。单词是单词实体类的实例。这些单词作为单词(mWords) List缓存在RecyclerViewAdapter中。当数据库中的单词更改时,此单词列表会自动更新和重新显示。
用于自动UI更新的数据流(反应性UI)
自动更新是可能的,因为我们正在使用LiveData。在MainActivity中,有一个观察者从数据库中观察到LiveData这个词,并在它们更改时得到通知。当发生更改时,将执行观察者的onChange()方法,并更新WordListAdapter中的mWord。可以观察到数据,因为它是LiveData。观察到的是由WordViewModel对象的getAllWords()方法返回的LiveData<list<word>>。WordViewModel从UI层隐藏关于后端的所有内容。它提供访问数据层的方法,并返回LiveData,以便MainActivity可以设置观察者关系。Activities(和Fragments)仅通过ViewModel与数据交互。因此,数据从何而来并不重要。
在这个demo,数据来自一个存储库。ViewModel不需要知道这个仓库与什么交互。它只需要知道如何通过Repository公开的方法与Repository交互。
Repository 管理一个或多个数据源。在WordListSample应用程序中,后端是一个Room数据库。Room是一个包装器,实现了SQLite数据库。房间为你做了很多工作,你以前不得不自己做。例如,Room完成了以前使用SQLiteOpenHelper类所做的一切。
DAO映射方法调用数据库查询,以便当Repository调用getAllWords()等方法时,Room可以通过执行SELECT * from word_table ORDER BY word ASC。
因为从查询返回的结果被观察到LiveData,所以每当Room中的数据发生变化时,会执行观察者接口的onChanged()方法,并更新UI。
单击以下链接下载此codelab的解决方案代码:
https://download.youkuaiyun.com/download/itismelzp/10753867