0%

安卓对象关系映射框架Room学习记录

写此篇博客是为了记录一下安卓Room的学习,围绕一个图文列表Demo展开,使用到了Room、ViewModel、Repository、AsyncTask、LiveData、RecyclerView等。

关于此篇博客的Demo的代码你可以在这里找到:GitHub

Room是google官方开发的对象关系映射(ORM)库框架,在SQLite上提供了一个抽象层,以便在充分利用SQLite的强大功能的同时,能够流畅地访问数据库。

导入相关依赖

首先导入Demo所需要的库:

1
2
3
4
5
6
7
8
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里面所有的字段对应到表上的每一列。

EntityDaoDatabase是Room的3个主要组件。

对Entity的操作如下:

  • 如果需要制定某个字段不作为表中的一列需要添加@Ignore注解。
  • 通过在一列中添加@PrimaryKey来设置主键,若设置autoGenerate = true则代表自动增长
  • 通过在一列中添加@ColumnInfo(name = "ColumnName")来设置列的名字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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;
}

随后创建构造函数与SetterGetter函数:

1
2
3
4
5
6
7
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的操作都通过注解实现,非常方便。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@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设计为单例模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
@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对象的方法修改一下:

1
2
3
INSTANCE = Room.databaseBuilder(context.getApplicationContext(), BabyCapDatabase.class, "babycap_database")
.allowMainThreadQueries()
.build();

Repository(仓库)

Repository是一个独立的层,介于领域层与数据映射层(数据访问层)之间。它的存在让领域层感觉不到数据访问层的存在,它提供一个类似集合的接口提供给领域层进行领域对象的访问。Repository是仓库管理员,领域层需要什么东西只需告诉仓库管理员,由仓库管理员把东西拿给它,并不需要知道东西实际放在哪。

AsyncTask

首先,因为对数据库的操作不能够放在主线程里面,我们采取AsyncTask这样子的操作

下面举两个个例子,展示了对数据库的插入与修改的操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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类,抽象对数据的操作方法

首先定义两个成员变量和构造方法:

1
2
3
4
5
6
7
8
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方法:

1
2
3
public LiveData<List<BabyCap>> getAllBabyCapLive() {
return allBabyCapLive;
}

最后,根据刚刚创建的AsyncTask来定义对数据库的操作,下面同样是举两个例子:

1
2
3
4
5
6
7
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来存放我们的数据

1
2
3
4
5
6
7
8
9
10
11
12
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();
}
}

顺带把对数据库的操作也封装进去:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

同样的,还是三个成员变量,一个用来存放EntityList、一个是ViewModel,因为还使用到了AlertDialog,所以有一个LayoutInflater,最后再加上一个构造函数:

1
2
3
4
5
6
7
8
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

1
2
3
public void setBabyCapList(List<BabyCap> babyCapList) {
this.babyCapList = babyCapList;
}

在onBindViewHolder方法里面,定义一下Delete和Update按钮的操作,别的操作就不赘述了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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方法:

1
2
3
4
5
6
@Override
public int getItemCount() {
if(babyCapList != null)
return babyCapList.size();
return 0;
}

MainActivity

最后一部分,我们在程序入口完善逻辑处理。首先需要到两个成员变量:

1
2
BabyCapViewModel babyCapViewModel;
BabyCapRecyclerViewAdapter babyCapRecyclerViewAdapter;

紧接着在onCreate方法里面将这两个类实例化为对象:

1
2
babyCapViewModel = ViewModelProviders.of(this).get(BabyCapViewModel.class);
babyCapRecyclerViewAdapter = new BabyCapRecyclerViewAdapter(babyCapViewModel, this);

然后把babyCapRecyclerViewAdapterRecyclerView关联上:

1
2
3
4
5
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

1
2
3
4
5
6
7
babyCapViewModel.getAllBabyCapLive().observe(this, new Observer<List<BabyCap>>() {
@Override
public void onChanged(List<BabyCap> babyCaps) {
babyCapRecyclerViewAdapter.setBabyCapList(babyCaps);
babyCapRecyclerViewAdapter.notifyDataSetChanged();
}
});

最后,我们使用两个按钮来方便我们对数据库的内容进行插入和删除:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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


参考资料