2025-11-25 17:46:22,某些文章具有时效性,若有错误或已失效,请在下方留言。Room Database 是 Android 官方提供的本地数据库框架,是 Jetpack 架构组件的一部分,用于在应用中以安全、简洁、高效的方式存储本地数据。它在 SQLite 之上提供了更高层的抽象,让开发者更容易读写数据库,并减少样板代码。
核心概念
Room Database 有三个主要的核心概念
- Entity
- DAO(Database Access Object)
- Database
Entity 实体
- 对应数据库中的一张表
- 每个字段对应表中的一列
- 使用
@Entity注解定义
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity
public class User {
@PrimaryKey(autoGenerate = true)
public int uid;
public String name;
public int age;
}
DAO 数据库访问对象
- 定义访问数据库的方法(增删改查)
Room会根据DAO自动生成实际SQL代码- 使用
@Dao注解
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import java.util.List;
@Dao
public interface UserDao {
@Query("SELECT * FROM User")
List<User> getAll();
@Insert
void insert(User user);
@Delete
void delete(User user);
}
Database 数据库定义
- 继承
RoomDatabase - 使用
@Database注解 - 将所有
Entity与DAO注册进去
import androidx.room.Database;
import androidx.room.RoomDatabase;
@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
}
代码示例
1. 声明依赖
在 bundle.gradle.kts 文件中,添加 ROOM 相关的依赖。
dependencies {
implementation(libs.appcompat)
implementation(libs.material)
implementation(libs.activity)
implementation(libs.constraintlayout)
testImplementation(libs.junit)
androidTestImplementation(libs.ext.junit)
androidTestImplementation(libs.espresso.core)
// ROOM 相关的声明
val room_version = "2.8.4"
implementation("androidx.room:room-runtime:$room_version")
// If this project only uses Java source, use the Java annotationProcessor
annotationProcessor("androidx.room:room-compiler:$room_version")
}
添加完成后,点击 Sync Now 按钮,开始同步配置。
2. 创建 Entity 实体
Entity 是表示 SQLite 数据库中的表的基本组件。新建 Contacts 实体,实体包含 id、name 以及 email。
package com.stewednoodles.contactsmanagerapp;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
/**
* 实体类
* @Entity: 将 tableName 与实体类 Contacts 进行映射
*/
@Entity(tableName = "contacts_table")
public class Contacts {
/**
* @ColumnInfo: 将列名与实体类中的字段进行映射
* @PrimaryKey: 设置主键
*/
@ColumnInfo(name = "contact_id") // 设置列名 数据库表中的 contact_id 与 Contacts 实体的 id 进行 映射
@PrimaryKey(autoGenerate = true) // 设置 主键,Entity 中必须设置主键
private int id;
@ColumnInfo(name = "contact_name")
private String name;
@ColumnInfo(name = "contact_email")
private String email;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Contacts(int id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
public Contacts() {
}
}
3. 创建 DAO
DAO 是一个接口,定义了一系列用于对 Entity 数据库表执行数据库操作的方法,例如 inset、update、delete、query 以及其他操作。
DAO 是一个用 @Dao 注解的接口。
package com.stewednoodles.contactsmanagerapp;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import java.util.List;
@Dao
public interface ContactDAO {
/**
* 插入数据
* @param contact
*/
@Insert
void insert(Contacts contact);
/**
* 删除数据
* @param contact
*/
@Delete
void delete(Contacts contact);
@Query("SELECT * FROM contacts_table")
List<Contacts> getAllContacts();
}
4. 创建 Database 类
Database 类是一个抽象类,用作数据库的持有者。
package com.stewednoodles.contactsmanagerapp;
import android.content.Context;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
/**
* 定义了数据库中的实体(表)和版本号
* - 数据库变更时,可以更新版本号
*/
@Database(entities = {Contacts.class}, version = 1)
public abstract class ContactDatabase extends RoomDatabase {
/**
* 获取 DAO
* @return DAO 对象
*/
public abstract ContactDAO getContactDAO();
// Database 需要遵循单例模式
private static ContactDatabase instance;
/**
* 获取数据库实例
* @param context 上下文
* @return 数据库实例
*/
public static synchronized ContactDatabase getInstance(Context context) {
if (instance == null) {
instance = Room.databaseBuilder(
context.getApplicationContext(),
ContactDatabase.class,
"contacts_db"
).fallbackToDestructiveMigration().build();
}
return instance;
}
}
5. 创建 Repository
Repository 可以从不同的数据源(不同的 REST API、缓存、本地数据库存储)收集数据,并将这些数据提供给应用程序的其余部分。有效递将数据源与应用程序的其他部分隔离开来,并为应用程序的其余部分提供一个干净的数据访问 API。
DAO 中的方法从 Repository 执行。
package com.stewednoodles.contactsmanagerapp;
import java.util.List;
public class Repository {
// 提供有效的数据源
// - ROOM Database
private final ContactDAO contactDAO;
public Repository(ContactDAO contactDAO) {
this.contactDAO = contactDAO;
}
/**
* DAO 中的方法从 Repository 执行
*/
// 添加联系人
public void addContact(Contacts contact) {
contactDAO.insert(contact);
}
// 删除联系人
public void deleteContact(Contacts contact) {
contactDAO.delete(contact);
}
// 获取所有联系人
public List<Contacts> getAllContacts() {
return contactDAO.getAllContacts();
}
}
6. Handler、Executor & Runnable
数据库操作,不能放在主线程,否则会阻塞 UI。
package com.stewednoodles.contactsmanagerapp;
import android.os.Handler;
import android.os.Looper;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Repository {
// 提供有效的数据源
// - ROOM Database
private final ContactDAO contactDAO;
ExecutorService executor;
Handler handler;
public Repository(ContactDAO contactDAO) {
this.contactDAO = contactDAO;
// 用于数据库的后台操作
executor = Executors.newSingleThreadExecutor();
// 用于前台 UI 更新
handler = new Handler(Looper.getMainLooper());
}
/**
* DAO 中的方法从 Repository 执行
*/
// 添加联系人
public void addContact(Contacts contact) {
// Runnable: 在独立线程运行任务
executor.execute(new Runnable() {
@Override
public void run() {
// 在独立的线程中,异步执行
contactDAO.insert(contact);
}
});
}
// 删除联系人
public void deleteContact(Contacts contact) {
executor.execute(new Runnable() {
@Override
public void run() {
contactDAO.delete(contact);
}
});
}
// 获取所有联系人
public List<Contacts> getAllContacts() {
return contactDAO.getAllContacts();
}
}
Handler
Handler 的作用是在特定线程(通常是主线程)中处理消息或执行代码。主要用途:
- 主线程更新 UI
- 切换新城
- 发送消息、延迟执行任务
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
// 在主线程执行
}
});
Executor
Executor 是一个执行 Runnable 的线程池框架。
Executor executor = Executors.newSingleThreadExecutor();
executor.execute(new Runnable() {
@Override
public void run() {
// 在线程池的线程中执行
}
});
Runnable
Runnable 是一个接口,表示可以在线程中执行的一段代码。
Runnable task = new Runnable() {
@Override
public void run() {
// 要在线程中执行的代码
}
};
7. Live Data
Live Data 通常用于 Repository 和 DAO 中,以便观察数据库的更改,并为 UI 提供实时更新。
package com.stewednoodles.contactsmanagerapp;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import java.util.List;
@Dao
public interface ContactDAO {
/**
* 插入数据
* @param contact
*/
@Insert
void insert(Contacts contact);
/**
* 删除数据
* @param contact
*/
@Delete
void delete(Contacts contact);
@Query("SELECT * FROM contacts_table")
LiveData<List<Contacts>> getAllContacts();
}
package com.stewednoodles.contactsmanagerapp;
import android.app.Application;
import android.os.Handler;
import android.os.Looper;
import androidx.lifecycle.LiveData;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Repository {
// 提供有效的数据源
// - ROOM Database
private final ContactDAO contactDAO;
ExecutorService executor;
Handler handler;
public Repository(Application application) {
ContactDatabase instance = ContactDatabase.getInstance(application);
this.contactDAO = instance.getContactDAO();
// 用于数据库的后台操作
executor = Executors.newSingleThreadExecutor();
// 用于前台 UI 更新
handler = new Handler(Looper.getMainLooper());
}
/**
* DAO 中的方法从 Repository 执行
*/
// 添加联系人
public void addContact(Contacts contact) {
// Runnable: 在独立线程运行任务
executor.execute(new Runnable() {
@Override
public void run() {
// 在独立的线程中,异步执行
contactDAO.insert(contact);
}
});
}
// 删除联系人
public void deleteContact(Contacts contact) {
executor.execute(new Runnable() {
@Override
public void run() {
contactDAO.delete(contact);
}
});
}
// 获取所有联系人
public LiveData<List<Contacts>> getAllContacts() {
return contactDAO.getAllContacts();
}
}
8. ViewModel
ViewModel 的作用是获取并保存 Activity 或 Fragment 所需要的信息。它作为 Model 与 View 之间的连接桥梁,负责将来自 Model 的数据进行转换,并向 View 提供数据流。
Activity 或 Fragment 应当能够观察 ViewModel 中的数据变化。ViewModel 通常通过 LiveData 或 Android Data Binding 来暴露这些信息。
package com.stewednoodles.contactsmanagerapp;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import java.util.List;
public class MyViewModel extends AndroidViewModel {
private Repository repository;
public MyViewModel(@NonNull Application application) {
super(application);
this.repository = new Repository(application);;
}
public void addContact(Contacts contact) {
repository.addContact(contact);
}
public void deleteContact(Contacts contact) {
repository.deleteContact(contact);
}
public LiveData<List<Contacts>> getAllContacts() {
return repository.getAllContacts();
}
}
9. View
开启 Data Binding
buildFeatures {
dataBinding = true
}
页面布局
主页面布局文件的内容
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycleView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/floatingActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="35dp"
android:layout_marginBottom="40dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
联系人列表页面布局
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_margin="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/text_view_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Name"
android:textSize="32sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/text_view_email"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Email"
android:textSize="24sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_view_name" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
10. 数据绑定
列表页面数据绑定的代码,如下所示
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 数据绑定 -->
<data>
<variable
name="contact"
type="com.stewednoodles.contactsmanagerapp.Contacts" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_margin="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/text_view_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@{contact.name}"
android:textSize="32sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/text_view_email"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@{contact.email}"
android:textSize="24sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_view_name" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
主页面悬浮按钮点击事件的绑定
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="clickHandler"
type="com.stewednoodles.contactsmanagerapp.MainActivityClickHandler" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycleView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:onClick="@{clickHandler::onFABClick}"
android:id="@+id/floatingActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="35dp"
android:layout_marginBottom="40dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
RecyclerView 数据绑定
RecyclerView 数据绑定的代码,如下所示
package com.stewednoodles.contactsmanagerapp;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.databinding.DataBindingUtil;
import androidx.databinding.ViewDataBinding;
import androidx.recyclerview.widget.RecyclerView;
import com.stewednoodles.contactsmanagerapp.databinding.ContactListItemBinding;
import java.util.ArrayList;
import java.util.List;
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ContactViewHolder> {
private ArrayList<Contacts> contacts;
public MyAdapter(ArrayList<Contacts> contacts) {
this.contacts = contacts;
}
public void setContacts(ArrayList<Contacts> contacts) {
this.contacts = contacts;
// 通知关联的 RecyclerView,其底层数据集已经发生变化,RecyclerView 应刷新其视图以反映这些变化。
notifyDataSetChanged();
}
@NonNull
@Override
public ContactViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ContactListItemBinding binding = DataBindingUtil.inflate(
LayoutInflater.from(parent.getContext()),
R.layout.contact_list_item, parent,
false
);
return new ContactViewHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull ContactViewHolder holder, int position) {
Contacts contact = contacts.get(position);
holder.contactListItemBinding.setContact(contact);
}
@Override
public int getItemCount() {
return contacts != null ? contacts.size() : 0;
}
class ContactViewHolder extends RecyclerView.ViewHolder {
private ContactListItemBinding contactListItemBinding;
public ContactViewHolder(@NonNull ContactListItemBinding contactListItemBinding) {
super(contactListItemBinding.getRoot());
this.contactListItemBinding = contactListItemBinding;
}
}
}













暂无评论内容