这是我参与「第四届青训营 」笔记创作活动的的第7天
一、前言
上一篇文章讲了android中的网络请求,这一次来学习一下android的数据存储吧。
文章的主要内容如下:
- 存储方式对比
- 数据库开源框架对比
- Room数据库的使用
二、存储方式对比
android官方已经给我提供了几种持久化存储的方式,每一种都各有优缺点,我们来看下面这张图片对比一下吧。
SharedPreferences
SharedPreferences可以存储一些比较简单的数据,它通过键值对的方式进行存储,类似于Java中的Map。
通常我们可以用它来存储一些设置的数据,或者是用户的id这一类简单的数据。
文件存储
文件存储也是很常见的存储方式,一般用来存储图片、音频、视频这一类比较特殊的数据。
ContentProvider
ContentProvider可以用来共享其它App的数据。
SQLite
SQLite是一个小型的数据库,它的包体积只有几百k,比较适合用在手机设备上,它通常用来存储结构化的数据,比较说保存新闻列表、消息内容。
三、数据库开源框架对比
比较常见的数据库框架如以下几种。
每一种都有各自的优缺点,但Room数据库因为是Google官方的,对谷歌的JetPack系列的库有更好的兼容性,比如可以支持LiveData、协程。
四、Room数据库的使用
1. 引入包
在项目下的app目录中的build.gradle中的dependencies中添加依赖
dependencies {
def room_version = "2.4.2"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
}
2. 主要组件
- 数据库类: 用于保存数据库并作为应用持久性数据底层连接的主要访问点。
- 数据实体: 用于表示应用的数据库中的表。
- 数据访问对象 (DAO): 提供您的应用可用于查询、更新、插入和删除数据库中的数据的方法。
3. 实现示例
假设现在有个需求,需要存储一个联系人的信息,假设一个联系人的信息有:联系人名、电话号码。
当然我们一边都会给每个数据表增加一个编号主键。
(1) 编写实体类
实体类代码如下,其中@Entity注解用于标识这是一个数据库实体类,@PrimaryKey标明这是一个主键,autoGenerate为是否自增,@ColumnInfo注解可以设置数据表的列名(不加也是可以的)。
@Entity
public class Contact {
@PrimaryKey(autoGenerate = true)
private int id;
@ColumnInfo(name = "name")
private String name;
@ColumnInfo(name = "phone_number")
private String phoneNumber;
// 省略setter、getter方法
...
@Override
public String toString() {
return "Contact{" +
"id=" + id +
", name='" + name + ''' +
", phoneNumber='" + phoneNumber + ''' +
'}';
}
}
(2) 编写Dao(数据访问对象)接口
Dao接口代码如下,其中@Dao注解用于标识这是一个Dao类。
可以看到Room对数据库的操作主要的注解有:@Query、@Insert、@Update、@Delete,方便对应CURD,但是这里的Query不一样,它可以执行任何的sql语句。
@Dao
public interface ContactDao {
@Query("SELECT * FROM contact")
List<Contact> getAll();
@Query("SELECT * FROM contact WHERE name LIKE :name LIMIT 1")
Contact findByName(String name);
@Insert
void insertAll(Contact... contacts);
@Update
void update(Contact contact);
@Delete
void delete(Contact contacts);
}
(3) 编写数据库类
数据库类代码如下,@Database注解用于标识这是一个数据库类,entities可以设置此数据库包含的数据表(实体类),version用于数据库的版本号。
值得注意的是,数据库类是抽象类,Dao类是一个接口。
因为Room框架在我们编译的时候会生成具体的实现类,按照规范,我们需要在数据库类定义对应的抽象方法来获取对应的Dao实例。
@Database(entities = {Contact.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract ContactDao contactDao();
private static volatile AppDatabase INSTANCE;
public static AppDatabase get(Context context) {
if (INSTANCE == null) {
synchronized (AppDatabase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
AppDatabase.class, "database-name").build();
}
}
}
return INSTANCE;
}
}
测试
需要注意的是,Room数据库默认不支持在主线程进行操作,这也是一个很好的规则,因为数据库操作本身就是一个耗时的操作,轻则可以引起界面卡顿,重则可能导致ANR,当然你可以在获取数据库类实例的时候设置允许在主线程操作,但Google并不推荐这样做。
测试代码如下,创建了一个Contact对象,并将它添加到数据库中,并查询数据库,然后输出Log。
new Thread(){
@Override
public void run() {
super.run();
// 获取ContactDao实例
ContactDao contactDao = AppDatabase.get(getApplicationContext()).contactDao();
Contact contact = new Contact();
contact.setName("小林");
contact.setPhoneNumber("10086");
// 将contact对象添加到数据库
contactDao.insertAll(contact);
// 查询数据库
List<Contact> all = contactDao.getAll();
Log.d(TAG, "查询结果: " + all);
}
}.start();
我们查看一下log,可以看到数据成功地插入并且成功地查询了。
关于Room数据库更多的操作可以查看:developer.android.google.cn/training/da…
五、总结
这里主要为大家介绍了android中常用的数据存储方式,并且比较了各种存储方式的优缺点以及常用的数据库框架,并且也介绍了Room数据库框架的简单使用。
除了Room框架之外,我也用过ObjectBox,ObjectBox相对于Room框架确实会快一些,不过它底层不是用的SQLite。
ObjectBox有几个好处,比如说支持动态更新表列,你可以在对某个表增加列之后几乎完全不需要做其它的操作,还有默认支持了CURD等常用的数据库操作,不用去编写常用的CURD方法,这个比较类似于mybatis-plus,我觉得还是挺不错的,当然Room数据库可以很好地支持Google的JetPack家族,所以各有优缺。
六、结语
如果喜欢或有所帮助的话,希望能点赞关注,鼓励一下作者。
如果文章有不正确或存疑的地方,欢迎评论指出。
参考
juejin.cn/post/712345… developer.android.google.cn/training/da…