写此篇博客是为了记录一下安卓Room
的学习,围绕一个图文列表Demo展开,使用到了Room、ViewModel、Repository、AsyncTask、LiveData、RecyclerView等。
关于此篇博客的Demo的代码你可以在这里找到:GitHub
Room是google官方开发的对象关系映射(ORM)库框架,在
SQLite
上提供了一个抽象层,以便在充分利用SQLite
的强大功能的同时,能够流畅地访问数据库。
导入相关依赖
首先导入Demo所需要的库:
def room_version = "2.2.5"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
testImplementation "androidx.room:room-testing:$room_version"
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'android.arch.lifecycle:extensions:1.1.1'
创建一个实体类(Entity)
每个Entity代表数据库中某个表的实体类,与表一一对应。默认情况下Room会把Entity里面所有的字段对应到表上的每一列。
Entity
、Dao
、Database
是Room的3个主要组件。
对Entity的操作如下:
- 如果需要制定某个字段不作为表中的一列需要添加
@Ignore
注解。 - 通过在一列中添加
@PrimaryKey
来设置主键,若设置autoGenerate = true
则代表自动增长 - 通过在一列中添加
@ColumnInfo(name = "ColumnName")
来设置列的名字
@Entity
public class BabyCap {
@PrimaryKey(autoGenerate = true)
private int id;
@ColumnInfo(name = "img_id")
private int news_thumb_id;
private String news_info;
private String news_title;
private boolean visibility;
@Ignore
private int unused;
}
随后创建构造函数与Setter
、Getter
函数:
public BabyCap(int news_thumb_id, String news_info, String news_title, boolean visibility) {
this.news_thumb_id = news_thumb_id;
this.news_info = news_info;
this.news_title = news_title;
this.visibility = visibility;
}
//为了减少篇幅,这里就不列出Setter和Getter函数了
创建访问数据库的方法(Dao)
这个组件代表了作为DAO的类或者接口。DAO是Room的主要组件,负责定义访问数据库的方法。Room使用过程中一般使用抽象DAO类来定义数据库的CRUD操作。DAO可以是一个接口也可以是一个抽象类。如果它是一个抽象类,它可以有一个构造函数,它将RoomDatabase作为其唯一参数。Room在编译时创建每个DAO实例。DAO里面所有的操作都是依赖方法来实现的。
在Demo里使用接口来实现。
而Insert、Update、Delete、Select的操作都通过注解实现,非常方便。
@Dao
public interface BabyCapDao {
@Insert
void insertBabyCap(BabyCap... babyCaps);
@Update
void updateBabyCap(BabyCap... babyCaps);
@Delete
void deleteBabyCap(BabyCap... babyCaps);
@Query("DELETE FROM BABYCAP")
void deleteAllBabyCap();
@Query("SELECT * FROM BABYCAP ORDER BY ID DESC")
List<BabyCap> getBabyCaps();
@Query("SELECT * FROM BABYCAP ORDER BY ID DESC")
LiveData<List<BabyCap>> getBabyCapsLiveData();
}
注意到有两个方法都可以返回实例的列表,通常我们使用第二个方法,当内容改变后方便的刷新View
列表里面的内容。
RoomDatabase(数据库)
数据库:包含数据库持有者,并作为应用已保留的持久关系型数据的底层连接的主要接入点。
使用 @Database
注释的类应满足以下条件:
- 是扩展
RoomDatabase
的抽象类。 - 在注释中添加与数据库关联的实体列表。
- 包含具有 0 个参数且返回使用
@Dao
注释的类的抽象方法。
使用的时候可以通过调用Room.databaseBuilder()
或者Room.inMemoryDatabaseBuilder()
获取实例。
两种方式获取Database对象的区别:
Room.databaseBuilder()
:生成Database对象,并且创建一个存在文件系统中的数据库。Room.inMemoryDatabaseBuilder()
:生成Database对象并且创建一个存在内存中的数据库。当应用退出的时候(应用进程关闭)数据库也消失。
为了减小开销,Demo里的Database设计为单例模式:
@Database(entities = {BabyCap.class}, version = 1, exportSchema = false)
public abstract class BabyCapDatabase extends RoomDatabase {
private static BabyCapDatabase INSTANCE;
static synchronized BabyCapDatabase getDatabase(Context context) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(), BabyCapDatabase.class, "babycap_database").build();
}
return INSTANCE;
}
public abstract BabyCapDao getBabyCapDao();
}
另外,Room默认不许在主线中对数据库中的数据进行增删查改等操作,我们需要将操作放在异步线程中去执行。如果你觉得麻烦或是需要先进行小小的测试,可以将获取Database对象的方法修改一下:
INSTANCE = Room.databaseBuilder(context.getApplicationContext(), BabyCapDatabase.class, "babycap_database")
.allowMainThreadQueries()
.build();
Repository(仓库)
Repository是一个独立的层,介于领域层与数据映射层(数据访问层)之间。它的存在让领域层感觉不到数据访问层的存在,它提供一个类似集合的接口提供给领域层进行领域对象的访问。Repository是仓库管理员,领域层需要什么东西只需告诉仓库管理员,由仓库管理员把东西拿给它,并不需要知道东西实际放在哪。
AsyncTask
首先,因为对数据库的操作不能够放在主线程里面,我们采取AsyncTask
这样子的操作
下面举两个个例子,展示了对数据库的插入与修改的操作:
static class InsertAsyncTask extends AsyncTask<BabyCap, Void, Void> {
private BabyCapDao BabyCapDao;
InsertAsyncTask(BabyCapDao BabyCapDao) {
this.BabyCapDao = BabyCapDao;
}
@Override
protected Void doInBackground(BabyCap... BabyCaps) {
BabyCapDao.insertBabyCap(BabyCaps);
return null;
}
}
static class UpdateAsyncTask extends AsyncTask<BabyCap, Void, Void> {
private BabyCapDao BabyCapDao;
UpdateAsyncTask(BabyCapDao BabyCapDao) {
this.BabyCapDao = BabyCapDao;
}
@Override
protected Void doInBackground(BabyCap... BabyCaps) {
BabyCapDao.updateBabyCap(BabyCaps);
return null;
}
}
可以看到在构造函数中传入了Dao,并重写了doInBackground
方法。
BabyCapRepository
接下来,创建一个BabyCapRepository
类,抽象对数据的操作方法
首先定义两个成员变量和构造方法:
private LiveData<List<BabyCap>> allBabyCapLive;
private BabyCapDao babyCapDao;
public BabyCapRepository(Context context) {
BabyCapDatabase babyCapDatabase = BabyCapDatabase.getDatabase(context.getApplicationContext());
babyCapDao = babyCapDatabase.getBabyCapDao();
allBabyCapLive = babyCapDao.getBabyCapsLiveData();
}
因为allBabyCapLive
是在外部使用的,我们还得有一个Getter方法:
public LiveData<List<BabyCap>> getAllBabyCapLive() {
return allBabyCapLive;
}
最后,根据刚刚创建的AsyncTask
来定义对数据库的操作,下面同样是举两个例子:
void insertBabyCaps(BabyCap... BabyCaps) {
new InsertAsyncTask(babyCapDao).execute(BabyCaps);
}
void updateBabyCaps(BabyCap... BabyCaps) {
new UpdateAsyncTask(babyCapDao).execute(BabyCaps);
}
ViewModel
ViewModel类是被设计用来以可感知生命周期的方式存储和管理 UI 相关数据,ViewModel中数据会一直存活即使 activity configuration发生变化,比如横竖屏切换的时候。
接下来我们使用ViewModel来存放我们的数据
public class BabyCapViewModel extends AndroidViewModel {
private BabyCapRepository babyCapRepository;
public BabyCapViewModel(@NonNull Application application) {
super(application);
babyCapRepository =new BabyCapRepository(application);
}
public LiveData<List<BabyCap>> getAllBabyCapLive() {
return babyCapRepository.getAllBabyCapLive();
}
}
顺带把对数据库的操作也封装进去:
void insertBabyCaps(BabyCap... BabyCaps) {
babyCapRepository.insertBabyCaps(BabyCaps);
}
void updateBabyCaps(BabyCap... BabyCaps) {
babyCapRepository.updateBabyCaps(BabyCaps);
}
void deleteBabyCaps(BabyCap... BabyCaps) {
babyCapRepository.deleteBabyCaps(BabyCaps);
}
void deleteAllBabyCaps() {
babyCapRepository.deleteAllBabyCaps();
}
BabyCapRecyclerViewAdapter
因为我们使用到了RecyclerView
,所以接下来我们来完善一下它的Adapter
同样的,还是三个成员变量,一个用来存放Entity
的List
、一个是ViewModel
,因为还使用到了AlertDialog
,所以有一个LayoutInflater
,最后再加上一个构造函数:
private List<BabyCap> babyCapList;
private BabyCapViewModel babyCapViewModel;
private LayoutInflater mInflater;
public BabyCapRecyclerViewAdapter(BabyCapViewModel babyCapViewModel, Context context) {
this.babyCapViewModel = babyCapViewModel;
this.mInflater = LayoutInflater.from(context);
}
接着,因为数据库的内容会发生,所以我们得定义一个Setter
方法用来修改babyCapList
:
public void setBabyCapList(List<BabyCap> babyCapList) {
this.babyCapList = babyCapList;
}
在onBindViewHolder方法里面,定义一下Delete和Update按钮的操作,别的操作就不赘述了:
final BabyCap babyCap = babyCapList.get(position);
holder.buttonDelete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
babyCapViewModel.deleteBabyCaps(babyCap);
}
});
holder.buttonUpdate.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
babyCap.setNews_info(babyCap.getNews_info()+"update!");
babyCapViewModel.updateBabyCaps(babyCap);
}
});
最后有一点需要注意,LiveData
在没有数据的时候会返回null
,如果直接对babyCapList
进行操作会报错,所以我们稍微修改一下getItemCount
方法:
@Override
public int getItemCount() {
if(babyCapList != null)
return babyCapList.size();
return 0;
}
MainActivity
最后一部分,我们在程序入口完善逻辑处理。首先需要到两个成员变量:
BabyCapViewModel babyCapViewModel;
BabyCapRecyclerViewAdapter babyCapRecyclerViewAdapter;
紧接着在onCreate方法里面将这两个类实例化为对象:
babyCapViewModel = ViewModelProviders.of(this).get(BabyCapViewModel.class);
babyCapRecyclerViewAdapter = new BabyCapRecyclerViewAdapter(babyCapViewModel, this);
然后把babyCapRecyclerViewAdapter
与RecyclerView
关联上:
RecyclerView recyclerView = findViewById(R.id.recycler_view);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setAdapter(babyCapRecyclerViewAdapter);
接着有个关键的步骤,当LiveData
的内容发生改变时,BabyCapRecyclerViewAdapter
中的babyCapList
的内容也需要紧跟着被更新,所以我们要对LiveData<List<BabyCap>>
创建一个observe
:
babyCapViewModel.getAllBabyCapLive().observe(this, new Observer<List<BabyCap>>() {
@Override
public void onChanged(List<BabyCap> babyCaps) {
babyCapRecyclerViewAdapter.setBabyCapList(babyCaps);
babyCapRecyclerViewAdapter.notifyDataSetChanged();
}
});
最后,我们使用两个按钮来方便我们对数据库的内容进行插入和删除:
buttonInsert = findViewById(R.id.buttonInsert);
buttonClear = findViewById(R.id.buttonClear);
buttonInsert.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
BabyCap i1 = new BabyCap(R.drawable.i1, "此系列服装有点cute,像不像小车夫。","毡帽系列", true);
BabyCap i2 = new BabyCap(R.drawable.i2, "宝宝变成了小蜗牛,爬啊爬啊爬啊。","蜗牛系列", true);
BabyCap i3 = new BabyCap(R.drawable.i3, "小蜜蜂,嗡嗡嗡,飞到西,飞到东。","小蜜蜂系列", true);
babyCapViewModel.insertBabyCaps(i1,i2,i3);
}
});
buttonClear.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
babyCapViewModel.deleteAllBabyCaps();
}
});
演示
结语
大部分关键的代码已经展示出来了,完整的项目代码可以去我的GitHub上下载:GitHub