概述
【Android|Android随笔-Room简单使用】处理大量结构化数据的应用可极大地受益于在本地保留这些数据。最常见的使用场景是缓存相关的数据,这样一来,当设备无法访问网络时,用户仍然可以在离线状态下浏览该内容。Room用于数据持久化,使用简单,对SQL不了解也可以快速使用,但暴露出来的api有限,遇到复杂的数据还需使用SQL。Room本身性能并无特别强大之处,但是在Androidx中配合了Lifecycle,可以感知生命周期是其他数据库无法比拟的;同时搭配WorkManager使用可以实现非即时(至少15分钟之后),且一定会实现的任务(APP没有被删除的情况下)(目前仅限于Google pixel)。本文只对Room的简单使用进行阐述,不涉及Lifecycle等其他内容。
Room 持久性库在 SQLite 上提供了一个抽象层,以便在充分利用 SQLite
的强大功能的同时,能够流畅地访问数据库。具体来说,Room 具有以下优势:
针对 SQL 查询的编译时验证。 可最大限度减少重复和容易出错的样板代码的方便注解。 简化了数据库迁移路径。
组成 Room 包含三个主要组件:
- Database:数据库类,用于保存数据库并作为应用持久性数据底层连接的主要访问点。
- Entity:数据实体,用于表示应用的数据库中的表。
- DAO:数据访问对象 ,提供您的应用可用于查询、更新、插入和删除数据库中的数据的方法。
文章图片
具体的流程如下:
文章图片
使用 1. 添加依赖
dependencies {
...
// 数据持久化room
implementation 'androidx.room:room-runtime:2.4.2'
annotationProcessor 'androidx.room:room-compiler:2.4.2'
}
2. 添加数据库schemas(选)
defaultConfig {
minSdkVersion MinSdkVersion
targetSdkVersion TargetSdkVersion
versionCode 1
versionName "1.0"javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName(),
"room.schemaLocation":"$projectDir/schemas".toString()]
}
}
}
3. 创建Entity
@Entity(tableName = "user")
public class User {
// 主键,自增
@PrimaryKey(autoGenerate = true)
@NonNull
public Integer id;
// 名称
@ColumnInfo(name = "name")
public String name;
// 年龄
public Integer age;
public User(String name, Integer age) {
this.name = name;
this.age = age;
}@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
- @Entity:表示User是一张数据表
- tableName:数据库表名,不写时默认为“”
- @primaryKeys:主键
- autoGenerate:默认false,为true时表示主键无需赋值,值会自动递增
- @ColumnInfo:字段属性,name为该属性在数据库中字段名称,不写默认为属性名称
4. 创建DAO
public interface UserDao {/**
* 查询所有用户
*
* @return
*/
@Query("SELECT * FROM user")
List queryAllUser();
/**
* 根据名称查询所有用户
*
* @return
*/
@Query("SELECT * FROM user WHERE name = :name")
List queryUserByName(String name);
/**
* 插入用户
*
* @param user
*/
@Insert
void insertCustom(User user);
/**
* 更新用户
*
* @param users
*/
@Update
void updateUser(User... users);
/**
* 删除用户
*
* @param user
*/
@Delete
void deleteUser(User user);
/**
* 清空数据
*/
@Query("DELETE FROM user")
void deleteAllUsers();
}
- @Query:查
- @Insert:增
- @Update:改
- @Delete:删
5. 创建Database
@Database(entities = {User.class}, version = 1, exportSchema = false)
public abstract class UserDatabase extends RoomDatabase {
// 数据库名称
private static final String DATABASE_NAME = "user_db";
// 数据库实例
private static UserDatabase INSTANCE;
// 单例
public static UserDatabase getInstance(Context context) {
if (INSTANCE == null) {
synchronized (UserDatabase.class) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(), UserDatabase.class, DATABASE_NAME)
.fallbackToDestructiveMigration()// 暴力升级,可能会出现数据丢失
.build();
}
}
return INSTANCE;
}// 暴露操作对象
public abstract UserDao userDao();
}
- @Database:数据库,需要指定entities
- version:数据库版本,一般只能升不能降,表格有所改动均需要升级
- exportSchema:数据配置,默认为true,需要进行第二步配置,若false,则无需配置
- fallbackToDestructiveMigration:暴力升级,可能会出现数据丢失
6. 创建异步任务
数据库操作可能会耗时,建议创建异步任务进行操作。
public class QueryUserTask extends AsyncTask> {
private UserDao userDao;
public QueryUserTask(UserDao userDao) {
this.userDao = userDao;
}@Override
protected List doInBackground(Void... voids) {
Log.d("QueryUserTask", "doInBackground: start");
List users = userDao.queryAllUser();
Log.d("QueryUserTask", "doInBackground: end");
return users == null ? Collections.EMPTY_LIST : users;
}
}
AsyncTask为异步任务,Java默认是同步任务,异步任务需要单独创建。
public abstract class AsyncTask {...}
- Params:参数类型,无参使用Void
- Progress:任务进度,不需要使用Void
- Result:返回类型,没有返回值使用Void
public class UserRepository {
private UserDao userDao;
private Context context;
public UserRepository(Context context) {
this.context = context.getApplicationContext();
UserDatabase centerDataBase = UserDatabase.getInstance(context.getApplicationContext());
userDao = centerDataBase.userDao();
}/**
* 查询所有用户
*
* @return
*/
public List queryUser() {
if (userDao == null) {
Log.e("UserRepository", "QueryUser: userDao is null");
return Collections.EMPTY_LIST;
}
List users = new QueryUserTask(userDao).doInBackground();
return users;
}
}
使用Repository是为了专门处理本地缓存数据和网络数据,统一数据。
8. 调用
public class MainActivity extends AppCompatActivity {private TextView query;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
query = findViewById(R.id.tv_query_all);
// 触发
query.setOnClickListener(v -> queryUser());
}private void queryUser(){
// 子线程
new Thread(() -> {
UserRepository userRepository = new UserRepository(getApplicationContext());
// 查询用户
List users = userRepository.queryUser();
// 更新ui
mHandler.sendEmptyMessage(users.size());
}).start();
}private Handler mHandler = new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
query.setText("已查询" + msg.what + "条数据");
}
};
}
数据库操作时需要在子线程中进行,操作完成后若需更新UI,则需切换到主线程进行UI刷新。若是数据库操作比较耗时,在主线程中操作,容易出现ANR。若一定要在主线程进行操作,则在UserDatabase实例时,需要调用allowMainThreadQueries():
// 单例
public static UserDatabase getInstance(Context context) {
if (INSTANCE == null) {
synchronized (UserDatabase.class) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(), UserDatabase.class, DATABASE_NAME)
.fallbackToDestructiveMigration()// 暴力升级,可能会出现数据丢失
.allowMainThreadQueries()// 允许在主线程操作
.build();
}
}
return INSTANCE;
}
这里使用了Handler进行线程切换,msg.what一般用户区分消息类型,若需要传递数据需要使用Message包裹对象,不可像代码中那样使用,代码中使用msg.what传递消息内容是为了简单方便,但显得不专业,不建议使用。主线程更新也可使用runOnUiThread(Runnable action),原理是一样的。
一般逻辑操作是放在ViewModel中进行,通常还有DataBinding,这里为了简单就直接在Activity中进行。
总结 Room使用看似有些繁琐,但相比于Android原生的SQLite还是简单不少,若是数据操作比较复杂,还是建议使用SQLite或其他三方库。
推荐阅读
- 安卓开发|安卓(自定义适配器)
- Android|Android - LayoutParams,小火箭,高内聚低耦合,回调,设备存储空间,获取应用信息
- 程序员|Android - LayoutParams,小火箭,高内聚低耦合,帮你突破瓶颈
- android.support.v4与Android.support.v7
- android中读取原始 Raw 资源
- Android mk简介
- Android ble蓝牙使用注意
- Android 弹性布局 FlexboxLayout了解一下
- Android-Java-进程与线程