最后
光有这些思路和搞懂单个知识的应用是还远远不够的,在Android开源框架设计思想中的知识点还是比较多的,想要搞懂还得学会整理和规划:我们常见的**Android热修复框架、插件化框架、组件化框架、图片加载框架、网络访问框架、RxJava响应式编程框架、IOC依赖注入框架、最近架构组件Jetpack等等Android第三方开源框架,**这些都是属于Android开源框架设计思想的。如下图所示:
这位阿里P8大佬针对以上知识点,熬夜整理出了一本长达1042页的完整版如何解读开源框架设计思想PDF文档,内容详细,把Android热修复框架、插件化框架、组件化框架、图片加载框架、网络访问框架、RxJava响应式编程框架、IOC依赖注入框架、最近架构组件Jetpack等等Android第三方开源框架这些知识点从源码分析到实战应用都讲的简单明了。
由于文档内容过多,篇幅受限,只能截图展示部分
整理不易,觉得有帮助的朋友可以帮忙点赞分享支持一下小编~
你的支持,我的动力;祝各位前程似锦,offer不断!!!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
public int userId;
}
可以通过 @ForeignKey
注解的 onDelete
和 onUpdate
属性指定级联操作,如级联更新和级联删除:
@Entity(foreignKeys = @ForeignKey(entity = User.class,
parentColumns = “id”,
childColumns = “user_id”,
onUpdate = ForeignKey.CASCADE,
onDelete = ForeignKey.CASCADE))
有时,一个包含嵌套对象的 entity 或 POJO 表示一个完整的数据库逻辑,可以使用 @Embedded
注解将该嵌套对象的字段分解到该表中,如 User
表需要包含 Address
相关字段,可以使用 @Embedded
注解表示这是个组合列:
public class Address {
public String street;
public String state;
public String city;
@ColumnInfo(name = “post_code”)
public int postCode;
}
@Entity
public class User {
@PrimaryKey
public int id;
public String firstName;
@Embedded
public Address address;
}
也就是说, User
表包含 id
,firstName
,street
,state
,city
,和 post_code
列。
Embedded 字段也能包含其他 Embedded 字段。
如果有另一个组合列也是 Address
类型的,可以使用 @Embedded
注解的 prefix
属性添加列名前缀以保证列的唯一性。
使用 DAO
DAO(data access objects)是应用中操作数据库的最直接的接口,应用中对数据库的操作都表现在这个对象上,也就是说,应用不需要知道具体的数据库操作方法,只需要利用 DAO 完成数据库操作就行了,所以这一系列 Dao
对象也构成了 Room 的核心组件。DAO 可以是个接口,也可以是个抽象类,如果是个抽象类,那么它可以有个构造器,以 RoomDatabase
作为唯一参数,Room 会在编译时自动生成每个 DAO 的实现类。
新增
定义一个用 @Insert
注解的 DAO 方法,Room 会自动生成一个在单个事务中将所有参数插入数据库的实现,如果方法只有一个参数,那么它可以返回 long
类型的 rowId
,如果方法参数是数组或集合,那么它可以返回 long[]
或 List<Long>
:
@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 friends);
}
更新
@Update
注解的方法可以更改一系列给定的 entity, 使用匹配的主键去查询更改这些 entity,可以返回 int
型的数据库更新行数:
@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,返回值也可以是 RxJava2
的 Publisher
或 Flowable
(需要添加 android.arch.persistence.room:rxjava2
依赖),甚至可以是 Cursor
(不建议直接使用 Cursor API )。
数据库的更新与迁移
随着应用功能的改变,你需要去更改 entity 和数据库,但很多时候,你不希望因此丢失数据库中已存在的的数据,尤其是无法从远程服务器恢复这些数据时。也就是说,如果你不提供必要的迁移操作,Room 将会重建数据库,数据库中所有的数据都将丢失。
为此, Room 允许你写一些 Migration
类去保护用户数据,每个 Migration
类指定一个 startVersion
和 endVersion
,在运行时,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
,那么数据库中所有的 Dao
和 Entity
都能使用它。如果注解了 Dao
,那么 Dao 中所有的方法都能使用它。如果注解了 Entity
,那么 Entity 中所有的字段都能使用它。如果注解了 POJO,那么 POJO 中所有的字段都能使用它。如果注解了 Entity
字段,那么只有这个 Entity 字段能使用它。如果注解了 Dao
方法,那么该 Dao 方法中所有的参数都能使用它。如果注解了 Dao
方法参数,那么只有这个参数能使用它:
总结:
面试是一个不断学习、不断自我提升的过程,有机会还是出去面面,至少能想到查漏补缺效果,而且有些知识点,可能你自以为知道,但让你说,并不一定能说得很好。
有些东西有压力才有动力,而学到的知识点,都是钱(因为技术人员大部分情况是根据你的能力来定级、来发薪水的),技多不压身。
附上我的面试各大专题整理: 面试指南,满满的都是干货,希望对大家有帮助!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
学到的知识点,都是钱(因为技术人员大部分情况是根据你的能力来定级、来发薪水的),技多不压身。
附上我的面试各大专题整理: 面试指南,满满的都是干货,希望对大家有帮助!
[外链图片转存中…(img-bQLNXzjR-1715907633318)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!