Android笔记(七)Jetpack组件库

ViewMode

 是专门存放与界面相关数据的,只要是界面看得到的数据,都应该存放在ViewMode中。
 它的一个重要特性是,只有当界面退出时才会被销毁,界面的旋转不会被重新创建。

一般创建方法

  1. 弃用 ViewModelProviders.of():已弃用 ViewModelProviders.of()。您可以将 FragmentFragmentActivity 传递给新的 ViewModelProvider(ViewModelStoreOwner)构造函数,以便在使用 Fragment 1.2.0 时实现相同的功能。

  2. 弃用 lifecycle-extensions 工件:在上面弃用ViewModelProviders.of()后,此版本标志着弃用 lifecycle-extensions 中的最后一个 API,因此现在该工件已完全被弃用。我们强烈建议依赖于您需要的特定 Lifecycle 工件(例如,如果您使用的是 LifecycleService,则依赖于 lifecycle-service;如果您使用的是 ProcessLifecycleOwner,则依赖于 lifecycle-process)而不是 lifecycle-extensions,因为将来不会有 lifecycle-extensions 的 2.3.0 版本。

  3. Activity/Frangment只能获取viewMode实例,而不能创建,因为后者周期长于前两者,这样会导致重复创建的可能。

  4. 更多创建用法见:创建ViewMode

先定义一个字段用于计数:

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()
	}

}

向ViewMode传递值

 上文的默认值是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

简介

 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

 是响应式组件,它能够在数据发生变化时通知给观察者。LiveData大多情况是与ViewMode配合使用,尽管它能单独使用。

基本用法

 如果说,ViewMode的作用是解耦,让Activity界面数据分离的话,那么LiveData就是去给响应数据变化提醒(因为在ViewMode中,并没有主动响应变化的值,而是修改后调用refreshCount()方法)。
 主动提醒值变化,有利于耗时操作和统一管理数据修改,不至于界面数据修改写的一塌糊涂。
比如说,如果ViewMode内部开启了线程去执行耗时逻辑,那么在点击按钮后去执行refreshCount()获取值,显然没有新值,而处理完后的结果值,又恰恰无法主动显示。这就是LiveData重心。
不要把Activity传给ViewMode这样会导致回收困难,内存泄露。一个周期较短的类不能把实例传给较长的类保管,也不能静态引用周期短的类

LiveData基本使用

class MainActivityViewMode(countReserved : Int) : ViewModel()
{
	var counter = MutableLiveData<Int>()

	init {
		// 这样以前保存的默认值就能恢复
		counter.value = countReserved
	}

	fun add() {
		counter.value = counter.value?.plus(1)
	}
}

MutableLiveData 是一种可变的LiveData,他有三种读写数据的方式:

  1. getValue()获取数据
  2. setValue()设置数据,只能在主线程调用
  3. 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
	}
}

map() 与 switchMap()

map()

&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()

  虽然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
	}
}

工作流程总结

  1. 外部调用MainActivityViewModegetUser()获取用户数据,不会发送任何对外请求,只是userIdData 数据发生变化而提醒
  2. userIdData 数据发生变化,switchMap就会执行,才会对外请求对象并存放到user变量中。
  3. 对于Activity/Fragment而言,只要观察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并不关心数据是否相同只要有调用setValuepostValue就一定触发数据变化

后记

  1. LiveData能作为Activity与ViewMode之间的通信桥梁,并且不存在内存泄漏的风险,靠的就是Lifecycles组件进行自我感知,在Activity销毁时及时释放,避免产生内存泄露放入问题
  2. Activity在不可见的时候(息屏、被其他Activity遮挡),LiveData如果数据发生变化,也不会通知给观察者,只有恢复时,才进行通知
  3. Activity在不可见的时候,如果数据发生了多次变化,当Activity恢复时,LiveData只会保留最新的一份数据并提供给观察者,其他旧的数据会被丢弃,这些功能也少不了Lifecycles组件

Room

这是一个基于数据库的ORM框架。这部分自行百度。

WorkManager

 WorkManager适用于处理一些定时任务,他可以根据操作系统版本自动选择底层是AlarmManager实现还是JobScheduler,从而减低使用成本。它还支持周期性任务链式任务处理等。

 与Service没有直接联系,并不相同。Serivce在没有被销毁时是一直保持后台运行的,而WorkManager只是一个定时处理工具,它可以保证即使应用程序退出,甚至手机重启,之前的任务仍然会得到执行,因此适合执行一些定期和服务器交互内容。

 另外,它所注册的周期性任务不一定准时执行。系统为了减少电池消耗,可能会触发时间临近的几个任务一起执行,这样以减少CPU被唤醒次数。

基本用法

添加依赖:

implementation 'androidx.work:work-runtime:2.7.0-alpha04'

主要工作分三步:

  1. 定义一个后台任务,实现具体业务逻辑
  2. 配置后台任务运行条件和约束信息,并构建后台任务请求
  3. 将后台任务传递给WorkManagerenqueue()方法,系统就能得以执行。

第一步:

class SimpleWork(context: Context, params: WorkerParameters) : Worker(context, params)
{
	override fun doWork(): Result
	{
		return Result.success()
	}
}

doWork()不会在主线程运行,要求返回的对象,可以有Result.success()Result.failure(),此外,还有Result.retry(),在表示失败的同时,可以结合WorkResult.BuildersetBackoffCriteria() 重新执行任务,之后会说。

第二步,这里只进行简单配置

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)

使用WorkManager处理复杂任务

上文并不能控制具体时间,其实用处也不大,这里进行更复杂的控制设置。

延时执行

延时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

Result.retry()

  结合WorkResult.BuildersetBackoffCriteria() 重新执行任务:

val request = OneTimeWorkRequest.Builder(SimpleWork::class.java)
		.apply {
			setBackoffCriteria(
					BackoffPolicy.LINEAR,
					10,
					TimeUnit.MINUTES
			)
			build()
		}

setBackoffCriteria()后两个参数很好理解,就是指定多久后执行任务。第一个参数是,多次失败后,下次执行失败后,以什么样的延迟方式执行LINEAR表示线性延迟,EXPONENTIAL则是指数型。

Result的success和failure

 这两个返回值是用于通知任务运行结果的,可以如下对后台监听:

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任务。
 所以建议是,不要依赖它实现核心功能,这不可靠。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值