Android Jetpack 实战
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 的基本用法主要分以下三步:
- 定义一个后台任务,并实现具体任务逻辑
- 配置该后台任务的运行条件和约束条件,并构建后台任务请求
- 将后台任务请求传入 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 实战相关推荐
- Android JetPack架构篇,一个实战项目带你学懂JetPack
第五届世界互联网大会昨日开幕,来自76个国家的1500余位嘉宾出席大会.腾讯公司董事会主席兼首席执行官马化腾在大会开幕式演讲中表示,全球产业都在进行数字化,在此期间机遇挑战并存,产业互联网机会巨大. ...
- Android Jetpack架构组件之 Room(使用、源码篇)
2019独角兽企业重金招聘Python工程师标准>>> 1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发 ...
- Android Jetpack组件之Navigation使用-源码
1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...
- Android Jetpack组件之 Room使用-源码
1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...
- android 使用4大组件的源码,Android Jetpack架构组件之 Paging(使用、源码篇)
1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...
- 现学现用Android Jetpack - Navigation
前言 即学即用Android Jetpack系列Blog的目的是通过学习Android Jetpack完成一个简单的Demo,本文是即学即用Android Jetpack系列Blog的第一篇. 记得去 ...
- 携程机票 Android Jetpack 与 Kotlin Coroutines 实践 | 开发者说·DTalk
本文原作者: 禹昂,携程机票移动端资深工程师,Kotlin 中文社区核心成员,图书<Kotlin 编程实践>译者. 原文发布于: 携程技术 https://mp.weixin.qq.com ...
- 带你领略Android Jetpack组件的魅力
1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...
- Kotlin Jetpack 实战:01. Kotlin 基础
背景 近几年,Android 相关的新技术层出不穷.往往这个技术还没学完,下一个新技术又出来了.很多人都是一脸黑人问号?不少开发者甚至开始哀嚎:"求求你们别再创造新技术了,我们学不动了!&q ...
最新文章
- Windows server 2008 iis7/iis7.5启用父路径的方法
- matplotlib画图时间长_Python学习第86课-数据可视化之matplotlib介绍
- java用i/o查看文件_Java文件I / O基础
- 编写Dockerfile的最佳实践
- CAD制图系列之中心线画法
- 通用阿里云的短信验证码(详细)
- CentOS7安装Pentaho Server 8.1 CE 社区版
- 初步搭建 prometheus+ Grafana服务器性能监控平台
- LibreOffice 6.2.2 Office办公套件发布
- 修改注册表值scancode map来屏蔽键盘上的键
- RT-Thread—FAL与EasyFlash组件移植
- FreeSWITCH 呼入系统的简要设计
- linux cp 全覆盖,Linux中使用cp命令进行强制覆盖的方法
- 基于MPPT算法的PV光伏阵列电网模型simulink仿真
- 轻松玩抠图:图像去除背景方法与技巧
- C#版 泡泡堂 1.0
- 100张图训练1小时,照片风格随意变,文末有Demo试玩|SIGGRAPH 2021
- bugku ctf 小山丘的秘密
- CSDN 博客备份工具
- CAD中的索引符号都有哪些?CAD标注符号大全
热门文章
- ORA-00904:标识符无效(太坑了!!)
- c语言 异或结合律,异或运算
- AR技术,让生活变得更加智能与多彩
- 学平面设计少走弯路,选择平面设计专业培训!
- mysql-5.0.83删除_【实验】WindowsXP + MySQL5 + Apache2 + PHP5 + phpMyAdmin环境搭建
- 判断主力出货洗盘的秘籍
- ZBrush中标准几何体与Polymesh
- Python爬虫--实现图片验证码全自动输入
- 青岛科技大学计算机研究生,青岛科技大学2019年硕士研究生拟录取名单公示
- JavaWeb之数据库的操作(MySQL)