class MainViewModelFactory(private val countReserved: Int) : ViewModelProvider.Factory {
override fun
return MainViewModel(countReserved) as T
}
}
我们这里实现了接口要求我们的 create
方法,在方法里面我们创建并返回了一个 MainViewModel
的实例,为什么我们这里就可以创建 MainViewModel
的实例了呢?因为 create()
方法的执行时机和 Activity
的生命周期无关,所以不会产生之前提到的问题。
最后修改 activity 中的代码,如下:
class MainActivity : AppCompatActivity() {
lateinit var viewModel: MainViewModel
lateinit var sp: SharedPreferences
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
sp = getSharedPreferences(“count_reserved”,Context.MODE_PRIVATE)
val countReserved = sp.getInt(“count_reserved”,0)
viewModel = ViewModelProvider(this,MainViewModelFactory(countReserved))
.get(viewModel::class.java)
…
btn_clear.setOnClickListener {
viewModel.counter = 0
refreshCounter()
}
refreshCounter()
}
override fun onPause() {
super.onPause()
val edit = sp.edit()
edit.putInt(“count_reserved”,viewModel.counter)
edit.apply()
}
…
}
Lifecycles
========================================================================
顾名思义,Lifecycles
是一个用来感知 Activity
生命周期的组件,下面来学习下简单用法。
简单使用:
新建一个 MyObserver
类,并实现 LifecycleObserver
接口
class MyObserver : LifecycleObserver{
}
LifecycleObserver
这是一个空方法接口,我们可以在 MyObserver
中定义任何方法,如果需要感知 Activity
的生命周期就需要为方法添加注解,如下所示:
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
注解,并传入了一种生命周期事件,生命周期事件一共有 7 种,分别是:ON_CREATE
、ON_START
、ON_RESUME
、ON_PAUSE
、ON_STOP
、ON_DESTROY
、ON_ANY
。前六种分别匹配 Activity
中相应的的生命周期回调,最后一种表示可以匹配 Activity
的任何生命周期回调。
接下来就是需要 LifecycleOwner
去通知 MyObserver
生命周期发生了变化,它可以使用如下的语法结构去通知 MyObserver
lifecycleOwner.lifecycle.addObserver(MyObserver())
这里 LifecycleOwner
调用了 getLifecycle
方法,得到一个 Lifecycle
对象,接着调用 addObserver
来观察 LifecycleOwner
的生命周期,再把 MyObserver
传进去。
LifecycleOwner
是个什么?如何让获取一个 LifecycleOwner
的实例?
大多数情况下,只要 Activity
是继承自 AppCompatActivity
的,或者 Fragment
是继承自 androidx.fragment.app.Fragment
的,那么它们本身就是一个 LifecycleOwner
的实例,所以我们在 Activity
中就可以这样写
class MainActivity : AppCompatActivity() {
…
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
…
lifecycle.addObserver(MyObserver())
}
…
}
现在程序可以感知到 Activity
的生命周期变化,但没法主动获知当前的生命周期状态,解决这个问题,只需要在 MyObserver
的构造函数中将 Lifecycle
对象传进去即可,如下:
class MyObserver(val lifecycle: Lifecycle) : LifecycleObserver{…}
有了 Lifecycle
对象后,就可以在任何地方调用 lifecycle.currntState
来主动获取当前的生命周期状态。
lifecycle.currntState
返回的生命周期状态时一个枚举类型,一共有 5 种状态类型,如下:
INITIALIZED
DESTROYED
CREATED
STARTED
RESUMED
LiveData
======================================================================
LiveData
是一种可观察的数据存储器类。与常规的可观察类不同,LiveData
具有生命周期感知能力,意指它遵循其他应用组件(如 Activity
、Fragment
或 Service
)的生命周期。这种感知能力可确保 LiveData
仅更新处于活跃生命周期状态的应用组件观察者。
简单使用
LiveData
可以包含任何类型的数据,并在数据发生变法的时候通知给观察者
修改 MainViewModel
中的代码,如下:
class MainViewModel(countReserved: Int) : ViewModel() {
var counter = MutableLiveData()
init {
counter.value = countReserved
}
fun plusOne() {
val count = counter.value ?: 0
counter.value = count + 1
}
fun clear() {
counter.value = 0
}
}
这里将 counter
变量修改成了一个 MutableLiveData
对象,这是一种可变的 LiveData
。它主要有三种读写数据的方法,分别是:
getvalue()
//用于获取 LiveData
中包含的数据
setValue()
//用于给 LiveData
设置数据,但是只能在主线程中调用
postValue()
//用于在非主线程中给 LiveData
设置数据
下面来修改 MainActivity 中的代码:
class MainActivity : AppCompatActivity() {
…
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
…
btn_plus.setOnClickListener{
viewModel.plusOne()
}
btn_clear.setOnClickListener {
viewModel.clear()
}
viewModel.counter.observe(this, Observer { count ->
tv_info.text = count.toString()
})
}
override fun onPause() {
super.onPause()
val edit = sp.edit()
edit.putInt(“count_reserved”,viewModel.counter.value ?: 0)
edit.apply()
}
}
这里 counter
变量已经变成了一个 LiveData
对象,任何 LiveData
对象都可以调用它的 observe()
方法来观察数据的变化。observer()
方法接收两个参数:第一个参数是一个 LifecycleOwner
对象,这里也就是 Activity
自己。第二个参数是一个 Observer
接口,当 counter
中包含的数据发生变化时,就会回调到这里。
关于 observe()
方法,Google 官方在专门面向 Kotlin 语言的 API 中提供了很多好用的语法扩展,要使用它需添加依赖:
implementation ‘androidx.lifecycle:lifecycle-livedata-ktv:2.2.0’
之后我们就可以使用如下结构的 observe()
方法了
viewModel.counter.observe(this) { count ->
tv_info.text = count.toString()
}
以上是 LiveData
基本用法,可以正常使用,但仍然不是最规范的用法, 主要问题是我们将 counter
这个可变的 LiveData
暴露给了外部,这样在 ViewModel
外面也是可以给 counter
设置数据,从而破坏了 LiveData
数据的封装性
比较推荐的做法是,永远只暴露不可变的 LiveData
给外部,下面来改造下 MainViewModel
,如下:
class MainViewModel(countReserved: Int) : ViewModel() {
val counter : LiveData
get() =_counter
private val _counter = MutableLiveData()
init {
_counter.value = countReserved
}
fun plusOne() {
val count = _counter.value ?: 0
_counter.value = count + 1
}
fun clear() {
_counter.value = 0
}
}
这里 _counter
变量对于外部便是不可见的,而我们又定义了一个 counter
变量,类型声明为不可变的 LiveData
,并在它的 get()
属性方法中返回 _counter
变量。
这样当外界调用 counter
变量时,实际上获得的是 _counter
的实例,但是无法给 counter
设置数据,从而保证了 LiveData
数据的封装性
LiveData 中的 map 和 switchMap
LiveData
为了能够应对各种不同需求场景,提供了两种转换方法:map()
和 switchMap()
方法
这个方法的作用是将实际包含数据的 LiveData
和仅用于观察数据的 LiveData
进行转换,实例如下:
比如有一个 User
类,其中包含用户的姓名、年龄,如下:
data class User(var firstName: String,var lastName:String, var age: Int) {}
这里包含了三个数据,但如果我们的 Activity
明确了只需要用户的姓名,不关心年龄时 还将整个 User
暴露出去就不太合适,这时候就可以使用 map()
方法,将 User
类型的 LiveData
自由的转型成任意其他类型的 LiveData
,如下:
class MainViewModel(countReserved: Int) : ViewModel() {
private val userLiveData = MutableLiveData()
val userName : LiveData = Transformations.map(userLiveData){ user ->
“${user.firstName} ${user.lastName}”
}
…
}
map()
方法接收两个参数:
第一个:参数是原始的 LiveData
对象;
第二个:参数是一个转换函数
我们就只需要在转换函数里写具体的逻辑即可
另外,我们将 userLiveData
声明成了 private
,以保证数据的封装性,外部只需要观察 userName
就可以了,当 userLiveData
数据发生变化时,map()
方法会监听到变化并执行转换函数中的逻辑,然后将转换之后的数据通知给 userName
的观察者
我们通过一个实例来学习。根据传入的 userId
参数去服务器请求或者到是数据库中查找相应的 User
对象,但是这里只是模拟示例,因此每次传入的 userId
当作用户姓名来创建一个新的 User
对象即可
代码如下,先创建一个 Repository
单例类,模拟获取用户数据的功能:
object Repository {
fun getUser(userId: String) : LiveData{
val liveData = MutableLiveData()
liveData.value = User(userId,userId,0)
return liveData
}
}
下面获取 Repository
的 LiveData
对象,
class MainViewModel(countReserved: Int) : Int{
…
fun getUser(userId: String) : LiveData {
return Repository.getUser(userId)
}
}
在 Activity
中观察 LiveData
viewModel.getUser(userId).observe(this) { ->
}
以上的这种呢做法完全错误,因为每次调用 getUser()
方法返回的都是一个新的 LiveData
实例,而上述写法会一直观察老的 LiveData
实例,这种情况下,LiveData
是不可能观察的
以下是正确做法,
借助 switchMap
,它的使用场景比较固定:如果 VIewModel
中的某个 LiveData
对象是调用另外的方法获取的,那么就可以借助 switchMap()
方法,将这里 LiveData
对象转换成另外一个可观察的 LiveData
对象。修改 MainViewModel
中的代码,如下:
class MainViewModel(countReserved: Int) : ViewModel() {
…
private val userIdLiveData = MutableLiveData()
val user: LiveData = Transformations.switchMap(userIdLiveData) { userId ->
Repository.getUser(userId)
}
fun getUser(userId: String) {
userIdLiveData.value = userId
}
}
switchMap()
方法接收两个参数:
第一个:传入新增的 userIdLiveData
,switchMap()
方法会对它进行观察
第二个:是一个转换函数,我们必须在这个转换函数中返回一个 LiveData
对象,因为 switchMap()
方法的工作原理就是要将转换函数中返回的 LiveData
对象转换成另一个可观察的 LiveData
对象。
现在 user
对象就是一个可观察的 LiveData
对象了
修改 Activity
中的代码,如下:
class MainActivity : AppCompatActivity() {
…
override fun onCreate(savedInstanceState: Bundle?) {
…
btn_getUser.setOnClickListener {
val userId = (0…1000).random().toString()
viewModel.getUser(userId)
}
viewModel.user.observe(this,{
tv_info.text = it.firstName
})
}
}
现在已经实现了功能并且可以正常运行了。
当我们的 ViewModel
中某个获取数据的方法有可能是没有参数的,这个时候该怎么办呢?
这里我们先创建一个空的 LiveData
对象:
class MyViewModel : ViewModel(){
private val refreshLiveData = MutableLiveData
val refreshResult = Transformatinos.switchMap(refreshLiveData) {
Repository.refresh()
}
fun refresh() {
refreshLiveData.value = refreshLiveData.value
}
}
在 refresh()
方法中,只是将 refreshLiveData
原有的数据取出来(默认是空),再重新设置到 refreshResult
当中,这样就能触发一次数据变化。
在 LiveData
内部不会判断即将设置的数据和原有数据是否相同,只要调用了 setValue()
或 postValue()
方法,就一定会触发数据变化事件,然后我们只需要在 Activity
中观察 refreshResult
这个 LiveData
对象即可
Room
==================================================================
Room
是 Google 官方推出的一个 ORM
(Object Relational Mapping
对象关系映射)框架,并将它加入了 Jetpack
中。
Room
的整体结构主要由 Entity
、Dao
、Database
这三个部分组成,每个部分都有自己明确的职责:
Entity
:用于定义封装实际数据的实体类,每个实体类都会在数据库中都有一张对应的表,并且表中的列是根据实体类中的字段自动生成的。
Dao
:Dao
是数据访问的意思,同长会在这里对数据库的各项操作进行封装。在实际编程中,逻辑层就不用和底层数据打交道了,直接和 Dao
层进行交互就可。
Database
:用于定义数据库中的关键信息,包括数据库的版本号、包含哪些实体类以及提供 Dao
层的访问实例。
Room 的具体用法
添加依赖:
…
apply plugin: ‘kotlin-kapt’
dependencies{
…
implementation ‘androidx.room:room-runtime:2.2.5’
kapt ‘androidx.room:room-compiler:2.2.5’
}
kapt
只能在 Kotlin
项目中使用,如果是 Java
项目的话,使用 amotationProcessor
即可
接下来按照刚才介绍的 Room
的三个部分来一一进行实现
我们直接就使用 上文定义的 User 类来改造
@Entity
data class User(var firstName: String,var lastName:String, var age: Int) {
@PrimaryKey(autoGenerate = true)
var id: Long = 0 //给每个实体类都添加一个字段,并设为主键
}
@Entity
注解:将 User
声明成了一个实体类
@PrimaryKey
注解:将 id
字段设为主键,将 autoGenerate
设置为 true
,使得主键的值自动生成
这一部分比较关键,因为所有访问数据库的操作都是在这里封装
新建一个 UserDao 接口,如下:
@Dao
interface UserDao {
@Insert
fun insetUser(user: User) : Long
@Update
fun updateUser(newUser: User)
@Query(“select * from User”)
fun loadAllUser() : List
@Query(“select * from User where age > :age”)
fun loadUsersOlderThan(age: Int) : List
@Delete
fun deleteUser(user: User)
@Query(“delete from User where lastName = :lastName”)
fun deleteUserByLastName(lastName: String) : Int
}
@Dao
注解:让 Room
能够识别到 UserDao
是一个 Dao
@Insert
注解:表示将参数传入 User
对象插入数据库中,插入完成后还会返回自动生成的主键 id
值
对程序员来说,很多技术的学习都是“防御性”的。也就是说,我们是在为未来学习。我们学习新技术的目的,或是为了在新项目中应用,或仅仅是为了将来的面试。但不管怎样,一定不能“止步不前”,不能荒废掉。
文章以下内容会给出阿里与美团的面试题(答案+解析)、面试题库、Java核心知识点梳理等,需要这些文档资料的,直接点击我的GitHub免费领取~
@Query(“delete from User where lastName = :lastName”)
fun deleteUserByLastName(lastName: String) : Int
}
@Dao
注解:让 Room
能够识别到 UserDao
是一个 Dao
@Insert
注解:表示将参数传入 User
对象插入数据库中,插入完成后还会返回自动生成的主键 id
值
对程序员来说,很多技术的学习都是“防御性”的。也就是说,我们是在为未来学习。我们学习新技术的目的,或是为了在新项目中应用,或仅仅是为了将来的面试。但不管怎样,一定不能“止步不前”,不能荒废掉。
[外链图片转存中…(img-t3pLgA2U-1646483965332)]
[外链图片转存中…(img-k5Hw4fPB-1646483965332)]
[外链图片转存中…(img-UvXKJp9E-1646483965333)]
[外链图片转存中…(img-zlaZuI5Y-1646483965333)]
文章以下内容会给出阿里与美团的面试题(答案+解析)、面试题库、Java核心知识点梳理等,需要这些文档资料的,直接点击我的GitHub免费领取~