你对Jetpack 架构组件了解多少?,我的Android美团求职之路

class MainViewModelFactory(private val countReserved: Int) : ViewModelProvider.Factory {

override fun create(modelClass: Class): T {

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_CREATEON_STARTON_RESUMEON_PAUSEON_STOPON_DESTROYON_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

它们与 Activity 的生命周期回调所对应的关系如图:你对Jetpack 架构组件了解多少?,我的Android美团求职之路_第1张图片

LiveData

======================================================================

LiveData 是一种可观察的数据存储器类。与常规的可观察类不同,LiveData 具有生命周期感知能力,意指它遵循其他应用组件(如 ActivityFragmentService)的生命周期。这种感知能力可确保 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() 方法

map()

这个方法的作用是将实际包含数据的 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 的观察者

switchMap()

我们通过一个实例来学习。根据传入的 userId 参数去服务器请求或者到是数据库中查找相应的 User 对象,但是这里只是模拟示例,因此每次传入的 userId 当作用户姓名来创建一个新的 User 对象即可

代码如下,先创建一个 Repository 单例类,模拟获取用户数据的功能:

object Repository {

fun getUser(userId: String) : LiveData{

val liveData = MutableLiveData()

liveData.value = User(userId,userId,0)

return liveData

}

}

下面获取 RepositoryLiveData 对象,

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() 方法接收两个参数:

  • 第一个:传入新增的 userIdLiveDataswitchMap() 方法会对它进行观察

  • 第二个:是一个转换函数,我们必须在这个转换函数中返回一个 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 官方推出的一个 ORMObject Relational Mapping 对象关系映射)框架,并将它加入了 Jetpack 中。

Room 的整体结构主要由 EntityDaoDatabase 这三个部分组成,每个部分都有自己明确的职责:

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

  • DaoDao 是数据访问的意思,同长会在这里对数据库的各项操作进行封装。在实际编程中,逻辑层就不用和底层数据打交道了,直接和 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 的三个部分来一一进行实现

定义 Entity:

我们直接就使用 上文定义的 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 ,使得主键的值自动生成

定义 Dao:

这一部分比较关键,因为所有访问数据库的操作都是在这里封装

新建一个 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

写在最后

对程序员来说,很多技术的学习都是“防御性”的。也就是说,我们是在为未来学习。我们学习新技术的目的,或是为了在新项目中应用,或仅仅是为了将来的面试。但不管怎样,一定不能“止步不前”,不能荒废掉。

你对Jetpack 架构组件了解多少?,我的Android美团求职之路_第2张图片

![你对Jetpack 架构组件了解多少?,我的Android美团求职之路_第3张图片
[]

你对Jetpack 架构组件了解多少?,我的Android美团求职之路_第4张图片
你对Jetpack 架构组件了解多少?,我的Android美团求职之路_第5张图片

文章以下内容会给出阿里与美团的面试题(答案+解析)、面试题库、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免费领取~

你可能感兴趣的:(程序员,面试)