本文开发环境为AndroidStudio 4.0 ,开发语言为kotlin,调试设备为OPPO Reno。
资料来自于一下链接。
其实早在2018年,Google就在I/O大会上发布了一系列的辅助工具包合集,称为Jetpack,主要包含四个方面。
根据图片可以看到几乎是涵盖Android开发的方方面面。我们只要学会使用其中的几个组件就能开发出架构很清晰的应用。在Android的官方demo中也是大量采用相关组件去做开发。关于这里面各模块的简单介绍可以参考这个点击前往。很重要的一点是Jetpack和Kotlin相结合真的是非常令人舒适。
前面简单介绍一下Jetpack,但本次重点还是简单说一下Room的使用体验,其实这玩意儿,Google 2017年就推出了。但是好像很少人用,反正我是2019年才知道,2020年才自己使用。用起来就一个字,爽!
Room是Google 针对Android数据库操作封装的一个组件,Room 持久性库在 SQLite 的基础上提供了一个抽象层,让用户能够在充分利用 SQLite 的强大功能的同时,获享更强健的数据库访问机制。这个组件将数据库的建库建表升级,增删查改的代码全部都帮你在编译时去实现了。而你要做的就是写你的数据类,数据操作类Dao,数据库类Database。下面我们就看一个简单的示例,看看Room的魅力。
下面你会觉得非常快,因为它用起来就是这么快。
在AndroidStudio(不会还有人用Eclipse吧 )中的build.gradle中添加Gradle依赖。通常情况下这个build.gradle文件指的是module下的那个文件。但是也有些项目配置的主module在project的build.gradle里面,所以看自己的用法。
apply plugin: 'kotlin-kapt'
dependencies {
implementation 'androidx.room:room-runtime:2.2.5'
implementation "androidx.room:room-ktx:2.2.5"
//这个是为了编译时根据注解生成代码
kapt 'androidx.room:room-compiler:2.2.5'
}
下面就可以愉快的,使用Room了。
如果我们设计一个需要使用到数据库的应用,首先我们肯定也是先设计表。来看一下用Room设计表。
@Entity(tableName = "people")
data class People(
@PrimaryKey @ColumnInfo(name = "name") val name: String,
@ColumnInfo(name = "age", defaultValue = "18") val age: Int, @ColumnInfo val address: String?
)
没错,我们的表建完了,并且我们的数据类也建好了,当列名和属性名一样是,甚至可以省略掉name。
package com.wanghang.mypeople
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
@Dao
interface PeopleDao {
@Insert(onConflict = OnConflictStrategy.IGNORE, entity = People::class)
fun insertPeople(people: People)
@Query("SELECT * FROM people WHERE name= :name LIMIT 1")
fun getPeople(name: String): People
@Query("SELECT age FROM people WHERE name= :name LIMIT 1")
fun getPeopleAge(name: String): Int
}
这个类就是我们以前经常写的在应用内对本地数据库进行增删查改操作,可以想一想以前我们自己写的DatabaseWrapper类有多复杂。这里我就简单写了一个insert和一个query操作,对于代码量和逻辑复杂度来说,就已经差距很明显了。
很显然这知识一个接口类,没有真正的实现,因为真正的实现编译时kapt帮我们实现了。我们可以看下实现。
package com.wanghang.mypeople;
//删掉import
@SuppressWarnings({"unchecked", "deprecation"})
public final class PeopleDao_Impl implements PeopleDao {
private final RoomDatabase __db;
private final EntityInsertionAdapter<People> __insertionAdapterOfPeople;
public PeopleDao_Impl(RoomDatabase __db) {
this.__db = __db;
this.__insertionAdapterOfPeople = new EntityInsertionAdapter<People>(__db) {
@Override
public String createQuery() {
return "INSERT OR IGNORE INTO `people` (`name`,`age`,`address`) VALUES (?,?,?)";
}
@Override
public void bind(SupportSQLiteStatement stmt, People value) {
if (value.getName() == null) {
stmt.bindNull(1);
} else {
stmt.bindString(1, value.getName());
}
stmt.bindLong(2, value.getAge());
if (value.getAddress() == null) {
stmt.bindNull(3);
} else {
stmt.bindString(3, value.getAddress());
}
}
};
}
@Override
public void insertPeople(final People people) {
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
__insertionAdapterOfPeople.insert(people);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
}
}
@Override
public People getPeople(final String name) {
final String _sql = "SELECT * FROM people WHERE name= ? LIMIT 1";
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
int _argIndex = 1;
if (name == null) {
_statement.bindNull(_argIndex);
} else {
_statement.bindString(_argIndex, name);
}
__db.assertNotSuspendingTransaction();
final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
try {
final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
final int _cursorIndexOfAge = CursorUtil.getColumnIndexOrThrow(_cursor, "age");
final int _cursorIndexOfAddress = CursorUtil.getColumnIndexOrThrow(_cursor, "address");
final People _result;
if(_cursor.moveToFirst()) {
final String _tmpName;
_tmpName = _cursor.getString(_cursorIndexOfName);
final int _tmpAge;
_tmpAge = _cursor.getInt(_cursorIndexOfAge);
final String _tmpAddress;
_tmpAddress = _cursor.getString(_cursorIndexOfAddress);
_result = new People(_tmpName,_tmpAge,_tmpAddress);
} else {
_result = null;
}
return _result;
} finally {
_cursor.close();
_statement.release();
}
}
@Override
public int getPeopleAge(final String name) {
final String _sql = "SELECT age FROM people WHERE name= ? LIMIT 1";
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
int _argIndex = 1;
if (name == null) {
_statement.bindNull(_argIndex);
} else {
_statement.bindString(_argIndex, name);
}
__db.assertNotSuspendingTransaction();
final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
try {
final int _result;
if(_cursor.moveToFirst()) {
_result = _cursor.getInt(0);
} else {
_result = 0;
}
return _result;
} finally {
_cursor.close();
_statement.release();
}
}
}
可以看到其实这就是我们平时自己写的DataWrapper类啊,而且还实现了cursor bind到对象上,数据库操作还全部都保持了原子性操作,以及加锁和释放锁的操作。其实这些代码都可以称之为模板代码,如果熟悉了数据查询那一套,重复写这些代码还是很浪费时间并且容易出现疏漏。交给编译器生成自然是最好不过。但是友情提示,还是要先会自己写这些,懂得其中的原理更好。
按照步骤,表结构设计好,数据查询类设计好,下面就开始数据库创建类了。
@Database(entities = [People::class], version = 1)
abstract class PeopleRoomDatabase : RoomDatabase() {
abstract fun peopleDao(): PeopleDao
companion object {
@Volatile
private var INSTANCE: PeopleRoomDatabase? = null
fun getDatabase(context: Context, scope: CoroutineScope): PeopleRoomDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
PeopleRoomDatabase::class.java,
"people_database"
)
.addCallback(PeopleDatabaseCallback(scope))
.build()
INSTANCE = instance
instance
}
}
}
}
这里是使用单例去创建Database对象,可以看到@Database注解的参数,entities表示表的集合。因为我的demo只有一个表,就直接将前面映射到表的数据类的类名加上就可以了。然后就是version,数据库版本号,因为是第一次床架所以版本号就是1。
我们可以看一下@Database可以添加那些参数。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Database {
/**
* The list of entities included in the database. Each entity turns into a table in the
* database.
*
* @return The list of entities in the database.
*/
Class<?>[] entities();
/**
* The list of database views included in the database. Each class turns into a view in the
* database.
*
* @return The list of database views.
*/
Class<?>[] views() default {};
/**
* The database version.
*
* @return The database version.
*/
int version();
/**
* You can set the annotation processor argument ({@code room.schemaLocation}) to tell Room to
* export the database schema into a folder. Even though it is not mandatory, it is a good
* practice to have version history of your schema in your codebase and you should commit the
* schema files into your version control system (but don't ship them with your app!).
*
* When {@code room.schemaLocation} is set, Room will check this variable and if it is set to
* {@code true}, the database schema will be exported into the given folder.
*
* {@code exportSchema} is {@code true} by default but you can disable it for databases when
* you don't want to keep history of versions (like an in-memory only database).
*
* @return Whether the schema should be exported to the given folder when the
* {@code room.schemaLocation} argument is set. Defaults to {@code true}.
*/
boolean exportSchema() default true;
}