写此篇博客是为了记录一下安卓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里面所有的字段对应到表上的每一列。
Entity
、Dao
、Database
是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; }
|
随后创建构造函数与Setter
、Getter
函数:
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; }
|
创建访问数据库的方法(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
同样的,还是三个成员变量,一个用来存放Entity
的List
、一个是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);
|
然后把babyCapRecyclerViewAdapter
与RecyclerView
关联上:
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
参考资料