Android Architecture Components 之 Room 篇,Android工程师跳槽经验分享

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

删除

@Delete 注解的方法可以删除一系列给定的 entity, 使用匹配的主键去查询更改这些 entity,可以返回 int 型的数据库删除行数:

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

查询

@Query 注解的方法可以让你方便地读写数据库,Room 会在编译时验证这个方法,所以如果查询有问题编译时就会报错。Room 还会验证查询的返回值,如果查询响应的字段名和返回对象的字段名不匹配,如果有些字段不匹配,你会看到警告,如果所有字段都不匹配,你会看到 error。下面是一个简单的查询,查询所有的用户:

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

如果你想要添加查询条件,可以使用 :参数名 的方式获取参数值:

@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 findUserWithName(String search);
    }

当然,查询条件集合也是支持的:

@Dao
public interface MyDao {
@Query(“SELECT first_name, last_name FROM user WHERE region IN (:regions)”)
public List loadUsersFromRegions(List regions);
}

很多时候,我们不需要查询表中的所有字段,我们只用到了 UI 用到的那几列,为了节省资源,也为了加快查询速度,我们就可以定义一个包含用到的字段的 POJO(这个 POJO 可以使用 @Embedded 注解) ,查询方法可以使用这个 POJO:

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

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

@Dao
public interface MyDao {
@Query(“SELECT first_name, last_name FROM user”)
public List loadFullName();
}

Room 也允许你方便地进行多表查询,如查询某个用户所借的所有书籍信息:

@Dao
public interface MyDao {
@Query("SELECT * FROM book "

  • "INNER JOIN loan ON loan.book_id = book.id "
  • "INNER JOIN user ON user.id = loan.user_id "
  • “WHERE user.name LIKE :userName”)
    public List findBooksBorrowedByNameSync(String userName);
    }

多表查询也能使用 POJO,如查询用户名和他的宠物名:

@Dao
public interface MyDao {
@Query("SELECT user.name AS userName, pet.name AS petName "

  • "FROM user, pet "
  • “WHERE user.id = pet.user_id”)
    public LiveData<List> loadUserAndPetNames();

// You can also define this class in a separate file, as long as you add the
// “public” access modifier.
static class UserPet {
public String userName;
public String petName;
}
}

查询方法的返回值可以是 LiveData 以便你能随着数据库的更新实时更新 UI,返回值也可以是 RxJava2PublisherFlowable(需要添加 android.arch.persistence.room:rxjava2 依赖),甚至可以是 Cursor(不建议直接使用 Cursor API )。

数据库的更新与迁移

随着应用功能的改变,你需要去更改 entity 和数据库,但很多时候,你不希望因此丢失数据库中已存在的的数据,尤其是无法从远程服务器恢复这些数据时。也就是说,如果你不提供必要的迁移操作,Room 将会重建数据库,数据库中所有的数据都将丢失。
为此, Room 允许你写一些 Migration 类去保护用户数据,每个 Migration 类指定一个 startVersionendVersion,在运行时,Room 会运行每个 Migration 类的 migrate() 方法,以正确的顺序将数据库迁移到最新版本:

Room.databaseBuilder(getApplicationContext(), MyDb.class, “database-name”)
.addMigrations(MIGRATION_1_2, MIGRATION_2_3).build();

static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE Fruit (id INTEGER, "

  • name TEXT, PRIMARY KEY(id))”);
    }
    };

static final Migration MIGRATION_2_3 = new Migration(2, 3) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE Book "

  • " ADD COLUMN pub_year INTEGER");
    }
    };

注意,为了保证迁移逻辑按预期运行,应该使用完整的查询而不是引用表示查询的常量。

在迁移过程完成后,Room 会验证 schema 以确保迁移正确的完成了,如果 Room 发现了问题,会抛出一个包含不匹配信息的异常。
迁移数据库是很重要也是无法避免的操作,如果迁移出错可能会导致你的应用陷入崩溃循环,为了保持应用的稳定性,你必须提前测试好迁移的整的过程。为了更好地测试,你需要添加 android.arch.persistence.room:testing 依赖,并且你需要导出数据库的 schema。在编译时,Room 会将你数据库的 schema 信息导出为 JSON 文件。为了导出 schema,你需要在 build.gradle 文件中设置 注解处理器属性room.schemaLocation:

android {

defaultConfig {

javaCompileOptions {
annotationProcessorOptions {
arguments = [“room.schemaLocation”:
“$projectDir/schemas”.toString()]
}
}
}
}

你需要将这个导出的 JSON 文件保存在版本控制系统中,因为这个文件代表了数据库的 schema 历史记录。同时你需要添加 schema 位置作为 asset 文件夹:

android {

sourceSets {
androidTest.assets.srcDirs += files(“$projectDir/schemas”.toString())
}
}

测试工具中的 MigrationTestHelper 类可以读这些 schema 文件,同时它也实现了 JUnit4 的 TestRule 接口,所以它可以管理创建数据库:

@RunWith(AndroidJUnit4.class)
public class MigrationTest {
private static final String TEST_DB = “migration-test”;

@Rule
public MigrationTestHelper helper;

public MigrationTest() {
helper = new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
MigrationDb.class.getCanonicalName(),
new FrameworkSQLiteOpenHelperFactory());
}

@Test
public void migrate1To2() throws IOException {
SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 1);

// db has schema version 1. insert some data using SQL queries.
// You cannot use DAO classes because they expect the latest schema.
db.execSQL(…);

// Prepare for the next version.
db.close();

// Re-open the database with version 2 and provide
// MIGRATION_1_2 as the migration process.
db = helper.runMigrationsAndValidate(TEST_DB, 2, true, MIGRATION_1_2);

// MigrationTestHelper automatically verifies the schema changes,
// but you need to validate that the data was migrated properly.
}
}

数据库的测试

写 JUnit 测试通常比 UI 测试更快更直观,利用 Room.inMemoryDatabaseBuilder 构造 in-memory 版本的数据库可以让你的测试更封闭:

@RunWith(AndroidJUnit4.class)
public class SimpleEntityReadWriteTest {
private UserDao mUserDao;
private TestDatabase mDb;

@Before
public void createDb() {
Context context = InstrumentationRegistry.getTargetContext();
mDb = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build();
mUserDao = mDb.getUserDao();
}

@After
public void closeDb() throws IOException {
mDb.close();
}

@Test
public void writeUserAndReadInList() throws Exception {
User user = TestUtil.createUser(3);
user.setName(“george”);
mUserDao.insert(user);
List byName = mUserDao.findUsersByName(“george”);
assertThat(byName.get(0), equalTo(user));
}
}

高级用法与技巧

TypeConverter

有些时候,我们需要把一些自定义数据类型存入数据库,或者在存入数据库前做一些类型转换,如我们需要把 Date 类型的字段作为 Unix 时间戳存入数据库:

public class Converters {
@TypeConverter
public static Date fromTimestamp(Long value) {
return value == null ? null : new Date(value);
}

@TypeConverter
public static Long dateToTimestamp(Date date) {
return date == null ? null : date.getTime();
}
}

然后使用 @TypeConverters 注解那些需要使用转换器的元素。如果注解了 Database,那么数据库中所有的 DaoEntity 都能使用它。如果注解了 Dao,那么 Dao 中所有的方法都能使用它。如果注解了 Entity,那么 Entity 中所有的字段都能使用它。如果注解了 POJO,那么 POJO 中所有的字段都能使用它。如果注解了 Entity 字段,那么只有这个 Entity 字段能使用它。如果注解了 Dao 方法,那么该 Dao 方法中所有的参数都能使用它。如果注解了 Dao 方法参数,那么只有这个参数能使用它:

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

查询的时候,你仍然可以用你的自定义类型,就像使用原语类型一样:

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

最后:学习总结——Android框架体系架构知识脑图(纯手绘xmind文档)

学完之后,若是想验收效果如何,其实最好的方法就是可自己去总结一下。比如我就会在学习完一个东西之后自己去手绘一份xmind文件的知识梳理大纲脑图,这样也可方便后续的复习,且都是自己的理解,相信随便瞟几眼就能迅速过完整个知识,脑补回来。

下方即为我手绘的Android框架体系架构知识脑图,由于是xmind文件,不好上传,所以小编将其以图片形式导出来传在此处,细节方面不是特别清晰。但可给感兴趣的朋友提供完整的Android框架体系架构知识脑图原件(包括上方的面试解析xmind文档)

除此之外,前文所提及的Alibaba珍藏版 Android框架体系架构 手写文档以及一本 《大话数据结构》 书籍等等相关的学习笔记文档,也皆可分享给认可的朋友!

——感谢大家伙的认可支持,请注意:点赞+点赞+点赞!!!

个知识,脑补回来。

下方即为我手绘的Android框架体系架构知识脑图,由于是xmind文件,不好上传,所以小编将其以图片形式导出来传在此处,细节方面不是特别清晰。但可给感兴趣的朋友提供完整的Android框架体系架构知识脑图原件(包括上方的面试解析xmind文档)
[外链图片转存中…(img-SKagbOfM-1711725936494)]

除此之外,前文所提及的Alibaba珍藏版 Android框架体系架构 手写文档以及一本 《大话数据结构》 书籍等等相关的学习笔记文档,也皆可分享给认可的朋友!

——感谢大家伙的认可支持,请注意:点赞+点赞+点赞!!!

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

  • 24
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值