四篇文章帶你快速入門Jetpck(下)之Room漠烧,WorkManager
Jetpack
Jetpack 是一個(gè)由多個(gè)庫(kù)組成的套件腔剂,可幫助開發(fā)者遵循最佳做法媒区,減少樣板代碼并編寫可在各種 Android 版本和設(shè)備中一致運(yùn)行的代碼,讓開發(fā)者精力集中編寫重要的代碼掸犬。
Android Architecture Component (AAC)袜漩。
官方推薦架構(gòu)
請(qǐng)注意,每個(gè)組件僅依賴于其下一級(jí)的組件湾碎。例如宙攻,Activity 和 Fragment 僅依賴于視圖模型。存儲(chǔ)區(qū)是唯一依賴于其他多個(gè)類的類胜茧;在本例中粘优,存儲(chǔ)區(qū)依賴于持久性數(shù)據(jù)模型和遠(yuǎn)程后端數(shù)據(jù)源仇味。
Room
ORM:也叫對(duì)象關(guān)系映射。
將面相對(duì)象的語言和面相關(guān)系的數(shù)據(jù)庫(kù)之間建立一種映射關(guān)系雹顺,稱之為了ORM丹墨。
ORM框架的好處就是,賦予我們可以用面相對(duì)象的思維來和數(shù)據(jù)庫(kù)進(jìn)行交互嬉愧,絕大多數(shù)情況不用在和SQL語句打交道贩挣。
Android推出的ORM框架,將它加入了Jetpack中没酣,這就是我們將學(xué)習(xí)的Room王财。
Room結(jié)構(gòu)
由Entity,Dao和Database三部分組成裕便。
- Entity:封裝實(shí)際數(shù)據(jù)的實(shí)體類绒净,每個(gè)實(shí)體類都會(huì)在數(shù)據(jù)中對(duì)應(yīng)一張表,并且表中的列是根據(jù)實(shí)體類中的字段自動(dòng)生成的偿衰。
- Dao:Dao是數(shù)據(jù)訪問對(duì)象的意思挂疆,通常會(huì)在這里對(duì)數(shù)據(jù)庫(kù)的各項(xiàng)操作進(jìn)行封裝,在實(shí)際編程時(shí)下翎,邏輯層就不需要和底層數(shù)據(jù)庫(kù)打交道了缤言,直接和Dao層進(jìn)行交互即可。
- Database:用于定義數(shù)據(jù)庫(kù)中的關(guān)鍵信息视事,包括數(shù)據(jù)庫(kù)的版本號(hào)胆萧,包含哪些實(shí)體類以及提供Dao層的訪問實(shí)例。
添加依賴
apply plugin: 'kotlin-kapt'
implementation 'androidx.room:room-runtime:2.2.5'
annotationProcessor "androidx.room:room-compiler:2.2.5"
kapt "androidx.room:room-compiler:2.2.5"
定義Entity
首先定義一個(gè)Entity俐东,也就是實(shí)體類跌穗。
@Entity
data class User(var firstName: String, var lastName: String, var age: Int) {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
}
這里我們?cè)赨ser的類名上使用@Entity注解,將它聲明成了一個(gè)實(shí)體類犬性,然后在User類中添加了一個(gè)id字段瞻离,并使用@PrimaryKey注解將它設(shè)為了主鍵,再把a(bǔ)utoGenerate參數(shù)指定成true乒裆,使得主鍵的值是自動(dòng)生成的套利。
定義Dao
@Dao
interface UserDao {
@Insert
fun insertUser(user: User): Long
@Update
fun updateUser(newUser: User)
@Query("select * from User")
fun loadAllUsers(): List<User>
@Query("select * from User where age > :age")
fun loadUsersOlderThan(age: Int): List<User>
@Delete
fun deleteUser(user: User)
@Query("delete from User where lastName = :lastName")
fun deleteUserByLastName(lastName: String): Int
}
定義Database
@Database(version = 1, entities = [User::class])
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
private var instance: AppDatabase? = null
@Synchronized
fun getDatabase(context: Context): AppDatabase {
instance?.let {
return it
}
return Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "app_database").build().apply {
instance = this
}
}
}
}
Room的數(shù)據(jù)庫(kù)升級(jí)
數(shù)據(jù)庫(kù)接口不可能在設(shè)計(jì)好了之后就永遠(yuǎn)一成不變,隨著需求和版本的變更鹤耍,數(shù)據(jù)庫(kù)也是需要升級(jí)的肉迫。
簡(jiǎn)單模式
fallbackToDestructiveMigration會(huì)將當(dāng)前的數(shù)據(jù)庫(kù)銷毀,然后在重新創(chuàng)建稿黄,隨之而來的問題就是數(shù)據(jù)全部丟失喊衫,適合測(cè)試階段。
Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "app_database").fallbackToDestructiveMigration().build()
升級(jí)模式
- 修改版本號(hào)杆怕。
- 定義升級(jí)版本的sql語句族购。
- 執(zhí)行語句壳贪。
示例
User
/**
* @Entity 將類聲明成實(shí)體類
*/
@Entity
data class User(var firstName: String, var lastName: String, var age: Int) {
/**
* @PrimaryKey 注解將字段設(shè)置為主鍵
* autoGenerate 為true 為主鍵的值為自動(dòng)生成
*/
@PrimaryKey(autoGenerate = true)
var id: Long = 0
}
UserDao
/**
* @Dao 注解才能識(shí)別他是Dao
* UserDao的內(nèi)部就是根據(jù)業(yè)務(wù)需求對(duì)各種數(shù)據(jù)庫(kù)操作進(jìn)行的封裝。
* 一般指的是CRUD操作
*
* Room編寫SQL語句支持在編譯時(shí)動(dòng)態(tài)建材SQL語句語法
*/
@Dao
interface UserDao {
@Insert
fun insertUser(user: User): Long
@Update
fun updateUser(user: User)
@Query("select * from User")
fun loadAllUsers(): List<User>
@Query("select * from User where age > :age")
fun loadUsersOlderThan(age: Int): List<User>
@Delete
fun deleteUser(user: User)
@Query("delete from User where lastName = :lastName")
fun deleteUserByLastName(lastName: String): Int
}
AppDatabase
/**
* @Database 聲明了數(shù)據(jù)庫(kù)的版本號(hào)以及包含哪些實(shí)體類寝杖,多個(gè)是實(shí)體類用逗號(hào)隔離開來违施。
* 另外AppDatabase類必須繼承自RoomDatabase類,并且一定要使用abstract關(guān)鍵字聲明成抽象類
*/
@Database(version = 1, entities = [User::class])
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
private var instance: AppDatabase? = null
@Synchronized
fun getDatabase(context: Context): AppDatabase {
instance?.let {
return it
}
return Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "app_database").build().apply {
instance = this
}
}
}
}
RoomActivity
class RoomActivity : AppCompatActivity() {
val TAG = this.javaClass.simpleName
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_room)
val userDao = AppDatabase.getDatabase(this).userDao()
val user1 = User("Y", "X", 22)
val user2 = User("T", "Y", 33)
btn_add.setOnClickListener {
thread {
user1.id = userDao.insertUser(user1)
user2.id = userDao.insertUser(user2)
}
}
btn_udpdate.setOnClickListener {
thread {
user1.age = 50
userDao.updateUser(user1)
}
}
btn_delete.setOnClickListener {
thread {
userDao.deleteUserByLastName("Y")
}
}
btn_query.setOnClickListener {
thread {
for (user in userDao.loadAllUsers()) {
Log.d(TAG, user.toString())
}
}
}
}
}
Book
@Entity
data class Book(var name: String, var pages: Int) {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
}
BookDao
@Dao
interface BookDao {
@Insert
fun insertBook(book: Book): Long
@Query("select * from Book")
fun loadAllBooks(): List<Book>
}
修改后的appdatabase
- version = 2
- User::class, Book::class
- abstract fun bookDao(): BookDao
- addMigrations(MIGRATION_1_2)
//變動(dòng):1
@Database(version = 2, entities = [User::class, Book::class])
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
//變動(dòng):2
abstract fun bookDao(): BookDao
companion object {
//變動(dòng):3
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("create table Book (id integer primary key autoincrement not null,name text not null,pages integer not null)")
}
}
private var instance: AppDatabase? = null
//變動(dòng):4
@Synchronized
fun getDatabase(context: Context): AppDatabase {
instance?.let {
return it
}
return Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "app_database").addMigrations(MIGRATION_1_2).build().apply {
instance = this
}
}
}
}
再次修改 增加 var author: String
@Entity
data class Book(var name: String, var pages: Int, var author: String) {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
}
再次修改第三版 version =3
- version = 3
- MIGRATION_2_3
- addMigrations(MIGRATION_1_2, MIGRATION_2_3)
@Database(version = 3, entities = [User::class, Book::class])
val MIGRATION_2_3 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("alter table Book add column author text not null default 'unknown'")
}
}
return Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "app_database").addMigrations(MIGRATION_1_2, MIGRATION_2_3).build().apply {
instance = this
}
WorkManager
WorkManager很適合用于處理一些要求定時(shí)執(zhí)行的任務(wù)瑟幕,它可以根據(jù)操作系統(tǒng)的版本自動(dòng)選擇底層是使用AlarmManager實(shí)現(xiàn)還是JobScheduler實(shí)現(xiàn)磕蒲,從而降低了我們的使用成本。
另外只盹, WorkManager還支持周期性任務(wù)辣往、鏈?zhǔn)饺蝿?wù)處理等功能,是一個(gè)非常強(qiáng)大的工具殖卑。
添加依賴
implementation 'androidx.work:work-runtime:2.4.0'
WorkManager的基本用法
WorkManager的基本用法其實(shí)非常簡(jiǎn)單站削,主要分為以下3步:
定義一個(gè)后臺(tái)任務(wù),并實(shí)現(xiàn)具體的任務(wù)邏輯孵稽。
配置該后臺(tái)任務(wù)的運(yùn)行條件和約束信息钻哩,并構(gòu)建后臺(tái)任務(wù)請(qǐng)求。
將該后臺(tái)任務(wù)請(qǐng)求傳入WorkManager的enqueue()方法中肛冶,系統(tǒng)會(huì)在合適的時(shí)間運(yùn)行。
定義后臺(tái)任務(wù)
第一步要定義一個(gè)后臺(tái)任務(wù)扯键,這里創(chuàng)建一個(gè)SimpleWorker類睦袖,代碼如下所示:
class SimpleWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
Log.d("SimpleWorker", "do work in SimpleWorker")
return Result.success()
}
}
對(duì)后臺(tái)任務(wù)進(jìn)行配置
第二步,配置后臺(tái)任務(wù)的運(yùn)行條件和約束信息荣刑,代碼如下所示:
val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java)
.setInitialDelay(5, TimeUnit.MINUTES)
.build()
最后一步馅笙,將構(gòu)建出的后臺(tái)任務(wù)請(qǐng)求傳入WorkManager的enqueue()方法中,系統(tǒng)就會(huì)在合適的時(shí)間去運(yùn)行了厉亏,代碼如下所示:
WorkManager.getInstance(context).enqueue(request)
延時(shí)啟動(dòng)
setInitialDelay(1, TimeUnit.MINUTES)
設(shè)置標(biāo)簽
addTag("example")
取消任務(wù)
WorkManager.getInstance(this).cancelWorkById(request.id)
WorkManager.getInstance(this).cancelAllWork()
WorkManager.getInstance(this).cancelAllWorkByTag("example")
觀察任務(wù)
WorkManager.getInstance(this).getWorkInfoByIdLiveData(request.id)
.observe(this) { workInfo ->
when (workInfo.state) {
WorkInfo.State.SUCCEEDED -> {
Log.d(TAG, "WorkInfo.State.SUCCEEDED")
}
WorkInfo.State.FAILED -> {
Log.d(TAG, "WorkInfo.State.FAILED")
}
WorkInfo.State.RUNNING -> {
Log.d(TAG, "WorkInfo.State.RUNNING")
}
WorkInfo.State.CANCELLED -> {
Log.d(TAG, "WorkInfo.State.CANCELLED")
}
WorkInfo.State.ENQUEUED -> {
Log.d(TAG, "WorkInfo.State.ENQUEUED")
}
}
}
重復(fù)執(zhí)行任務(wù)
doWork方法要返回Result.retry().
setBackoffCriteria(BackoffPolicy.LINEAR, 1, TimeUnit.SECONDS)
示例
class WorkManagerActivity : AppCompatActivity() {
val TAG = this.javaClass.simpleName
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_work_manager)
btn_do.setOnClickListener {
val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java)
.setInitialDelay(2, TimeUnit.SECONDS).addTag("example")
.setBackoffCriteria(BackoffPolicy.LINEAR, 1, TimeUnit.SECONDS)
.build()
WorkManager.getInstance(this).enqueue(request)
WorkManager.getInstance(this).getWorkInfoByIdLiveData(request.id)
.observe(this) { workInfo ->
when (workInfo.state) {
WorkInfo.State.SUCCEEDED -> {
Log.d(TAG, "WorkInfo.State.SUCCEEDED")
}
WorkInfo.State.FAILED -> {
Log.d(TAG, "WorkInfo.State.FAILED")
}
WorkInfo.State.RUNNING -> {
Log.d(TAG, "WorkInfo.State.RUNNING")
}
WorkInfo.State.CANCELLED -> {
Log.d(TAG, "WorkInfo.State.CANCELLED")
}
WorkInfo.State.ENQUEUED -> {
Log.d(TAG, "WorkInfo.State.ENQUEUED")
}
}
}
//WorkManager.getInstance(this).cancelWorkById(request.id)
}
btn_cancel.setOnClickListener {
//WorkManager.getInstance(this).cancelAllWork()
WorkManager.getInstance(this).cancelAllWorkByTag("example")
}
}
}