当前位置:   article > 正文

Jetpack全家桶之架构组件——生命周期管理库(Lifecycle、LiveData 、ViewModel)_jitpack 全家桶 协程

jitpack 全家桶 协程

生命周期管理库 (Lifecycles) 由三个组件构成,包括 Lifecycle、LiveData 和 ViewModel。它可以用来解决常见的生命周期问题

一、认知

在介绍说正题之前,先来说说下啥是Jetpack,它跟AndroidX又有怎样的关联(不能一上来就瞎头巴脑学一通最后还不知道自己学的是哪个派系的,要有金字塔学习方式)。首先上个官方的定义:

Jetpack 是一个由多个库组成的套件,可帮助开发者遵循最佳做法、减少样板代码并编写可在各种 Android 版本和设备中一致运行的代码,让开发者可将精力集中于真正重要的编码工作

早期google官网有对Jetpack进行分类的(如下图),但目前看不到划分图片或说明了,原因应该是目前内容更丰富了不止是局限于此。
在这里插入图片描述
下面是自己对目前Jetpack库的一些划分和认知:

第一个是核心类,你也可以把它理解为基础类,也就是说我们一个最基本的 Android 工程都会默认依赖这些组件库

activity.*fragment*appcompat*corearch.coreannotationcollectionCustomview

第二个是架构组件,Jetpack 推出之后很令人兴奋的一点,就是 Google 引入了现代 Android 应用开发的架构指南,结合 MVVM 的架构设计,帮助我们轻松的处理 UI 与业务逻辑之间的关系

databinding*LifecycleLiveDataViewModelstartuproom*paging*hilt*navigation*work*datastoresavedstate

第三个是 UI 组件,这里需要说明一点,大多数的 UI 组件其实都包含着核心组件中的 appcompat * 中了,这里列出的是 Jetpack 中以独立组件库存在的 UI 组件

viewpage2composedrawerlayoutcoordinatorlayoutrecyclerviewpaletteconstraintlayoutemojitransitionswiperefreshlayoutwebktbrowserMaterial Design Components *、`dynamicanimation等

第四个是特殊业务组件,根据不同的业务场景,选择性使用

multidexcamera*media2slicesharetargetpreferencewindowads

第五个非移动端或者不常用组件,涉及游戏、车载、TV 等或平时极少使用的组件

carweartvprovidergamesenterpriserecommendation(TV相关)、tracingprint

第六个是弃用的组件,有一些是因为官方不再更新维护了,有一些是在 Jetpack 中有更好的替代解决方案,如果我们的项目中还在使用这些组件库的话,建议尽快替换到最新的替代组件上

cardview(MaterialCardView替代)media(media2替代)gridlayoutsqlite(room替代)percentlayout(coordinatorlayout替代)legacyloaderlocalbroadcastmanager

第七个是用于测试的组件

benchmarktest*

瞎BB了这么多,AndroidX嘞这是啥,不急不急稳住,这就徐徐道来。AndroidX包含了啥:其实就是如上说明的这些各个组件的实现包装每个功能都是放到AndroidX的包名下而已(命名空间),此外还有之前的support4、support7等各个support包的大统一,还有一些频繁更新和迭代的特性功能,使得在不同的 Android 版本和不同的设备上,实现行为一致的工作代码说白了AndroidX是针对程序猿的一种说法,Jetpack是google对外宣传的一种说法,其实本质都是说的是一个东西,角度不一样而已

说明下:为毛这些功能不直接集成到Android系统SDK中呢?为毛还要单独放到AndroidX中呢?其实也很好理解,一般Android系统更新频率在半年或者一年更新一次,AndroidX可以频繁更新呀,有bug可以及时修复

二、Lifecycles

作用:感知Android系统组件Activity、Service(LifecycleService)、Fragment、Application应用程序生命周期,解耦页面与组件。

说明作用之后,来说下为什么我们要使用它,而不是自己定义一个观察者,然后在对应的组件的生命周期的方法里调用自定义的观察者方法(前期google没推出这个组件我们就是这么干的,如下代码片)。

自定义缺陷:

1.自定义需要在组件的生命周期方法里添加很多额外的代码

2.代码入侵式强

class MyObserver{
	fun activityStart(){}
	fun activityStop(){}
}
class MainActivity:AppCompatActivity{
	lateinit var observer:MyObserver
	override fun onCreate(savedInstanceState: Bundle?) {
		observer = MyObserver()
	}
	override fun onStart(){
		super.onStart()
		observer.activityStart()
	}
	override fun onStop(){
		super.onStop()
		observer.activityStop()
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

使用

1.定义一个观察者,继承LifecycleObserver

class MyObserver : LifecycleObserver{
	@OnLifecycleEvent(Lifecycle.Event.ON_START)
	fun activityStart(){
		//监听生命周期方法后需要处理的动作
	}
	@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
	fun activityStop(){
		//监听生命周期方法后需要处理的动作
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

说明:@OnLifecycleEvent注解类型一共有7种:ON_START、ON_START、ON_RESUME、ON_PAUSE、ON_STOP、ON_DESTORY、ON_ANY(表示任何生命周期方法都会回调)

2.添加监听

1.在Activity中使用

class MainActivity : AppCompatActivity{
	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		lifecycle.addObserver(MyObserver())//就添加这一行代码就够了
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

说明:这里MainActivity需要继承AppCompatActivity,因为AppCompatActivity实现了LifecycleOwner接口,这也是我们规避掉了自定义实现需要自己手动添加代码触发逻辑的原理

2.在Application中使用

class MyApplication : Application{
	override fun onCreate(){
		super.onCreate()
		ProcessLifecycleOwner.get().lifecycle().addObserver(new ApplicationObserver())
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

3.属于补充,非必须流程,如果需要在观察者对象中知道当前处于什么状态生命周期那么可以把Lifecycle当做参数传递

class MyObserver(val lifecycle:Lifecycle):LifecycleObserver{
	lifecycle.currentState//主动获取当前的生命周期状态
}
  • 1
  • 2
  • 3

三、ViewModel

作用

1.专门用于存放页面相关的数据,减少Activity中的逻辑,提高类的可维护性和单元测试

2.保存瞬态数据的丢失(屏幕的旋转)

3.防止异步调用的内存泄漏

4.在多个Fragment之前共享数据(仅限于同一个Activity中的Fragment)

如上说明了4种作用,上一张官方的生命周期图以解疑惑(一图胜千语)

在这里插入图片描述

基本用法

1.添加依赖

dependencies{
	implementation "andoridx.lifecycle:lifecycle-extensions:2.1.0"
}
  • 1
  • 2
  • 3

2.创建一个与页面绑定相关数据的ViewModel类

class MainViewModel : ViewModel(){
	var counter = 0 //用于计数
}
  • 1
  • 2
  • 3

3.在页面中使用ViewModel类,获取数据

class Mainctivity : AppCompatActivity{
	lateinit var viewModel : MainViewModel
	override fun onCreate(savedInstanceState:Bundle?){
		……
		//这里必须使用ViewModelProviders.of(<你的页面实例>).get(<你的ViewModel>::class.java),不能使用new一个ViewModel的方式,因为ViewModel具有它自身独特的生命周期
		viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
		plusOneBtn.setOnclickListener{
			viewModel.counter++
			refreshCounter()
		}
		refreshCounter()
	}
	private fun refreshCounter(){
		infoText.text = viewModel.counter.toString()
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

4.如果ViewModel子类的构造函数存在传参的方式,这时候就需要使用工厂类的方式创建ViewModel了

class MainViewModel(countReserved:Int):ViewModel(){
	var counter = countReserved
}
class MainViewModelFactory(countReserved:Int):ViewModelPrvider.Factory{
	override fun <T: ViewModel> create(modelClass:Class<T>):T{
		retrun MainViewModel(countReserved) as T
	}
}
class Mainctivity : AppCompatActivity{
	lateinit var viewModel : MainViewModel
	override fun onCreate(savedInstanceState:Bundle?){
		……
		//这里必须使用ViewModelProviders.of(<你的页面实例>).get(<你的ViewModel>::class.java),不能使用new一个ViewModel的方式,因为ViewModel具有它自身独特的生命周期
		viewModel = ViewModelProviders.of(this,MainViewModelFactory(countReserved)).get(MainViewModel::class.java)
	}
	
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

进阶使用

1.ViewModel 配合数据绑定

  • 与LiveData 单独结合使用

    1.使用LiveData返回简单的数据

    之前我们都是在Activity中手动获取ViewModel中的数据,但ViewModel却无法将数据的变化主动通知给Activity,所以接下来就引出了LiveData来解决这个问题。LiveData是一个可被观察的对象,在数据发送变化时可以自动触发通知给观察者。LiveData可用包含任何的数据类型。

    class MainViewModel(countReserved:int):ViewModel(){
    	val counter = LiveData<Int>
        	get() = _counter
        private val _counter = MutableLiveData<Int>()
    	init{
    		_counter.value = countReserved
    	}
    	fun plusOne(){
    		val count = _counter.value?:0
    		_counter.value = count + 1
    	}
    	fun clear(){
    		_counter.value = 0
    	}
    }
    class MainActivity : AppCompatActivity{
        override fun onCreate(savedInstanceState:Bundle?){
            ……
            plusOneBtn.setOnClickListener{
                viewModel.plusOne()
            }
            clearBtn.setOnclickListener{
                viewModel.clear()
            }
            //观察数据的变化
            viewModel.counter.observe(this,Observer{count ->
                infoText.text = count.toString()
            })
        }
    }
    //说明下:MutableLiveData是一种可变的LiveData,主要有3中读写数据的方法,分别是getValue()、setValue()在主线程中赋值、postValue()在非主线程中给LiveData设置数据
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    2.使用map和switchMap来变换LiveData返回获取的数据

    //1.map使用场景
    data class User(val firstName:String,var lastName:String,var age:Int)
    class MainViewModel(countReserved:Int):ViewModel(){
    	val userLivedData = MutableLiveData<User>()
        val userName : LiveData<String> = Transformations.map(userLivedData){user ->
            "${user.firstName} #{user.lastName}"
        }
    }
    //2.switchMap使用场景
    //当某个LiveData对象是调用另一个方法获取,比如如下:
    object Repository{
        fun getUser(userId:String):LiveData<User>{
            val liveData = MutableLiveData<User>()
            liveData.value = User(userId,userId,0)//通常这个值从网络获取,这里只是简单的模拟
            return 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
        }
        //错误用法
        /**
        * 原因:Repository.getUser(userId)内部每次都是重新new一个User对象,
        * 所以在Activity中使用observe去观察该对象变换的话,无法感知数据的变化,
        * 因为每次都是一个新的对象LiveData,是一个不可观察的
        */
        fun getUser(userId:String):LiveData<User>{
            return Repository.getUser(userId)
        }
    }
    //说明:如果getUser方法没有入参,则可以直接使用自己给自身复制(userIdLiveData.value = userIdLiveData.value),
    //然后就会自动刷新感知触发
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
  • 与Data Binding结合使用,实现数据与UI双向绑定

    作用

    Data Binding是一个通过观察数据变化来更新 UI 的组件库。通过 ViewModelLiveDataData Binding的组合,您可以移除以往给 LiveData 添加观察者的做法,改为直接在 XML 中绑定View ModelLiveData

    使用

    1.在 Android Studio 中 build.gradle 配置开启 Data Binding

    dataBinding {
       enabled = true
    }
    
    • 1
    • 2
    • 3

    2.Data Binding布局使用layout包裹

    <layout xmlns:android="http://schemas.android.com/apk/res/android">
        <data>
        	<!-- 定义ViewModel数据变量 -->
            <variable name="viewmodel"
            	type="com.android.MyViewModel"/>
        </data>
    	<TextView
                android:id="@+id/userName"
                android:text="@{viewmodel.userName}"<!-- 使用数据变量值 -->
                android:layout_height="wrap_content"
                android:layout_width="wrap_content"/>
    </layout>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    3.Acitivity中使用,调用binding.setLifecycleOwner(this) 方法,然后将 ViewModel 传递给 binding对象,就可以将 LiveDataData Binding结合起来。经过该操作后,那么 UI 将根据 LiveData 值的改变自动刷新

    class MainActivity : AppCompatActivity() {
    
        // 这个ktx扩展需要依赖 androidx.activity:activity-ktx:1.0.0
        private val myViewModel: MyViewModel by viewModels()
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
             //填充视图并创建 Data Binding 对象
            val binding: MainActivityBinding = 
                DataBindingUtil.setContentView(this, R.layout.main_activity)
    
            //声明这个 Activity 为 Data Binding 的 lifecycleOwner
            binding.lifecycleOwner = this
    
            // 将 ViewModel 传递给 binding
            binding.viewmodel = myViewModel
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    说明:

    • Binding工具类生成规则:
      默认以布局命名规则生成activity_main.xml,例如布局文件名为:activity_main,则对应生成则为:ActivityMainBinding

    • 控件ID生成规则:
      默认已驼峰命名法,例如控件ID为:user_id,则后续使用binding实例化点userId即可

2.ViewModel 中的 Saved State —— 后台进程重启时,ViewModel 的数据恢复

使用背景

Activity 和 Fragment 通常会在下面三种情况下被销毁:

​ 1.从当前界面永久离开:用户退出应用,对应 Activity 实例被永久关闭

​ 2.Activity 配置 (configuration) 被改变: 例如,旋转屏幕等操作**(可以直接使用onSaveInstanceState、ViewModel解决)**

​ 3.应用在后台时,其进程被系统杀死:在设备剩余运行内存不足,系统又亟须释放一些内存的时候。当进程在后台被杀死后,用户又返回该应用时,Activity 也需要被重建。(可以使用onSaveInstanceState解决 )

如上第二、三种情况通常需要我们恢复数据,此时可以使用 **SavedStateHandle**解决此问题。

使用

1.添加依赖

implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0"
  • 1

2.修改调用 ViewModelProvider 的方式

//下面的 Kotlin 扩展需要依赖以下或更新新版本的 ktx 库:
//androidx.fragment:fragment-ktx:1.0.0(最新版本 1.2.4) 或
//androidx.activity:activity-ktx:1.0.0 (最新版本 1.1.0)
val viewModel by viewModels { SavedStateViewModelFactory(application, this) }
// 或者不使用 ktx
val viewModel = ViewModelProvider(this, SavedStateViewModelFactory(application, this)).get(MyViewModel::class.java)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

3.使用 SaveStateHandle(使用键值对的形式保存

class MyViewModel(state :SavedStateHandle) :ViewModel() {

    // 将Key声明为常量
    companion object {
        private val USER_KEY = "userId"
    }

    private val savedStateHandle = state

    fun saveCurrentUser(userId: String) {
        // 存储 userId 对应的数据
        savedStateHandle.set(USER_KEY, userId)
    }

    fun getCurrentUser(): String {
        // 从 saveStateHandle 中取出当前 userId
        return savedStateHandle.get(USER_KEY)?: ""
    }
    //使用 LiveData方式
    // getLiveData 方法会取得一个与 key 相关联的 MutableLiveData 
	// 当与 key 相对应的 value 改变时 MutableLiveData 也会更新
	private val _userId : MutableLiveData<String> = savedStateHandle.getLiveData(USER_KEY)

	// 只暴露一个不可变 LiveData 的情况
	val userId : LiveData<String> = _userId
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

3.viewModelScope —— Kotlin 协程与 ViewModel 的集成

定义

viewModelScope 是一个 ViewModel 的 Kotlin 扩展属性。它能在 ViewModel 销毁时 (onCleared()方法调用时) 退出。这样一来,只要您使用了 ViewModel,您就可以使用 viewModelScope 在 ViewModel 中启动各种协程,而不用担心任务泄漏

使用场景

当 ViewModel 被销毁时,通常都会有一些与其相关的操作也应当被停止。例如,假设您正在准备将一个位图 (bitmap) 显示到屏幕上。这种操作就符合协程的一些特征: 既不能在执行时阻塞主线程,又要求在用户退出相关界面时停止执行。使用协程进行此类操作时,就应当使用viewModelScope,因为如果想要使用协程,您要么限定一个作用域 (scope),要么获得一个作用域的访问权限。而在 ViewModel 中,我们可以使用 viewModelScope 来管理协程的作用域

示例:

class MyViewModel() : ViewModel() {

    fun initialize() {
        viewModelScope.launch {
            processBitmap()
        }
    }

    suspend fun processBitmap() = withContext(Dispatchers.Default) {
        // 在这里做耗时操作
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

4.ViewModel 与 NavGraph 集成

使用场景

一连串的流程(这一串流每次必须都是从头开始的不能从中间某个流程开始),如 登录或支付流程。

这个个人觉得使用的场景比较少,这里就直接贴下官方的链接地址

编程方式与导航组件交互

参考文献

ViewModel 四种集成方式

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/253908
推荐阅读
相关标签
  

闽ICP备14008679号