Room | 青训营笔记

91 阅读5分钟

这是我参与「第四届青训营」笔记创作活动的第3天

1. Room简介

处理大量结构化数据的应用可极大地受益于在本地保留这些数据。最常见的使用场景是缓存相关的数据,这样一来,当设备无法访问网络时,用户仍然可以在离线状态下浏览该内容。

Room 持久性库在 SQLite 上提供了一个抽象层,以便在充分利用 SQLite 的强大功能的同时,能够流畅地访问数据库。具体来说,Room 具有以下优势:

  • 针对 SQL 查询的编译时验证。
  • 可最大限度减少重复和容易出错的样板代码的方便注解。
  • 简化了数据库迁移路径。

主要组件

Room 包含三个主要组件:

  • 数据库类(Database),用于保存数据库并作为应用持久性数据底层连接的主要访问点。
  • 数据实体(Entity),用于表示应用的数据库中的表。
  • 数据访问对象 (DAO),提供您的应用可用于查询、更新、插入和删除数据库中的数据的方法。 数据库类为应用提供与该数据库关联的 DAO 的实例。反过来,应用可以使用 DAO 从数据库中检索数据,作为关联的数据实体对象的实例。此外,应用还可以使用定义的数据实体更新相应表中的行,或者创建新行供插入。

2.Room使用示例

提示:本示例不止使用了3大组件,还另外添加了repository,viewmodel,以及简单多线程的使用;

  1. 在项目的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

  1. 接下来去设计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>

F$H7MJ0D(5PXAC`856MTTSS.png

这里是使用了TextInputLayout,这是一个浮动标签,同时在其内部包裹了TextInputEditText
什么是浮动标签呢,上面截图就是静态的一般情况下,然后当你点击某个文本框输入的时候,会出现如下效果:

1FC9B4447384DF7EDD4829E578C79C63.jpg 3.下面该到后台代码编写环节了;

image.png

@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);
        }
    }
});

添加数据的一些逻辑操作及判断; 其他的均类似这样的操作,不再赘述;

效果:
添加: 4464CA3F0470AD8632B46426647D780E.jpg 更新:

6C97D8B14D4B01287040838C002FAB31.jpg 删除并清空文本框:

B7CA3E84B1E27DAF682138645BE726EF.jpg

今天就到此为止啦~