这是我参与「第四届青训营」笔记创作活动的第3天
1. Room简介
处理大量结构化数据的应用可极大地受益于在本地保留这些数据。最常见的使用场景是缓存相关的数据,这样一来,当设备无法访问网络时,用户仍然可以在离线状态下浏览该内容。
Room 持久性库在 SQLite 上提供了一个抽象层,以便在充分利用 SQLite 的强大功能的同时,能够流畅地访问数据库。具体来说,Room 具有以下优势:
- 针对 SQL 查询的编译时验证。
- 可最大限度减少重复和容易出错的样板代码的方便注解。
- 简化了数据库迁移路径。
主要组件
Room 包含三个主要组件:
- 数据库类(Database),用于保存数据库并作为应用持久性数据底层连接的主要访问点。
- 数据实体(Entity),用于表示应用的数据库中的表。
- 数据访问对象 (DAO),提供您的应用可用于查询、更新、插入和删除数据库中的数据的方法。 数据库类为应用提供与该数据库关联的 DAO 的实例。反过来,应用可以使用 DAO 从数据库中检索数据,作为关联的数据实体对象的实例。此外,应用还可以使用定义的数据实体更新相应表中的行,或者创建新行供插入。
2.Room使用示例
提示:本示例不止使用了3大组件,还另外添加了repository,viewmodel,以及简单多线程的使用;
- 在项目的
build.gradle(Module:app)中添加以下依赖:
// ViewModel and LiveData
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.5.1'
implementation 'androidx.lifecycle:lifecycle-livedata:2.5.1'
// Room
implementation 'androidx.room:room-runtime:2.4.3'
annotationProcessor 'androidx.room:room-compiler:2.4.3'
在本次示例中用到了视图绑定,所以还需要添加:
buildFeatures {
viewBinding true
}
然后点击Sync Now;
- 接下来去设计
activity_main.xml这里代码比较多,贴部分示例,以及截图如下:
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/id_textField"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Id">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="number" />
</com.google.android.material.textfield.TextInputLayout>
这里是使用了TextInputLayout,这是一个浮动标签,同时在其内部包裹了TextInputEditText;
什么是浮动标签呢,上面截图就是静态的一般情况下,然后当你点击某个文本框输入的时候,会出现如下效果:
3.下面该到后台代码编写环节了;
@Entity//数据库实体
public class Customer {
@PrimaryKey(autoGenerate = true)//自增主键
public int uid;
@ColumnInfo(name = "first_name")
@NonNull
public String firstName;
@ColumnInfo(name = "last_name")
@NonNull
public String lastName;
public double salary;
public Customer( @NonNull String firstName, @NonNull String lastName, double
salary) {
this.firstName=firstName;
this.lastName=lastName;
this.salary = salary;
}
}
有两种类型的Dao方法可以定义数据库交互:
- Room提供了简单的注解,可以无需写SQL语句即可执行简单的增删查改操作;
- 如果需要定义更复杂的增删查改操作,则可以使用
@query注解,然后自己按需编写SQL语句
@Dao
public interface CustomerDAO {
@Query("SELECT * FROM customer ORDER BY last_name ASC")//自己写SQL
LiveData<List<Customer>> getAll();
@Query("SELECT * FROM customer WHERE uid = :customerId LIMIT 1")
Customer findByID(int customerId);
@Insert//纯注解,简单SQL
void insert(Customer customer);
@Delete
void delete(Customer customer);
@Update
void updateCustomer(Customer customer);
@Query("DELETE FROM customer")
void deleteAll();
}
接下来该是database了:
@Database(entities = {Customer.class}, version = 1, exportSchema = false)
public abstract class CustomerDatabase extends RoomDatabase {
public abstract CustomerDAO customerDao();
private static CustomerDatabase INSTANCE;
//创建了一个带有线程池的ExecutorService,以便后面在后台线程上异步执行数据库操作
private static final int NUMBER_OF_THREADS = 4;
public static final ExecutorService databaseWriteExecutor = Executors.newFixedThreadPool(NUMBER_OF_THREADS);
//多线程环境中的同步方法意味着不允许两个线程同时访问数据
//下面即单例模式打开数据库,防止打开多个数据库实例
public static synchronized CustomerDatabase getInstance(final Context context) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
CustomerDatabase.class, "CustomerDatabase")
.fallbackToDestructiveMigration()
.build();
}
return INSTANCE;
}
}
entity就是对应的实体类,exportSchema默认为true,以保存数据库版本历史记录,但它要求您在defaultConfig下的module level gradle file中提供文件夹的详细信息;不想要保存历史版本可以设置为false。
除了主要的三组件外,还额外创建了repository类,我们使用Repository类为应用程序其余部分的数据访问提供了良好的API。我们使用它来减少room类之间的依赖关系,并更好地管理查询;
说的通俗一点,我们为什么需要repository呢;屏蔽细节。
上层(activity/fragment/presenter)不需要知道数据的细节(或者说 - 数据源),来自于网络、数据库,亦或是内存等等。如此,一来上层可以不用关心细节,二来底层可以根据需求修改,不会影响上层,两者的分离用可以帮助协同开发。\
贴上部分代码:
public class CustomerRepository {
private CustomerDAO customerDao;
private LiveData<List<Customer>> allCustomers;
public CustomerRepository(Application application){
CustomerDatabase db = CustomerDatabase.getInstance(application);
customerDao =db.customerDao();
allCustomers= customerDao.getAll();
}
}
可以看到在这里,对于插入、更新和删除等每个操作,我们都有在后台执行的代码(我们不在主线程上运行)。Room在一个单独的线程上执行所有查询(从4个线程池中重用)。
public void insert(final Customer customer){
CustomerDatabase.databaseWriteExecutor.execute(new Runnable() {
@Override
public void run() {
customerDao.insert(customer);
}
});
}
再使用LiveData和ViewModel来实现观察者模式。为了使用Room实现LiveData,我们添加了ViewModel类,该类将包含对LiveData的引用。
最后一步,该到MainActivity了,挑部分来说一下:
customerViewModel = ViewModelProvider.AndroidViewModelFactory.getInstance(getApplication()).create(CustomerViewModel.class);
customerViewModel.getAllCustomers().observe(this, new Observer<List<Customer>>() {
@Override
public void onChanged(@Nullable final List<Customer> customers) {
String allCustomers = "";
for (Customer temp : customers) {
String customerDetails = (temp.uid + " " + temp.firstName + " " + temp.lastName + " " + temp.salary);
allCustomers = allCustomers + System.getProperty("line.separator") + customerDetails;
}
binding.textViewRead.setText("All data: " + allCustomers);
}
});
这就是典型的通过livedata观察者模式,更改了数据后及时通知,并更改All data显示内容;
binding.addButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
String name = binding.nameTextField.getEditText().getText().toString();
String surname = binding.surnameTextField.getEditText().getText().toString();
String strSalary = binding.salaryTextField.getEditText().getText().toString();
if ((!name.isEmpty() && name!= null)&& (!surname.isEmpty() &&
strSalary!=null) && (!strSalary.isEmpty() && surname!=null)) {
double salary = Double.parseDouble(strSalary);
Customer customer = new Customer(name, surname, salary);
customerViewModel.insert(customer);
binding.textViewAdd.setText("Added Record: " + name + " " + surname + " " + salary);
}
}
});
添加数据的一些逻辑操作及判断; 其他的均类似这样的操作,不再赘述;
效果:
添加:
更新:
删除并清空文本框:
今天就到此为止啦~