点击上方蓝字关注我,知识会给你力量

Lifecycle

国际惯例,官网镇楼

https://developer.android.com/topic/libraries/architecture/lifecycle

关于Lifecycle的基本使用,这里就不详细介绍了,毕竟官网讲的很清楚了,而且大部分时间,我们也用太感知细节,这也是JetPack的魅力所在。

Lifecycle作为JetPack的核心组件之一,在JetPack的多个组件中都扮演着非常重要的角色。

大部分时候,我们在使用JetPack的组件时,都不需要特别考虑Lifecycle,这得益于大部分JetPack组件的Lifecycle Aware特性,类似lifecycleScope、ViewModelScope都可以在生命周期结束时,自动对资源进行释放,可以说,Lifecycle是JetPack组件的脊梁,而且大部分时间,可以开箱即用,不用做太多配置就可以直接掌控生命周期。

Activity可以作为LifecycleOwner,在AAC架构中扮演着重要的作用,那么Activity是怎么关联Lifecycle的呢?

AppCompatActivity继承自FragmentActivity,FragmentActivity继承自ComponentActivity,在ComponentActivity中,通过LifecycleRegistry来关联生命周期,但是ComponentActivity并没有直接处理生命周期,而是通过ReportFragment来进行代理。

在ComponentActivity的onCreate中,对ReportFragment进行了初始化,代码如下所示。

ReportFragment.injectIfNeededIn(this);

在ReportFragment中,对不同版本做了兼容处理:

image-20210915161754312

在ReportFragment中,通过activity.getLifecycle()来获取Activity中申明的LifecycleRegistry,并对生命周期进行管理。

在App初始化的时候,Android利用ContentProvider来初始化ProcessLifecycleOwnerInitializer,在这里,又会对LifecycleDispatcher和ProcessLifecycleOwner中的ActivityInitializationListener进行初始化,从而实现生命周期的感知。

细心的读者可能发现了,这里使用的是已经被废弃的android.app.Fragment,其原因就是为了兼容旧版本的Fragment所做的妥协。

lifecycleScope

lifecycleScope是Lifecycle的拓展函数,是Lifecycle对协程的支持,所以要使用lifecycleScope必须要先引入Lifecycle。

lifecycleScope也是CoroutineScope,所以也支持launch函数来构建,但是lifecycleScope提供了更加精确的,带生命周期的创建函数,如下所示。

lifecycleScope.launchWhenCreated{}
lifecycleScope.launchWhenStarted{}
lifecycleScope.launchWhenResumed{}

分别对应Activity的生命周期。

lifecycleScope可以直接使用,也可以针对特定的生命周期控制,一般写法有如下两种。

  • 直接使用

lifecycleScope.launch {XXXX
}
  • 带特定生命周期控制,有两种方式

// 方式1
lifecycleScope.launchWhenStarted {XXX
}
// 方式2
lifecycleScope.launch {whenStarted {XXX}
}

lifecycleScope能自动取消协程,避免泄漏的原理其实非常简单,就是在Lifecycle的生命周期回调中,在onDestroy中对协程做Cancel操作。

image-20210728173437294

lifecycleScope的这种处理方式具有很强的指导意义,我们在平时的代码实现中,都可以借用这种方式来对生命周期进行自动管理。

普通组件感知生命周期

普通的组件是无法感知生命周期的,但是借助Lifecycle,我们就可以很轻松的为任意组件增加对生命周期的感知,其原理实际上就是对普通组件增加LifecycleEventObserver的实现,这样在LifecycleOwner的生命周期发生改变时,就能在onStateChanged中获取对应的生命周期变化了,代码如下所示。

@SuppressLint("ViewConstructor")
class LifecycleAwareView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, lifecycleOwner: LifecycleOwner
) : View(context, attrs, defStyleAttr), LifecycleEventObserver {init {// Do something initLog.d("xys", "Init-------")lifecycleOwner.lifecycle.addObserver(this)}private fun release() {// Do something releaseLog.d("xys", "Release-------")}override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {when (event) {Lifecycle.Event.ON_DESTROY -> {release()source.lifecycle.removeObserver(this)}}}
}

如上所示,这是一个非常简单的自定义View,只不过实现了LifecycleEventObserver接口,并在init的时候增加监听,并对onStateChanged做了处理,增加了View在生命周期结束时的处理。

调用如下。

binding.root.addView(LifecycleAwareView(this, lifecycleOwner = this))

传入对应的LifecycleOwner即可。

Lifecycle in RecyclerView

比较常见的LifecycleOwner是Activity和Fragment,通常我们也是以这两个作为程序运行的承载界面,将生命周期与它们绑定是合理的,但在RecyclerView的场景下,这个界面生命周期的粒度就有些太粗了,如果我们在某个ViewHolder中发起网络请求,当这个ViewHolder被回收,那么这个请求在未处理的情况下,就会导致内存泄漏,所以通常的做法是ViewHolder的事件通过回调的方式托管到Activity,这样的方式,在业务开发场景下,是合理的,但是却不利于业务组件的封装。

首先,将业务逻辑回调到Activity,业务组件就只能以Activity作为使用范围,无法更加精细的控制组件粒度。

其次,回调托管写起来也比较麻烦。

所以,如果能自动管理ViewHolder的生命周期,那么就可以以ViewHolder,甚至是其中的View来作为业务组件的粒度划分,这样可以将业务逻辑统一处理而不用担心内存泄漏,而且业务方在使用时,可以直接黑盒使用某个业务组件,不必关心其中的逻辑。

当然这种处理也是要分场景考虑的,其中一个重点就是这个组件是偏业务还是偏功能,也就是是否要将业务逻辑统一包进组件,想清楚这个问题后,才能去开发一个业务组件。

那么我们如何来控制一个View的生命周期呢?

View其实提供了一个OnAttachStateChangeListener,可以回调View的onViewAttachedToWindow和onViewDetachedFromWindow,这就可以作为View的生命周期监控,再利用协程的CompletionHandler,来获得协程执行完成的回调,就可以对View的生命周期做处理了,代码如下所示。

private class ViewStateListener(private val view: View,private val job: Job
) : View.OnAttachStateChangeListener, CompletionHandler {override fun onViewDetachedFromWindow(v: View) {view.removeOnAttachStateChangeListener(this)Log.d("xys", "release")job.cancel()}override fun onViewAttachedToWindow(v: View) {}override fun invoke(cause: Throwable?) {view.removeOnAttachStateChangeListener(this)job.cancel()}
}

接下来,我们通过协程拦截器,在协程执行前,注入对View的监听,代码如下所示。

private class ViewAutoDisposeInterceptor(private val view: View
) : ContinuationInterceptor {override val key: CoroutineContext.Key<*>get() = ContinuationInterceptoroverride fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {val job = continuation.context[Job]if (job != null) {view.addOnAttachStateChangeListener(ViewStateListener(view, job))}return continuation}
}

最后,我们创建一个拓展函数,给View返回一个协程作用域,这个协程作用域可以在Viewdetached的时候,自动cancel协程的执行,从而避免内存泄漏,代码如下所示。

private const val TAG = R.id.autodispose_view_tagval View.autoDisposeScope: CoroutineScopeget() {val exist = getTag(TAG) as? CoroutineScopeif (exist != null) {return exist}val newScope = CoroutineScope(SupervisorJob() +Dispatchers.Main +ViewAutoDisposeInterceptor(this))setTag(TAG, newScope)return newScope}

这里给View设置了Tag,从而可以更好的利用缓存。

有了View、ViewHolder的生命周期管理,就可以很好的将耗时业务逻辑的处理封装到View、ViewHolder中,示例代码如下所示。

internal class SampleAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =object : RecyclerView.ViewHolder(TextView(parent.context)) {}override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {(holder.itemView as TextView).text = position.toString()val job = holder.itemView.autoDisposeScope.launch {delay(1000)Log.d("xys", "success $position")}job.invokeOnCompletion {Log.d("xys", "complete $position")}}override fun getItemCount(): Int = 300
}

这样就可以对任意View、ViewHolder创建自适应生命周期管理的协程作用域,从而实现autoDispose的功能。

向大家推荐下我的网站 https://xuyisheng.top/  点击原文一键直达

专注 Android-Kotlin-Flutter 欢迎大家访问

往期推荐

  • flutter与compose的爱恨情仇

  • 从精准化测试看ASM在Android中的强势插入-读懂diff

  • 闲言碎语——第四期

  • ConstraintLayout2.0一篇写不完之MotionLabel

本文原创公众号:群英传,授权转载请联系微信(Tomcat_xu),授权后,请在原创发表24小时后转载。

< END >

作者:徐宜生

更文不易,点个“三连”支持一下

再谈协程之Lifecycle潜行者相关推荐

  1. 再谈协程之suspend到底挂起了啥

    点击上方蓝字关注我,知识会给你力量 Kotlin编译器会给每一个suspend函数生成一个状态机来管理协程的执行. Coroutines简化了Android上的异步操作.正如文档中所解释的,我们可以用 ...

  2. 再谈HTTP2性能提升之背后原理—HTTP2历史解剖

    即使千辛万苦,还是把网站升级到http2了,遇坑如<phpcms v9站http升级到https加http2遇到到坑>. 因为理论相比于 HTTP 1.x ,在同时兼容 HTTP/1.1 ...

  3. python协程详解_对Python协程之异步同步的区别详解

    一下代码通过协程.多线程.多进程的方式,运行代码展示异步与同步的区别. import gevent import threading import multiprocessing # 这里展示同步和异 ...

  4. 再谈编程范式-程序语言背后的思想

    link link 编程范式 托马斯.库尔提出"科学的革命"的范式论后,Robert Floyd在1979年图灵奖的颁奖演说中使用了编程范式一词.编程范式一般包括三个方面,以OOP ...

  5. php协程和goroutine,浅谈协程和Go语言的Goroutine

    0x00.前言 前面写了一篇 今天来学习Go语言的Goroutine机制,这也可能是Go语言最为吸引人的特性了,理解它对于掌握Go语言大有裨益,话不多说开始吧! 通过本文你将了解到以下内容:什么是协程 ...

  6. 再谈编程范式—程序语言背后的思想

    编程范式 托马斯.库尔提出"科学的革命"的范式论后,Robert Floyd在1979年图灵奖的颁奖演说中使用了编程范式一词.编程范式一般包括三个方面,以OOP为例: 1,学科的逻 ...

  7. Kotlin协程之Dispatchers原理

    文章目录 前置知识 demo startCoroutineCancellable 小结 Kotlin协程不是什么空中阁楼,Kotlin源代码会被编译成class字节码文件,最终会运行到虚拟机中.所以从 ...

  8. python协程和线程_线程和协程之间的区别

    线程和协程之间的区别很大,甚至大过进程和线程之间的区别.线程建立在进程之上,协程建立在线程之上.那么协程是什么呢? 协程是一段计算机程序,它一般是一个协作类型的子程序,执行时允许暂停和恢复.协程非常适 ...

  9. 再谈JSON -json定义及数据类型

    再谈json 近期在项目中使用到了highcharts ,highstock做了一些统计分析.使用jQuery ajax那就不得不使用json, 可是在使用过程中也出现了非常多的疑惑,比方说,什么情况 ...

最新文章

  1. 第十七届智能车竞赛何时开始呀?
  2. 解决pycharm问题:module ‘pip‘ has no attribute ‘main‘
  3. Python基础教程:用模块化来搭项目
  4. 性能优化--布局优化技巧
  5. qotd服务_QOTD:Java线程与Java堆空间
  6. 腾讯视频怎样开启深色模式保护眼睛
  7. 埃夫特机器人示教器keba_埃夫特下一代智能工业机器人研发及产业化项目奠基...
  8. .net发送带附件邮件
  9. 使用wps插件,实现word转PDF
  10. Java根据位置获取经纬度计算距离
  11. pygame小游戏——英语单词挑战
  12. 杭州云栖大会“弹性计算用户实践专场”等你来
  13. Mac系统 - 升级node版本
  14. 请你根据微信登录界面设计测试用例
  15. Java银行储户后台系统
  16. virt-viewer的简单使用
  17. SEO新手怎么做好网站关键词优化?
  18. SL-PCA(子空间学习模型)——前景提取
  19. 怎么让人爆照_8招,让你用高逼格照片引爆朋友圈!
  20. 物联网操作系统Zephyr(入门篇)之1.1 Zephyr源码架构

热门文章

  1. tello通信_快乐六一留守儿童通信大使招募|用书信成就更好的自己
  2. 请写写一个 程序员年终总结
  3. 离线版Gerber查看器+PCB/PCBA检测神器新功能!
  4. 互联网乱弹之酷六的一生
  5. 画论89 戴以恒《醉苏斋画诀》
  6. 日本の行政区画--都道府県
  7. EPM问题汇总之--ADF_FACES-60097报错
  8. c# legend 显示位置_添加地图图例 Arcengine+C#
  9. Flutter Android 13系统bug android.media.EncoderProfiles$VideoProfile.getWidth()
  10. 电缆故障测试仪使用及注意事项——TFN FB11电缆故障测试仪