Android Room简要指南---摘自官方指南

一: 使用 Room 将数据保存到本地数据库

注:本文摘自安卓开发文档,仅供学习使用,链接如下:安卓开发文档ROOM

Room 在 SQLite 上提供了一个抽象层,以便在充分利用 SQLite 的强大功能的同时,能够流畅地访问数据库。

处理大量结构化数据的应用可极大地受益于在本地保留这些数据。最常见的用例是缓存相关数据。这样,当设备无法访问网络时,用户仍可在离线状态下浏览相应内容。设备之后重新连接到网络后,用户发起的所有内容更改都会同步到服务器。

*注意:要在应用中使用 Room,请在应用的 build.gradle 文件中声明 Room 依赖项。链接如下:*https://developer.android.google.cn/jetpack/androidx/releases/room#declaring_dependencies

Room 包含 3 个主要组件:

1.数据库:包含数据库持有者,并作为应用已保留的持久关系型数据的底层连接的主要接入点。

使用 @Database 注释的类应满足以下条件:

是扩展 RoomDatabase 的抽象类。
在注释中添加与数据库关联的实体列表。
包含具有 0 个参数且返回使用 @Dao 注释的类的抽象方法。
在运行时,您可以通过调用 Room.databaseBuilder() 或 Room.inMemoryDatabaseBuilder() 获取 Database 的实例。

2.Entity:表示数据库中的表。

3.DAO:包含用于访问数据库的方法。

应用使用 Room 数据库来获取与该数据库关联的数据访问对象 (DAO)。然后,应用使用每个 DAO 从数据库中获取实体,然后再将对这些实体的所有更改保存回数据库中。最后,应用使用实体来获取和设置与数据库中的表列相对应的值。

Room 不同组件之间的关系如图 1 所示:
在这里插入图片描述

图 1. Room 架构图

以下代码段包含具有一个实体和一个 DAO 的示例数据库配置。
User

@Entity
    public class User {
        @PrimaryKey
        public int uid;

        @ColumnInfo(name = "first_name")
        public String firstName;

        @ColumnInfo(name = "last_name")
        public String lastName;
    }

UserDao

@Dao
    public interface UserDao {
        @Query("SELECT * FROM user")
        List<User> getAll();

        @Query("SELECT * FROM user WHERE uid IN (:userIds)")
        List<User> loadAllByIds(int[] userIds);

        @Query("SELECT * FROM user WHERE first_name LIKE :first AND " +
               "last_name LIKE :last LIMIT 1")
        User findByName(String first, String last);

        @Insert
        void insertAll(User... users);

        @Delete
        void delete(User user);
    }
    

创建上述文件后,您可以使用以下代码获取已创建的数据库的实例:

AppDatabase db = Room.databaseBuilder(getApplicationContext(),
            AppDatabase.class, "database-name").build();

二:使用 Room 实体定义数据

使用 Room 持久性库时,您可以将相关字段集定义为实体。对于每个实体,系统会在关联的 Database 对象中创建一个表来存储这些项。您必须通过 Database 类中的 entities 数组引用实体类。

注意:要在应用中使用实体,请向应用的 build.gradle 文件中添加架构组件工件。

以下代码段展示了如何定义实体:

@Entity
public class User {
    @PrimaryKey
    public int id;

    public String firstName;
    public String lastName;
}

要保留某个字段,Room 必须拥有该字段的访问权限。您可以将某个字段设为公开字段,也可以为其提供 getter 和 setter。如果您使用 getter 和 setter 方法,则请注意,这些方法需遵循 Room 中的 JavaBeans 规范。

注意:实体可以具有空的构造函数(如果相应的 DAO 类可以访问保留的每个字段),也可以具有其参数包含的类型和名称与该实体中字段的类型和名称一致的构造函数。Room 还可以使用完整或部分构造函数,例如仅接收部分字段的构造函数。

1.使用主键
每个实体必须将至少 1 个字段定义为主键。即使只有 1 个字段,您仍然需要为该字段添加 @PrimaryKey 注释。此外,如果您想让 Room 为实体分配自动 ID,则可以设置 @PrimaryKey 的 autoGenerate 属性。如果实体具有复合主键,您可以使用 @Entity 注释的 primaryKeys 属性,如以下代码段所示:

 @Entity(primaryKeys = {"firstName", "lastName"})
    public class User {
        public String firstName;
        public String lastName;
    }

默认情况下,Room 将类名称用作数据库表名称。如果您希望表具有不同的名称,请设置 @Entity 注释的 tableName 属性,如以下代码段所示:

@Entity(tableName = "users")
    public class User {
        // ...
    }

注意:SQLite 中的表名称不区分大小写

与 tableName 属性类似,Room 将字段名称用作数据库中的列名称。如果您希望列具有不同的名称,请将 @ColumnInfo 注释添加到字段,如以下代码段所示:

 @Entity(tableName = "users")
    public class User {
        @PrimaryKey
        public int id;

        @ColumnInfo(name = "first_name")
        public String firstName;

        @ColumnInfo(name = "last_name")
        public String lastName;
    }

2.忽略字段
默认情况下,Room 会为在实体中定义的每个字段创建一个列。如果某个实体中有您不想保留的字段,则可以使用 @Ignore 为这些字段注释,如以下代码段所示:

@Entity
    public class User {
        @PrimaryKey
        public int id;

        public String firstName;
        public String lastName;

        @Ignore
        Bitmap picture;
    }

如果实体继承了父实体的字段,则使用 @Entity 属性的 ignoredColumns 属性通常会更容易:

 @Entity(ignoredColumns = "picture")
    public class RemoteUser extends User {
        @PrimaryKey
        public int id;

        public boolean hasVpn;
    }

3.提供表搜索支持
Room 支持多种类型的注释,可让您更轻松地搜索数据库表中的详细信息。除非应用的 minSdkVersion 低于 16,否则请使用全文搜索。

4.支持全文搜索
如果您的应用需要通过全文搜索 (FTS) 快速访问数据库信息,请使用虚拟表(使用 FTS3 或 FTS4 SQLite 扩展模块)为您的实体提供支持。要使用 Room 2.1.0 及更高版本中提供的这项功能,请将 @Fts3 或 @Fts4 注释添加到给定实体,如以下代码段所示:

  // Use `@Fts3` only if your app has strict disk space requirements or if you
    // require compatibility with an older SQLite version.
    @Fts4
    @Entity(tableName = "users")
    public class User {
        // Specifying a primary key for an FTS-table-backed entity is optional, but
        // if you include one, it must use this type and column name.
        @PrimaryKey
        @ColumnInfo(name = "rowid")
        public int id;

        @ColumnInfo(name = "first_name")
        public String firstName;
    }

注意:启用 FTS 的表始终使用 INTEGER 类型的主键且列名称为“rowid”。如果是由 FTS 表支持的实体定义主键,则必须使用相应的类型和列名称。

5.添加基于 AutoValue 的对象
注意:此功能旨在用于基于 Java 的实体。要在基于 Kotlin 的实体中实现相同的功能,最好改用数据类。
在 Room 2.1.0 及更高版本中,您可以将基于 Java 的不可变值类(使用 @AutoValue 为其注释)用作应用的数据库中的实体。此支持在实体的两个实例被视为相等(如果这两个实例的列包含相同的值)时尤为有用。

将带有 @AutoValue 注释的类用作实体时,您可以使用 @PrimaryKey、@ColumnInfo、@Embedded 和 @Relation 为类的抽象方法注释。不过,您必须在每次使用这些注释时添加 @CopyAnnotations 注释,以便 Room 可以正确解释这些方法的自动生成实现。

以下代码段展示了一个使用 @AutoValue 注释的类(Room 将其标识为实体)的示例:

User.java

@AutoValue
    @Entity
    public abstract class User {
        // Supported annotations must include `@CopyAnnotations`.
        @CopyAnnotations
        @PrimaryKey
        public abstract long getId();

        public abstract String getFirstName();
        public abstract String getLastName();

        // Room uses this factory method to create User objects.
        public static User create(long id, String firstName, String lastName) {
            return new AutoValue_User(id, firstName, lastName);
        }
    }

三:在数据库中创建视图

2.1.0 及更高版本的 Room 持久性库为 SQLite 数据库视图提供了支持,从而允许您将查询封装到类中。Room 将这些查询支持的类称为视图,在 DAO 中使用时,它们的行为与简单数据对象的行为相同。

注意:与实体类似,您可以针对视图运行 SELECT 语句。不过,您无法针对视图运行 INSERT、UPDATE 或 DELETE 语句。

1.创建视图
要创建视图,请将 @DatabaseView 注释添加到类中。将注释的值设为类应该表示的查询。

以下代码段提供了一个视图示例:

 @DatabaseView("SELECT user.id, user.name, user.departmentId," +
                  "department.name AS departmentName FROM user " +
                  "INNER JOIN department ON user.departmentId = department.id")
    public class UserDetail {
        public long id;
        public String name;
        public long departmentId;
        public String departmentName;
    }

2.将视图与数据库相关联
要将此视图添加为应用数据库的一部分,请在应用的 @Database 注释中添加 views 属性:

@Database(entities = {User.class}, views = {UserDetail.class},
          version = 1)
public abstract class AppDatabase extends RoomDatabase {
    public abstract UserDao userDao();
}

四:使用 Room DAO 访问数据

要使用 Room 持久性库访问应用的数据,您需要使用数据访问对象 (DAO)。这些 Dao 对象构成了 Room 的主要组件,因为每个 DAO 都包含一些方法,这些方法提供对应用数据库的抽象访问权限。

通过使用 DAO 类(而不是查询构建器或直接查询)访问数据库,您可以拆分数据库架构的不同组件。此外,借助 DAO,您可以在测试应用时轻松模拟数据库访问。

注意:在将 DAO 类添加到您的应用之前,请先向应用的 build.gradle 文件中添加架构组件工件。

DAO 既可以是接口,也可以是抽象类。如果是抽象类,则该 DAO 可以选择有一个以 RoomDatabase 为唯一参数的构造函数。Room 会在编译时创建每个 DAO 实现。

注意:除非您对构建器调用 allowMainThreadQueries(),否则 Room 不支持在主线程上访问数据库,因为它可能会长时间锁定界面。异步查询(返回 LiveData 或 Flowable 实例的查询)无需遵守此规则,因为此类查询会根据需要在后台线程上异步运行查询。

1.定义方法以方便使用
您可以使用 DAO 类表示多个便捷查询。本文档提供了几个常见示例。

1).Insert
当您创建 DAO 方法并使用 @Insert 对其进行注释时,Room 会生成一个实现,该实现在单个事务中将所有参数插入到数据库中。

以下代码段展示了几个示例查询:

 @Dao
    public interface MyDao {
        @Insert(onConflict = OnConflictStrategy.REPLACE)
        public void insertUsers(User... users);

        @Insert
        public void insertBothUsers(User user1, User user2);

        @Insert
        public void insertUsersAndFriends(User user, List<User> friends);
    }

如果 @Insert 方法只接收 1 个参数,则可返回 long,这是插入项的新 rowId。如果参数是数组或集合,则应返回 long[] 或 List。

如需了解详情,请参阅 @Insert 注释的参考文档以及 rowid 表格的 SQLite 文档。

2).Update
Update 便捷方法会修改数据库中以参数形式给出的一组实体。它使用与每个实体的主键匹配的查询。

以下代码段演示了如何定义此方法:

 @Dao
    public interface MyDao {
        @Update
        public void updateUsers(User... users);
    }

虽然通常没有必要,但您可以让此方法返回一个 int 值,表示数据库中更新的行数。

3).Delete
Delete 便捷方法会从数据库中删除一组以参数形式给出的实体。它使用主键查找要删除的实体。

以下代码段演示了如何定义此方法:

@Dao
    public interface MyDao {
        @Delete
        public void deleteUsers(User... users);
    }

虽然通常没有必要,但您可以让此方法返回一个 int 值,表示从数据库中删除的行数。

2.查询信息
@Query 是 DAO 类中使用的主要注释。它允许您对数据库执行读/写操作。每个 @Query 方法都会在编译时进行验证,因此如果查询出现问题,则会发生编译错误,而不是运行时失败。

Room 还会验证查询的返回值,这样的话,当返回的对象中的字段名称与查询响应中的对应列名称不匹配时,Room 会通过以下两种方式之一提醒您:

如果只有部分字段名称匹配,则会发出警告。
如果没有任何字段名称匹配,则会发出错误。

1).简单查询

 @Dao
    public interface MyDao {
        @Query("SELECT * FROM user")
        public User[] loadAllUsers();
    }

这是一个极其简单的查询,可加载所有用户。在编译时,Room 知道它在查询用户表中的所有列。如果查询包含语法错误,或者数据库中没有用户表格,则 Room 会在您的应用编译时显示包含相应消息的错误。

2).将参数传递给查询
在大多数情况下,您需要将参数传递给查询以执行过滤操作,例如仅显示某个年龄以上的用户。要完成此任务,请在 Room 注释中使用方法参数,如以下代码段所示:

@Dao
public interface MyDao {
    @Query("SELECT * FROM user WHERE age > :minAge")
    public User[] loadAllUsersOlderThan(int minAge);
}

在编译时处理此查询时,Room 会将 :minAge 绑定参数与 minAge 方法参数相匹配。Room 通过参数名称进行匹配。如果有不匹配的情况,则应用编译时会出现错误。

您还可以在查询中传递多个参数或多次引用这些参数,如以下代码段所示:

 @Dao
    public interface MyDao {
        @Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge")
        public User[] loadAllUsersBetweenAges(int minAge, int maxAge);

        @Query("SELECT * FROM user WHERE first_name LIKE :search " +
               "OR last_name LIKE :search")
        public List<User> findUserWithName(String search);
    }

3).返回列的子集
大多数情况下,您只需获取实体的几个字段。例如,您的界面可能仅显示用户的名字和姓氏,而不是用户的每一条详细信息。通过仅提取应用界面中显示的列,您可以节省宝贵的资源,并且您的查询也能更快完成。

借助 Room,您可以从查询中返回任何基于 Java 的对象,前提是结果列集合会映射到返回的对象。例如,您可以创建以下基于 Java 的普通对象 (POJO) 来获取用户的名字和姓氏:

public class NameTuple {
        @ColumnInfo(name = "first_name")
        public String firstName;

        @ColumnInfo(name = "last_name")
        @NonNull
        public String lastName;
    }

现在,您可以在查询方法中使用此 POJO:

 @Dao
    public interface MyDao {
        @Query("SELECT first_name, last_name FROM user")
        public List<NameTuple> loadFullName();
    }

Room 知道该查询会返回 first_name 和 last_name 列的值,并且这些值会映射到 NameTuple 类的字段。因此,Room 可以生成正确的代码。如果查询返回太多的列,或者返回 NameTuple 类中不存在的列,则 Room 会显示一条警告。

注意:这些 POJO 也可以使用 @Embedded 注释

五:了解 Room 为何不允许对象引用

要点:Room 不允许实体类之间进行对象引用。因此,您必须明确请求您的应用所需的数据。

映射从数据库到相应对象模型之间的关系是一种常见做法,极其适用于服务器端。即使程序在访问字段时加载字段,服务器仍然可以正常工作。

但在客户端,这种延迟加载是不可行的,因为它通常发生在界面线程上,并且在界面线程上查询磁盘上的信息会导致严重的性能问题。界面线程通常需要大约 16 毫秒来计算和绘制 Activity 的更新后的布局,因此,即使查询只用了 5 毫秒,您的应用仍然可能会用尽剩余的时间来绘制框架,从而导致明显的显示故障。如果有一个并行运行的单独事务,或者设备正在运行其他磁盘密集型任务,则查询可能需要更多时间才能完成。不过,如果您不使用延迟加载,则应用会抓取一些不必要的数据,从而导致内存消耗问题。

对象关系型映射通常将决定权留给开发者,以便他们可以针对自己的应用用例执行最合适的操作。开发者通常会决定在应用和界面之间共享模型。不过,这种解决方案并不能很好地扩展,因为界面会不断发生变化,共享模型会出现开发者难以预测和调试的问题。

例如,假设界面加载了 Book 对象的列表,其中每本图书都有一个 Author 对象。您最初可能设计让查询使用延迟加载,从而让 Book 实例检索作者。对 author 字段的第一次检索会查询数据库。一段时间后,您发现还需要在应用的界面中显示作者姓名。您可以轻松访问此名称,如以下代码段所示:

authorNameTextView.setText(book.getAuthor().getName());

不过,这种看似无害的更改会导致在主线程上查询 Author 表。

如果您事先查询作者信息,则在您不再需要这些数据时,就会很难更改数据加载方式。例如,如果应用的界面不再需要显示 Author 信息,则应用会有效地加载不再显示的数据,从而浪费宝贵的内存空间。如果 Author 类引用其他表(例如 Books),则应用的效率会进一步下降。

要使用 Room 同时引用多个实体,请改为创建包含每个实体的 POJO,然后编写用于联接相应表的查询。这种结构合理的模型结合 Room 强大的查询验证功能,可让您的应用在加载数据时消耗较少的资源,从而改善应用的性能和用户体验。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
您好!对于在Matlab中实现Room-FFT(室内快速傅里叶变换),您可以按照以下步骤进行操作: 1. 准备音频信号:首先,您需要获取或生成要进行FFT的音频信号。您可以使用`audioread`函数从音频文件中读取信号,或者使用Matlab提供的生成函数(如`sin`或`rand`)创建一个信号。确保信号的采样率与您的应用场景相匹配。 2. 加载房间响应:获取或模拟房间的冲击响应(即房间的频率响应)。这可以从真实环境中测量获得,或者使用声学模拟软件进行模拟。将冲击响应加载到Matlab中。 3. 执行FFT:使用Matlab中的FFT函数对音频信号和房间冲击响应进行傅里叶变换。可以使用`fft`函数或`fft2`函数,具体取决于您的信号是一维还是二维。如果信号是一维的,则使用一维FFT;如果信号是二维的(如音频信号为立体声),则使用二维FFT。 4. 频率响应处理:通过将音频信号的频域表示与房间冲击响应的频域表示相乘(逐点乘法),可以获得经过房间响应的音频信号的频域表示。这相当于在时域中将信号与冲击响应进行卷积操作。 5. 反变换:对经过频率域处理的信号进行反傅里叶变换,以获得时域中的输出信号。使用`ifft`函数或`ifft2`函数进行逆FFT操作。 6. 分析结果:分析反变换后的信号以了解音频在房间内的效果。您可以计算信号的频谱特征,比如声音的能量分布、频率响应曲线等。 请注意,上述步骤是一个基本框架,您可能需要根据具体情况进行修改或添加其他处理步骤。希望这对您有所帮助!如果您还有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值