赞
踩
是专门存放与界面相关数据的,只要是界面看得到的数据,都应该存放在ViewMode中。
它的一个重要特性是,只有当界面退出时才会被销毁,界面的旋转不会被重新创建。
ViewModelProviders.of()
:已弃用 ViewModelProviders.of()
。您可以将 Fragment 或 FragmentActivity 传递给新的 ViewModelProvider(ViewModelStoreOwner)
构造函数,以便在使用 Fragment 1.2.0 时实现相同的功能。lifecycle-extensions
工件:在上面弃用ViewModelProviders.of()
后,此版本标志着弃用 lifecycle-extensions
中的最后一个 API,因此现在该工件已完全被弃用。我们强烈建议依赖于您需要的特定 Lifecycle 工件(例如,如果您使用的是 LifecycleService,则依赖于 lifecycle-service;如果您使用的是 ProcessLifecycleOwner,则依赖于 lifecycle-process)而不是 lifecycle-extensions
,因为将来不会有 lifecycle-extensions
的 2.3.0 版本。 先定义一个字段用于计数:
class MainActivityViewMode : ViewModel()
{
var counter = 0
}
再于mainactivity写入:
class MainActivity : AppCompatActivity() { private lateinit var textView: TextView lateinit var viewMode: MainActivityViewMode @SuppressLint("SetTextI18n") @RequiresApi(Build.VERSION_CODES.S) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 获取实例,不能创建 viewMode = ViewModelProvider(this)[MainActivityViewMode::class.java] ... linearLayout.addView(Button(this).apply { setOnClickListener { viewMode.counter++ refreshCount() } }) // 创建时刷新 refreshCount() ... } private fun refreshCount() { textView.text = viewMode.counter.toString() } }
上文的默认值是0,有没有办法在初始时,赋予默认值给viewMode呢?这就是本篇要介绍的情况。
下述代码就能保证不管程序进入后台还是退出,计数器值都不会丢失
// 在构造器添加需要初始化赋予的对象或类型 class MainActivityViewMode(countReserved : Int) : ViewModel() { var counter = countReserved } class MainViewModeFactory( private val countReserved: Int ) : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { // 因为这里实例化执行时机和Activity/Fragment生命周期无关, // 所以可以创建 return MainActivityViewMode(countReserved) as T } }
对应地,只需要修改下列代码就能完成操作:
viewMode = ViewModelProvider(
this,
MainViewModeFactory(countReserved)
)[MainActivityViewMode::class.java]
完整代码:
class MainActivity : AppCompatActivity() { private lateinit var textView: TextView lateinit var viewMode: MainActivityViewMode lateinit var sp : SharedPreferences @SuppressLint("SetTextI18n") @RequiresApi(Build.VERSION_CODES.S) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) sp = getPreferences(Context.MODE_PRIVATE) val countReserved = sp.getInt("counter", 0) viewMode = ViewModelProvider( this, MainViewModeFactory(countReserved) )[MainActivityViewMode::class.java] val scrollView = ScrollView(this) val linearLayout = LinearLayout(this) linearLayout.orientation = LinearLayout.VERTICAL scrollView.addView(linearLayout) textView = TextView(this).apply { textSize = 40f } linearLayout.addView(textView) linearLayout.addView(Button(this).apply { setOnClickListener { viewMode.counter++ refreshCount() } }) // 创建时刷新 refreshCount() setContentView(scrollView) } private fun refreshCount() { textView.text = viewMode.counter.toString() } override fun onPause() { super.onPause() sp.edit { putInt("counter", viewMode.counter) } } }
Lifecycles可以用来感知Activity生命周期。它独特的地方在于,可以再一个非Activity类中感知Activity的生命周期。
有一种解决方案是,自己创建一个监听器,重写Activity的onStart()、onCreate()方法等建立监听:
class MyObserver { fun onStart() {} fun onStop() {} } class MyActivity : AppCompatActivity() { lateinit var observer: MyObserver override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) observer = MyObserver() } override fun onStart() { super.onStart() observer.onStart() } override fun onStop() { super.onStop() observer.onStop() } }
而Lifecycles可以轻易实现,又不用些大量逻辑处理。
简单的实现方法是:
class MyObserver : LifecycleObserver
{
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onStart() {}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onStop() {}
}
只需要添加注解,就能实现对应方法监听,Lifecycles内置了七种枚举字段,它们分别是:
public enum Event { /** *LifecycleOwner的onCreate事件的常量。 */ ON_CREATE, /** * LifecycleOwner的onStart事件的常量。 */ ON_START, /** * LifecycleOwner的onResume事件的常量。 */ ON_RESUME, /** * LifecycleOwner的onPause事件的常量。 */ ON_PAUSE, /** * LifecycleOwner的onStop事件的常量。 */ ON_STOP, /** * LifecycleOwner的onDestroy事件的常量。 */ ON_DESTROY, /** *可用于匹配所有事件的事件常量。 */ ON_ANY; .... }
由于继承自AppCompatActivity
或andeoidx的Fragment
已经是LifecycleOwner实例,所以可以这么监听:
lifecycle.addObserver(MyObserver())
否则,你需要自己实现一个LifecycleOwner实例,调用方法如下:
lifecycleOwner.lifecycle.addObserver(MyObserver())
class MyObserver(val lifecycle: Lifecycle) : LifecycleObserver
{
...
}
只需要传递Lifecycle,调用Lifecycle.currentState
就能主动获取五种不同的生命周期状态,具体值的意思这里不详细说明,请自行查阅。
比如,获取的是CAEATED,说明onCreate()
已经执行了,但onStart()
还没有。获取的是STARTED,则onStart()
执行了,而onResume()
还没有。
是响应式组件,它能够在数据发生变化时通知给观察者。LiveData大多情况是与ViewMode配合使用,尽管它能单独使用。
如果说,ViewMode的作用是解耦,让Activity界面数据分离的话,那么LiveData就是去给响应数据变化提醒(因为在ViewMode中,并没有主动响应变化的值,而是修改后调用refreshCount()
方法)。
主动提醒值变化,有利于耗时操作和统一管理数据修改,不至于界面数据修改写的一塌糊涂。
比如说,如果ViewMode内部开启了线程去执行耗时逻辑,那么在点击按钮后去执行refreshCount()
获取值,显然没有新值,而处理完后的结果值,又恰恰无法主动显示。这就是LiveData重心。
不要把Activity传给ViewMode这样会导致回收困难,内存泄露。一个周期较短的类不能把实例传给较长的类保管,也不能静态引用周期短的类。
class MainActivityViewMode(countReserved : Int) : ViewModel()
{
var counter = MutableLiveData<Int>()
init {
// 这样以前保存的默认值就能恢复
counter.value = countReserved
}
fun add() {
counter.value = counter.value?.plus(1)
}
}
MutableLiveData 是一种可变的LiveData,他有三种读写数据的方式:
getValue()
获取数据setValue()
设置数据,只能在主线程调用postValue()
非主线程中设置数据这样设置好的值,响应值写法如下:
linearLayout.addView(Button(this).apply {
setOnClickListener {
viewMode.add()
}
})
viewMode.counter.observe(this) {
textView.text = it.toString()
}
也就是只要修改值即可,不用手动更新。
counter
这个可变的变量暴露出去了,这就导致设置形同虚设:既然想方设法把修改内容局限在只能引用ViewMode的方法以便于响应数据,就不能暴露其他任何修改的可能,解决方法之一是:只暴露不可变的LiveData给外部,因此如下改动:
// 在构造器添加需要初始化赋予的对象或类型 class MainActivityViewMode(countReserved : Int) : ViewModel() { val counter : LiveData<Int> get() = privateCounter private var privateCounter = MutableLiveData<Int>() init { privateCounter.value = countReserved } fun add() { privateCounter.value = privateCounter.value?.plus(1) } }
这种写法最为规范,也是官方推荐的写法。
详细代码:
class MainActivity : AppCompatActivity() { private lateinit var textView: TextView lateinit var viewMode: MainActivityViewMode lateinit var sp : SharedPreferences @SuppressLint("SetTextI18n") @RequiresApi(Build.VERSION_CODES.S) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) sp = getPreferences(Context.MODE_PRIVATE) val countReserved = sp.getInt("counter", 0) viewMode = ViewModelProvider( this, MainViewModeFactory(countReserved) )[MainActivityViewMode::class.java] // ui操作 val scrollView = ScrollView(this) val linearLayout = LinearLayout(this) linearLayout.orientation = LinearLayout.VERTICAL scrollView.addView(linearLayout) textView = TextView(this).apply { textSize = 40f } linearLayout.addView(textView) linearLayout.addView(Button(this).apply { setOnClickListener { viewMode.add() } }) viewMode.counter.observe(this) { textView.text = it.toString() } setContentView(scrollView) } override fun onPause() { super.onPause() sp.edit { putInt("counter", viewMode.counter.value ?:0 ) } } }
MainActivityViewMode.kt内:
// 在构造器添加需要初始化赋予的对象或类型 class MainActivityViewMode(countReserved : Int) : ViewModel() { val counter : LiveData<Int> get() = privateCounter private var privateCounter = MutableLiveData<Int>() init { privateCounter.value = countReserved } fun add() { privateCounter.value = privateCounter.value?.plus(1) } } class MainViewModeFactory( private val countReserved: Int ) : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { // 因为这里实例化执行时机和Activity/Fragment生命周期无关, // 所以可以创建 return MainActivityViewMode(countReserved) as T } }
&emspmap()
方法作用是:将实际包含数据的LiveData转换成仅用于观察的数据,也就是不照搬LiveData内数据,而是进行一定的修改。
比如说数据类包含了用户的姓名、年龄、性别:
data class User(var name : String, var age : Int, var sex : String)
由上一节内容知道,不能直接暴露,要用一个只读变量做桥梁:
class MainActivityViewMode(countReserved : Int) : ViewModel()
{
val user : LiveData<User>
get() = privateUser
private val privateUser = MutableLiveData<User>()
}
但假如只需要用户姓名和性别就够了,而再暴露额外的年龄不符合开发规范,map就解决了这个问题:
这里,只需要观察user就可以了
class MainActivityViewMode(countReserved : Int) : ViewModel()
{
// 顺序不能变 - Bug? 2022/7/7
private val privateUser = MutableLiveData<User>()
val user : LiveData<String> = Transformations.map(privateUser) {
"${it.name}, ${it.sex}"
}
}
虽然switchMap()比map()使用场景更加固定,但它更为常见:上文的User是在ViewMode创建的,这并不常见,更多的,是调用其他方法获取而来。
这里先定义类:
object Repository
{
// 每次调用,都会获取新的实例
fun getUser(userId : Int = 0): LiveData<User>
{
val liveData = MutableLiveData<User>()
liveData.value = User(age = userId)
return liveData
}
}
然后再在ViewMode添加方法:
fun getUser(userId: Int): LiveData<User> = Repository.getUser(userId)
这样子在主Activity中,如下引用是完全错误的,因为getUser(id)
调用获取的值是不变的,而再次调用,依然会重新观察老的实例,这样的情况下,这个类是不可观察的。
viewMode.getUser(id).observe(this) {}
而switchMap的作用就出来了:将函数
viewMode.getUser(id)
中返回的对象变成一个可观察的对象
这里,在适当的时机调用getUser()
,只需要观察user就可以了。
class MainActivityViewMode(countReserved : Int) : ViewModel()
{
private val userIdData = MutableLiveData<Int>()
val user : LiveData<User> = Transformations.switchMap(userIdData) {
Repository.getUser(it)
}
fun getUser(userId: Int) {
userIdData.value = userId
}
}
MainActivityViewMode
的getUser()
获取用户数据,不会发送任何对外请求,只是userIdData 数据发生变化而提醒switchMap
就会执行,才会对外请求对象并存放到user变量中。user
就行工作流程中有这么一段话:userIdData 数据发生变化而提醒,假如,并没有传递的参数,是不是无从谈起?答案是否定的,可以如下写:
class MainActivityViewMode(countReserved : Int) : ViewModel()
{
private val refreshData = MutableLiveData<Any?>()
val user : LiveData<User> = Transformations.switchMap(refreshData) {
Repository.refresh()
}
fun getUser(userId: Int) {
refreshData.value = refreshData.value
}
}
也就是说,LiveData并不关心数据是否相同,只要有调用setValue
或postValue
,就一定触发数据变化
这是一个基于数据库的ORM框架。这部分自行百度。
WorkManager适用于处理一些定时任务,他可以根据操作系统版本自动选择底层是AlarmManager实现还是JobScheduler,从而减低使用成本。它还支持周期性任务、链式任务处理等。
与Service没有直接联系,并不相同。Serivce在没有被销毁时是一直保持后台运行的,而WorkManager只是一个定时处理工具,它可以保证即使应用程序退出,甚至手机重启,之前的任务仍然会得到执行,因此适合执行一些定期和服务器交互内容。
另外,它所注册的周期性任务不一定准时执行。系统为了减少电池消耗,可能会触发时间临近的几个任务一起执行,这样以减少CPU被唤醒次数。
添加依赖:
implementation 'androidx.work:work-runtime:2.7.0-alpha04'
主要工作分三步:
WorkManager
的enqueue()
方法,系统就能得以执行。第一步:
class SimpleWork(context: Context, params: WorkerParameters) : Worker(context, params)
{
override fun doWork(): Result
{
return Result.success()
}
}
doWork()
不会在主线程运行,要求返回的对象,可以有Result.success()
、Result.failure()
,此外,还有Result.retry()
,在表示失败的同时,可以结合WorkResult.Builder
的 setBackoffCriteria() 重新执行任务,之后会说。
第二步,这里只进行简单配置:
val request = OneTimeWorkRequest.Builder(SimpleWork::class.java).build()
OneTimeWorkRequest用于一次请求,是WorkRequest的子类。也有子类PeriodicWorkRequest,用于创建周期性后台任务请求,下列示例代码是周期不小于15分钟的写法:
val request = PeriodicWorkRequest.Builder(
SimpleWork::class.java,
15,
TimeUnit.MINUTES
).build()
最后提交就行:
WorkManager.getInstance(context).enqueue(request)
上文并不能控制具体时间,其实用处也不大,这里进行更复杂的控制设置。
延时5分钟
val request = OneTimeWorkRequest.Builder(SimpleWork::class.java)
.apply {
setInitialDelay(5, TimeUnit.MINUTES)
build()
}
添加标签,最主要的功能是通过标签取消后台请求:
val request = OneTimeWorkRequest.Builder(SimpleWork::class.java)
.apply {
setInitialDelay(5, TimeUnit.MINUTES)
addTag("tag")
build()
}
没有标签,也可以用id来取消:
WorkManager.getInstance(this).apply {
cancelAllWorkByTag("tag")
cancelWorkById(request.id)
// 取消所有任务
cancelAllWork()
}
使用标签,可以把同一标签的所有任务取消(也就是标签可以重复命名,区别于id)
结合WorkResult.Builder
的 setBackoffCriteria() 重新执行任务:
val request = OneTimeWorkRequest.Builder(SimpleWork::class.java)
.apply {
setBackoffCriteria(
BackoffPolicy.LINEAR,
10,
TimeUnit.MINUTES
)
build()
}
setBackoffCriteria()
后两个参数很好理解,就是指定多久后执行任务。第一个参数是,多次失败后,下次执行失败后,以什么样的延迟方式执行。LINEAR
表示线性延迟,EXPONENTIAL
则是指数型。
这两个返回值是用于通知任务运行结果的,可以如下对后台监听:
WorkManager.getInstance(this).apply { getWorkInfoByIdLiveData(request.id).observe(this@MainActivity) { when(it.state) { WorkInfo.State.SUCCEEDED -> { TODO("成功时执行") } WorkInfo.State.FAILED -> { TODO("失败时执行") } else -> {} } } }
这样就能监听数据结果了,当然,你知道了有getWorkInfoByIdLiveData() 自然也有基于Target的方法。
https://juejin.cn/post/6966852605825777678
前面介绍的WorkManager功能,在国产手机上都可能得不到正确运行,因为大多数厂商会给予用户一键清除所有非白名单的功能,被杀死的程序既没法接收广播,也不能运行WorkManager任务。
所以建议是,不要依赖它实现核心功能,这不可靠。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。