Jetpack 是一个开发组件的工具集,它的主要目的是帮助我们编写出更加简洁、规范的代码

ViewModel

传统的开发模式下,Activity 的任务太重了,既要负责逻辑处理,又要控制 UI 展示,还得处理网络回调,长此以往,项目会变得异常臃肿。ViewModel 的一个重要作用就是帮助 Activity 分担一部分工作,专门用于存放与界面相关的数据

1. 创建 ViewModel

在 app/build.gradle 文件添加依赖

dependencies {implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'...
}

通常来讲,比较好的规范是给每一个 Activity 和 Fragment 都创建一个对应的 ViewModel,这里就为 MainActivity 创建一个对应的 MainViewModel

class MainViewModel : ViewModel() {var counter = 0;
}

接下来在 MainActivity 中使用这个变量

class MainActivity : AppCompatActivity() {lateinit var viewModel: MainViewModeloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)ViewModelProvider(this).get(MainViewModel::class.java)button.setOnClickListener {viewModel.counter++}}
}

2. 向 ModelView 传递参数

如果退出程序再打开,那么之前的数据就会丢失了。因此,我们需要在退出程序时保存数据,然后重新打开程序再读取之前保存的数据,并传递给 MainModelView,因此这里修改 MainModelView 中的代码

class MainViewModel(countReserved: Int) : ViewModel() {var counter = 0;
}

借助 ViewModelProvider.Factory 向 MainViewModel 的构造函数传递数据,新建一个 MainViewModelFactory 类,在构造函数也接收一个 countReserved 参数。另外,实现 create() 方法,在这里创建 MainViewModel 实例,并 countReserved 参数传进去

class MainViewModelFactory(private val countReserved: Int) : ViewModelProvider.Factory {override fun <T : ViewModel?> create(modelClass: Class<T>): T {return MainViewModel(countReserved) as T}
}

LifeCycles

在编写程序时,可能会经常遇到需要感知 Activity 生命周期的情况,因此,我们需要能够时刻感知 Activity 的生命周期,以便在合适的时候进行相应的逻辑控制

新建一个 MyObserver 类,并让它实现 LifecycleObserver 接口

class MyObserver : LifecycleObserver {@OnLifecycleEvent(Lifecycle.Event.ON_START)fun activityStart() {Log.d("MyObserver", "activityStart")}@OnLifecycleEvent(Lifecycle.Event.ON_STOP)fun activityStop() {Log.d("MyObserver", "activityStop")}
}

我们在方法上使用 @OnLifecycleEvent 注解,并传入生命周期事件。生命周期事件的类型一共有七种:ON_CREATE、ON_START、ON_RESUME、ON_PAUSE、ON_STOP、ON_DESTROY 分别匹配 Activity 中相应的生命周期回调。另外还有一种 ON_ANY 类型,表示可以匹配 Activity 的任何生命周期回调。因此,上述代码中的方法就分别对应 Activity 的 onStart() 和 onStop() 触发执行

然后,在 MainActivity 添加一行代码,MyObserver 就能自动感知到 Activity 的生命周期了

class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)lifecycle.addObserver(MyObserver())}
}

如果希望在 MyObserver 中主动获取当前的生命周期状态,只需要在 MyObserver 的构造函数中将 Lifecycle 对象传进来即可。有了 Lifecycle 对象之后,我们就可以在任何地方调用 lifecycle.currentState 来主动获知当前的生命周期状态。lifecycle.currentState 返回的生命周期状态是一个枚举类型,一共有 INITIALIZED、DESTROYED、CREATED、STARTED、RESUMED 这五种类型,它们与 Activity 的生命周期回调所对应的关系如图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W8Kib0zS-1642256075778)(G:\SSS\Android\blog\lifecycle生命周期状态.jpg)]

LiveData

LiveData 是 Jetpack 提供的一种响应式编程组件,它可以包含任何类型的数据,并在数据发生变化的时候通知给观察者。LiveData 特别适合与 ViewModel 结合使用,如果我们将 ViewModel 中的数据用 LiveData 来包装,然后在 Activity 中去观察它,就可以主动将数据变化通知给 Activity 了

修改 MainViewModel 中的代码

class MainViewModel(countReserved: Int) : ViewModel() {var counter = MutableLiveData<Int>()init {counter.value = countReserved}fun plusOne() {val count = counter.value ?: 0counter.value = count + 1}fun clear() {counter.value = 0}
}

MutableLiveData 是一种可变的 LiveData,主要有三种读写数据的方法,分别是 getValue()、setValue()、postValue()

  • getValue() 方法用于获取 LiveData 中包含的数据
  • setValue() 方法用于给 LiveData 设置数据,但只能在主线程调用
  • postValue() 方法用于在非线程中给 LiveData 设置数据

接下来开始改造 MainActivity 的代码

class MainActivity : AppCompatActivity() {lateinit var viewModel: MainViewModeloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)viewModel.counter.observe(this, Observer { count ->...})}
}

调用 viewModel.counter.observe() 方法观察数据变化,该方法接收两个参数:第一个参数是一个 LifecycleOwner 对象,因此直接传 this 即可;第二个参数是一个 Observer 接口,当 counter 中包含的数据发生变化,就会回调到这里

map 和 switchMap

LiveData 为了能够应付不同的需求场景,提供了两种转换方法:map()switchMap(),提供了两种转换方法:map()switchMap() 方法

先来看 map() 方法,它的作用是将实际包含数据的 LiveData 和仅用于观察数据的 LiveData 进行转换。比如有一个 User 类,包含用户的姓名和年龄,我们可以在 ViewModel 中创建创建一个 LiveData 来包含 User 类型的数据。但 MainActivity 中明确只会显示用户的姓名,而不关心用户的年龄,那么这个时候还将整个 User 类型的 LiveData 暴露给外部,就显得不那么合适了

map() 方法就是专门用于解决这种问题的,它可以将 User 类型的 LiveData 自由地转型成任意其他类型的 LiveData

class MainViewModel(countReserved: Int) : ViewModel() {private val userLiveData = MutableLiveData<User>()val username: LiveData<String> = Transformations.map(userLiveData) {user -> "${user.firstName} ${user.lastName}"}
}

这里的逻辑也很简单,就是将 User 对象转换成一个只包含用户姓名的字符串

接下来是 switchMap() 方法,前面我们所学的所有内容都有一个前提:LiveData 对象的实例都是在 ViewModel 中创建的,然而实际项目中,很有可能 ViewModel 中的某个 LiveData 对象是调用另外的方法获取的,而且这个 LiveData 对象每次都是一个新的实例,沿用以前的写法来观察,只能一直观察老的 LiveData,从而无法观察到数据的变化

这种情况,我们可以借助 switchMap() 方法,将新的 LiveData 对象转换成另外一个可观察的 LiveData 对象

class MainViewModel(countReserved: Int) : ViewModel() {private val userIdLiveData = MutableLiveData<String>()val user: LiveData<User> = Transformations.switchMap(userIdLiveData) {userId -> Repository.getUser(userId)}fun getUser(userId: String) {userIdLiveData.value = userId}
}

Room

Room 是 Android 推出的一款 ORM 框架,主要由 Entity、Dao 和 Database 三部分组成,每个部分都有明确的职责:

  • Entity

    用于定义封装实际数据的实体类,每个实体类都会在数据库中有一张对应的表,表中的列是根据实体类中的字段自动生成

  • Dao

    Dao 是数据访问对象,通常会在这里对数据库的各项操作进行封装

  • Database

    用于定义数据库中的关键信息,包括数据库的版本号、包含哪些实体类以及提供 Dao 层的访问实例

要使用 Room,需要在 app/build.gradle 文件添加如下的依赖

apply plugin: 'kotlin-kapt'dependencies {...implementation "androidx.room:room-runtimer:2.1.0"kapt "androidx.room:room-compiler:2.1.0"
}

这里新增一个 kotlin-kapt 插件,同时在 dependencies 闭包中添加两个 Room 依赖库。由于 Room 会根据我们在项目中声明的注解动态生成代码,因此一定要使用 kapt 引入 Room 的编译时注解库,而启用编译时注解功能则一定要先添加 kotlin-kapt 插件

@Entity
data class User(val firstName: String, var lastName: String, var age: Int) {@PrimaryKey(autoGenerate = true)var id: Long = 0
}

我们在 User 的类名上使用 @Entity 注解,将它声明成一个实体类,然后在 User 类中添加一个 id 字段,并使用 @PrimaryKey 注解将它设为主键,再设 autoGenerate = true,使得主键的值是自动生成

接下来看一下 Dao,新建一个 UserDao 接口

@Dao
interface UserDao {@Insertfun insertUser(user: User): Long@Updatefun updateUser(user: User)@Deletefun deleteUser(user: User)@Query("select * from User")fun loadAllUsers(): List<User>@Query("select * from User where age > :age")fun loadUsersOlderThan(age: Int): List<User>
}

UserDao 接口使用了一个 @Dao 注解,识别成一个 Dao。Room 也提供了 @Insert@Delete@Update@Query 这四种注解。但是想要从数据库中查询数据,或者使用非实体类参数来增删改数据,就必须编写 SQL 语句,并使用 @Query

关于 Database,只需要定义好三部分的内容:数据库版本号、包含哪些实体类、以及提供 Dao 层的访问实例

@Database(version = 1, entities = [User::class])
abstract class AppDatabase : RoomDatabase() {abstract fun userDao(): UserDaocompanion object {private var instance: AppDatabase? = null@Synchronizedfun getDatabase(context: Context): AppDatabase {instance?.let {return it}return Room.databaseBuilder(context.applicationContext,AppDatabase::class.java, "app_database").build().apply {instance = this}}}
}

如果要升级数据库,修改版本号,实现一个 Migration 匿名类,并传入 1 和 2 这两个参数,最后在构建实例时加入一个 addMigration() 方法,并把 MIGRATION_1_2 传入

@Database(version = 2, entities = [User::class])
abstract class AppDatabase : RoomDatabase() {abstract fun userDao(): UserDaocompanion object {val MIGRATION_1_2 = object : Migration(1, 2) {override fun migrate(database: SupportSQLiteDatabase) {database.execSQL("...")}}private var instance: AppDatabase? = null@Synchronizedfun 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}}}
}

WorkManager

WorkManager 适合处理一些要求定时执行的任务,它可以根据操作系统的版本自动选择合适的实现。另外,它还支持周期性任务、链式任务处理等功能

使用 WorkManager 注册的周期性任务不能保证一定会准时执行,这是系统为了减少电量消耗,可能会将触发时间临近的几个任务放在一起执行,这样可以大幅减少 CPU 被唤醒的次数

1. 基本用法

在 app/build.gradle 文件中添加如下依赖

dependencies {...implementation "androidx.work:work-runtime:2.2.0"
}

WorkManager 的基本用法主要分以下三步:

  1. 定义一个后台任务,并实现具体任务逻辑
  2. 配置该后台任务的运行条件和约束条件,并构建后台任务请求
  3. 将后台任务请求传入 WorkManager 的 enqueue() 方法,系统会在合适的时间运行

第一步,先定义一个后台任务,编写后台任务逻辑

class SimpleWorker(context: Context, params: WorkerParameters) : Worker(context, params) {override fun doWork(): Result {return Result.success()}
}

第二步,配置该任务的运行条件和约束信息,这里只进行最基本的配置

// 构建单次运行的后台任务请求
val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java).build()
// // 构建周期性运行的后台任务请求
val request = PeriodicWorkRequest.Builder(SimpleWorker::class.java, 15, TimeUnit.MINUTES).build()

最后一步,将构建出的后台任务请求传入 WorkManager 的 enqueue() 方法中,系统就会在合适的时间去运行

WorkManager.getInstance(context).enqueue(request)

2. 处理复杂任务

让后台任务在指定的延迟时间后运行

val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java).setInitialDelay(5, TimeUnit.MINUTES).build()

给后台任务请求添加标签,最主要的一个功能就是可以通过标签来取消后台任务请求

val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java)....addTag("simple").build()...WorkManager.getInstance(this).cancelAllWorkByTag("simple")

如果后台任务的 doWork() 方法中返回 Result.retry(),那么可以结合 setBackoffCriteria() 方法来重新执行任务,接收三个参数:第一个参数用于指定如果任务再次执行失败,下次重试的时间应该以什么样的形式延迟;第二第三个参数用于指定在多久之后重新执行任务

val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java)....setBackoffCriteria(BackoffPolicy.LINEAR, 10, TimeUnit.SECOND).build()

对后台任务的运行结果进行监听,调用 getWorkInfoByIdLiveData() 方法,并传入后台任务请求 id,会返回一个 LiveData 对象。然后我们就可以调用 LiveData 对象的 observe() 方法观察数据变化,以此监听后台任务的运行结果

WorkManager.getInstance(context).getWorkInfoByIdLiveData(request.id).observe(this) { workInfo -> if(workInfo.state == WorkInfo.State.SUCCEEDED) {...} else if(workInfo.state == WorkInfo.State.FAILED) {...}}

最后再看看链式任务,定义三个独立的后台任务,依次执行

WorkManager.getInstance(context).beginWith(sync).then(compress).then(upload).enqueue(request)

Android Jetpack 实战相关推荐

  1. Android JetPack架构篇,一个实战项目带你学懂JetPack

    第五届世界互联网大会昨日开幕,来自76个国家的1500余位嘉宾出席大会.腾讯公司董事会主席兼首席执行官马化腾在大会开幕式演讲中表示,全球产业都在进行数字化,在此期间机遇挑战并存,产业互联网机会巨大. ...

  2. Android Jetpack架构组件之 Room(使用、源码篇)

    2019独角兽企业重金招聘Python工程师标准>>> 1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发 ...

  3. Android Jetpack组件之Navigation使用-源码

    1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...

  4. Android Jetpack组件之 Room使用-源码

    1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...

  5. android 使用4大组件的源码,Android Jetpack架构组件之 Paging(使用、源码篇)

    1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...

  6. 现学现用Android Jetpack - Navigation

    前言 即学即用Android Jetpack系列Blog的目的是通过学习Android Jetpack完成一个简单的Demo,本文是即学即用Android Jetpack系列Blog的第一篇. 记得去 ...

  7. 携程机票 Android Jetpack 与 Kotlin Coroutines 实践 | 开发者说·DTalk

    本文原作者: 禹昂,携程机票移动端资深工程师,Kotlin 中文社区核心成员,图书<Kotlin 编程实践>译者. 原文发布于: 携程技术 https://mp.weixin.qq.com ...

  8. 带你领略Android Jetpack组件的魅力

    1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...

  9. Kotlin Jetpack 实战:01. Kotlin 基础

    背景 近几年,Android 相关的新技术层出不穷.往往这个技术还没学完,下一个新技术又出来了.很多人都是一脸黑人问号?不少开发者甚至开始哀嚎:"求求你们别再创造新技术了,我们学不动了!&q ...

最新文章

  1. Windows server 2008 iis7/iis7.5启用父路径的方法
  2. matplotlib画图时间长_Python学习第86课-数据可视化之matplotlib介绍
  3. java用i/o查看文件_Java文件I / O基础
  4. 编写Dockerfile的最佳实践
  5. CAD制图系列之中心线画法
  6. 通用阿里云的短信验证码(详细)
  7. CentOS7安装Pentaho Server 8.1 CE 社区版
  8. 初步搭建 prometheus+ Grafana服务器性能监控平台
  9. LibreOffice 6.2.2 Office办公套件发布
  10. 修改注册表值scancode map来屏蔽键盘上的键
  11. RT-Thread—FAL与EasyFlash组件移植
  12. FreeSWITCH 呼入系统的简要设计
  13. linux cp 全覆盖,Linux中使用cp命令进行强制覆盖的方法
  14. 基于MPPT算法的PV光伏阵列电网模型simulink仿真
  15. 轻松玩抠图:图像去除背景方法与技巧
  16. C#版 泡泡堂 1.0
  17. 100张图训练1小时,照片风格随意变,文末有Demo试玩|SIGGRAPH 2021
  18. bugku ctf 小山丘的秘密
  19. CSDN 博客备份工具
  20. CAD中的索引符号都有哪些?CAD标注符号大全

热门文章

  1. ORA-00904:标识符无效(太坑了!!)
  2. c语言 异或结合律,异或运算
  3. AR技术,让生活变得更加智能与多彩
  4. 学平面设计少走弯路,选择平面设计专业培训!
  5. mysql-5.0.83删除_【实验】WindowsXP + MySQL5 + Apache2 + PHP5 + phpMyAdmin环境搭建
  6. 判断主力出货洗盘的秘籍
  7. ZBrush中标准几何体与Polymesh
  8. Python爬虫--实现图片验证码全自动输入
  9. 青岛科技大学计算机研究生,青岛科技大学2019年硕士研究生拟录取名单公示
  10. JavaWeb之数据库的操作(MySQL)