Jetpack
Jetpack 是一个丰富的组件库,它的组件库按类别分为 4 类,分别是架构(Architecture)、界面(UI)、行为(behavior)和基础(foundation)。每个组件都可以单独使用,也可以配合在一起使用。每个组件都给用户提供了一个标准,能够帮助开发者遵循最佳做法,减少样板代码并编写可在各种 Android 版本和设备中一致运行的代码,让开发者能够集中精力编写重要的业务代码。
Jetpack 是各种组件库的统称,AndroidX 是这些组件的统一包名。
AndroidX 对原始 Android Support Library 进行了重大改进,后者现在已不再维护。androidx 软件包完全取代了 support 包,不仅提供同等的功能,而且提供了新的库。Jetpack 组件中也是完全使用 androidx 开头的包名。
与 Support Library 一样,androidx 命名空间中的库与 Android 平台分开提供,并向后兼容各个 Android 版本。
LiveData
LiveData是一种可观察的数据存储器类。与常规的可观察类不同,LiveData 具有生命周期感知能力,意指它遵循其他应用组件(如 Activity、Fragment 或 Service)的生命周期。这种感知能力可确保 LiveData 仅更新处于活跃生命周期状态的应用组件观察者。
LiveData的优点有很多,如不会造成内存泄漏,另外还有一个最重要的优势是数据驱动UI,也是MVVM模型的核心设计,所以LiveData通常会配合ViewModel来使用,ViewModel负责触发数据的更新,更新会通知到LiveData,然后LiveData再通知活跃状态的观察者。此外还有与页面的生命周期绑定,数据自动更新等等。
创建 LiveData 对象
在ViewModel中创建LiveData数据对象
private val userList: MutableLiveData> by lazy {
MutableLiveData>()
}
fun getUserListLiveData() = userList
观察 LiveData 对象
在UI中初始化承载userList对象的ViewModel。
private val mainViewModel: MainViewModel
mainViewModel.getUserListLiveData().observe(this, Observer {
text.text = it.toString()
})
更新 LiveData 对象
直接在获取数据之后调用setValue或者postValue即可,一般情况下在ViewModel中进行数据的请求和和LiveData的更新。
userList.value = it
另外LiveData还可以和Room、协程以及与Flow流进行相互转化。
https://developer.android.google.cn/topic/libraries/architecture/livedata
ViewModel
ViewModel类是被设计用来以可感知生命周期的方式存储和管理 UI 相关数据,ViewModel中数据会一直存活即使 activity configuration发生变化,比如横竖屏切换的时候。ViewModel是Google的MVVM中的重要一环VM。
ViewModel的定义
ViewModel需要集成系统API提供的ViewModel类,然后就是如何操作LiveData了。
class MainViewModel : ViewModel() {
private val userList = MutableLiveData>()
@ExperimentalCoroutinesApi
fun getAllUser() {
GlobalScope.launch(Dispatchers.Main) {
getAllUserFlow()
.flowOn(Dispatchers.IO)
.onStart {
Log.e("MainActivity", "getAllUser onStart")
}
.onCompletion {
Log.e("MainActivity", "getAllUser onCompletion")
}
.onEach {
userList.value = it
}
.catch {
Log.e("MainActivity", "getAllUser catch it=${it.message}")
}
.collect()
}
}
}
ViewModel的使用
在UI(Activity或者Fragment)中实例化ViewModel,最基本的方式:
val viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
还可以依赖activity-ktx,它利用kotlin的属性委托property delegate进行初始化:
implementation "androidx.activity:activity-ktx:1.2.2"
val viewModel: MainViewModel by viewModels()
这是在Activity中使用ViewModel,若在Fragment中使用可以依赖fragment-ktx:
implementation "androidx.fragment:fragment-ktx:1.3.2"
val viewModel: MainViewModel by activityViewModels()
另外还有一种,使用Koin一款依赖注入框架进行ViewModel初始化:
val viewModel: MainViewModel by inject()
ViewModel的生命周期
ViewModel对象存在的时间范围是获取 ViewModel ViewModelProvider 的 Lifecycle。ViewModel将一直留在内存中,直到限定其存在时间范围的 Lifecycle 永久消失:对于 Activity,是在 Activity 完成时;而对于 Fragment,是在 Fragment 分离时。
如何判断ViewModel的生命周期与哪个UI绑定呢,看ViewModelProvider的参数。
在 Fragment 之间共享数据
传统的Fragment共享数据是比较困难的事情,首先Fragment之间是隔离的,不能通信。其次如果使用他们的Activity则有一个类型强转的问题,Fragment的设计初衷就不是为单一Activity服务的,也不可取。最后一般都得借助另外一个类来保存多个Fragment都要使用的数据,这样做会让整体架构设计变得很糟糕。现在使用ViewModel让此事变得非常轻松合理。就是每个Fragment都可以直接订阅ViewModel的observe,因为ViewModel的数据驱动UI的本质还是观察者模式,每个Fragment都可以持有一个观察者。
https://developer.android.google.cn/topic/libraries/architecture/viewmodel
Room
Room 在 SQLite 上提供了一个抽象层,以便在充分利用 SQLite 的强大功能的同时,能够流畅地访问数据库。
如需在应用中使用 Room,请将以下依赖项添加到应用的 build.gradle 文件。
implementation 'androidx.room:room-runtime:2.2.6'
kapt 'androidx.room:room-compiler:2.2.6'
implementation 'androidx.room:room-ktx:2.2.6'
testImplementation 'androidx.room:room-testing:2.2.6'
基本使用
Room 包含 3 个主要组件:Database、Entity、Dao。
Database:包含数据库持有者,并作为应用已保留的持久关系型数据的底层连接的主要接入点。包含:@Database、继承扩展 RoomDatabase 的抽象类、在注释中添加与数据库关联的实体列表、包含具有 0 个参数且返回使用@Dao注释的类的抽象方法。
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
Entity:
@Entity(tableName="user")
data class User(
@PrimaryKey
val userId: UserId,
@ColumnInfo(name = "name")
val name: Name,
@ColumnInfo(name = "age")
val age: Age
)
typealias UserId = Int
typealias Name = String
typealias Age = Int
Dao:
@Dao
interface UserDao {
@Query("SELECT * FROM user")
fun getAllUser(): Flow>
@Query("SELECT * FROM user WHERE userId = :id")
fun findUserById(id: UserId): Flow
@Insert
fun insertUser(user: User)
@Delete
fun deleteUser(user: User)
}
初始化Database:
Room.databaseBuilder(
applicationContext,
AppDatabase::class.java, "database-name"
)
.addCallback(AppDatabaseCallback())
.addMigrations(AppDatabaseMigration())
.build()
其中的AppDatabaseCallback是管理Database的初始化每个阶段的回调
class AppDatabaseCallback : RoomDatabase.Callback() {
/**
* Called when the database is created for the first time. This is called after all the
* tables are created.
*
* @param db The database.
*/
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
}
/**
* Called when the database has been opened.
*
* @param db The database.
*/
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
}
/**
* Called after the database was destructively migrated
*
* @param db The database.
*/
override fun onDestructiveMigration(db: SupportSQLiteDatabase) {
super.onDestructiveMigration(db)
}
}
其中AppDatabaseMigration是升级库升级管理器
class AppDatabaseMigration : Migration(1, 2) {
/**
* Should run the necessary migrations.
*
*
* This class cannot access any generated Dao in this method.
*
*
* This method is already called inside a transaction and that transaction might actually be a
* composite transaction of all necessary `Migration`s.
*
* @param database The database instance
*/
override fun migrate(database: SupportSQLiteDatabase) {
TODO("Not yet implemented")
}
}
现在看起来Room并没有特别的优势能让我们忍不住马上把代码从SQLite切换过来,但是本地数据库的访问毕竟访问的是磁盘,所以最好的写法一定是异步访问的,如果用Flow和协程配合把Room包装起来也会让代码阅读起来非常的流畅,并且Room和Flow流配合也是数据响应的。
https://developer.android.google.cn/training/data-storage/room
Flow
Flow其实和RxJava解决的事情是一样的。链式调用,完美解决回调函数的使用出现的无限代码缩进;异步,和协程配合可以解决异步调用;Flow可以其他技术兼容,比方说Room、ViewModel并且可以与LiveData相互转化,就是说Flow可以串起来整个架构的数据流。
创建数据流
1.flow
flow {
emit(1)
}
2.flowof 其中也是调用了flow()去初始化一个Flow
flowOf(1)
修改数据流
修改数据流的方法有很多,基本上RxJava有的基本上都可以找到对应的方法。这里只说几个重要的:
flowOf(1)
.flowOn(Dispatchers.IO)
.onStart {
Log.e("MainActivity", "onStart")
}
.onCompletion {
Log.e("MainActivity", "onCompletion")
}
.onEach {
liveData.value = it
}
.catch {
Log.e("MainActivity", "catch it=$it")
}
flowOn()作用是指定被观察者在哪类线程中执行,很像RxJava,接受一个Dispatchers参数,Dispatchers.IO IO线程,一般处理网络请求;Dispatchers.Main 主线程,一般处理UI展示;Dispatchers.Default 默认线程由系统分配,主要处理CPU密集型操作,比方说一个庞大的遍历。flowOn()和RxJava的observeOn()一样都是作用于调用链它以上的部分。但是仅仅这样写还不会实现异步的目的,Flow必须配合协程完成异步请求。
onStart()是在Flow流成功执行之前最先触发的方法,这里可以做一些准备工作。
onCompletion()是在Flow流成功执行完成之后最后一个方法,表示完成。都可以在RxJava中找到影子。
onEach()这个方法的传入的高阶函数有个参数传回表示Flow最后的对象,就是整个Flow流的目的。相当于RxJava方法的onNext()。
catch()是调用链抛出异常之后会触发此方法的执行,参数是Exception和RxJava一样,它的作用域也是在调用链它以上的部分,所以一般写在整个流的最后,确保异常不会外流导致崩溃。
此外还有很多二级方法可供使用:https://www.jianshu.com/p/0d0ee5fd4931
Flow转化LiveData
最简单的使用
flowOf(1).asLiveData()
https://developer.android.google.cn/kotlin/flow
协程
上面说了Flow流,但是单独Flow流是没法进行工作的,它只是一种设计结构,这里说说如果配合协程进行异步任务开发。
协程是一种并发设计模式,您可以在 Android 平台上使用它来简化异步执行的代码。
特点
轻量:您可以在单个线程上运行多个协程,因为协程支持挂起suspend,不会使正在运行协程的线程阻塞。挂起比阻塞节省内存,且支持多个并行操作。
内存泄漏更少:使用结构化并发机制在一个作用域内执行多项操作。
内置取消支持:取消操作会自动在运行中的整个协程层次结构内传播。
Jetpack 集成:许多 Jetpack 库都包含提供全面协程支持的扩展。某些库还提供自己的协程作用域,可供您用于结构化并发。
如需在 Android 项目中使用协程,请将以下依赖项添加到应用的 build.gradle 文件中:
dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
}
第一个依赖的协程自身的功能包,第二个依赖提供了viewModelScope扩展属性可以直接调用launch(默认参数为Dispatchers.Main)
挂起函数
被suspend关键字修饰的函数为可挂起函数
private suspend fun getAllUser()
挂起函数可以调用普通函数,普通函数不可以直接调用挂起函数,挂起函数只能在协程中或其他挂起函数中调用。挂起函数就是将函数的执行暂停,然后省下来资源去做别的事情,挂起函数挂起协程时,不会阻塞协程所在的线程。挂起函数是协程实现异步任务的主要手段。
协程的创建
GlobalScope.launch(Dispatchers.Main)
launch()是CoroutineScope的扩展函数,而GlobalScope又继承CoroutineScope,核心的东西都在CoroutineScope中,CoroutineScope可以看作协程自身,Dispatchers.Main指定协程执行在主线程中但不会阻塞线程。launch()方法有一个返回值Job,类似于RxJava的Disposable,可以用全局变量持有,它可以取消并且有简单生命周期。
runBlocking {}:是创建一个新的协程同时阻塞当前线程,直到协程结束。这个不应该在协程中使用,主要是为main函数和测试设计的。
withContext {}:不会创建新的协程,在指定协程上运行挂起代码块,并挂起该协程直至代码块运行完成。
async {}:可以实现与 launch()一样的效果,在后台创建一个新协程,唯一的区别是它有返回值,因为CoroutineScope.async {}返回的是 Deferred 类型。Deferred是Job的子类,区别在于完成之后又返回值可以根据返回值做一些处理。
协程与Flow配合实现异步
GlobalScope.launch(Dispatchers.Main) {
getAllUserFlow()
.flatMapConcat {
deleteUserFlow(it)
}
.flowOn(Dispatchers.IO)
.onStart {
Log.e("MainActivity", "onStart")
}
.onCompletion {
Log.e("MainActivity", "onCompletion")
}
.catch {
Log.e("MainActivity", "catch it=${it.message}")
}
.collect()
}
Dispatchers.Main:表示协程在主线程上创建。
getAllUserFlow():调用网络服务接口,返回Flow携带数据。
.flowOn(Dispatchers.IO):表示被观察者就是getAllUserFlow()的操作在IO线程上执行。
.collect():相当于RxJava的subscribe(),不过它可以不携带参数,也可以携带,建议是用.OnEach()处理返回的数据,.collect()只做订阅,因为Flow属于冷链必须订阅才能激活。因为之前讲过.catch()方法只能捕获到它前面的流操作,所以使用携带参数的.collect(),在处理函数中若抛出异常是直接导致程序崩溃的。
这样协程的基本使用的可以运用到项目中了。https://developer.android.google.cn/kotlin/coroutines
koin
https://zhuanlan.zhihu.com/p/188485918