中秋月圆之夜,我与协程的泄漏做斗争
作者:访客发布时间:2023-12-15分类:程序开发学习浏览:102
前言
协程系列文章:
- 一个小故事讲明白进程、线路、Kotlin进程到底是什么关系?
- 少年,你知道Kotlin程序最初的样子吗?
- 讲真,Kotlin编程的兴起/恢复没那么神奇(故事篇)
- 讲真,Kotlin编程的兴起/恢复没那么神奇(原理篇)
- Kotlin编程调整切换线路是什么时候打开的
- Kotlin编程之线程池探索之旅(与Java线程池PK)
- Kotlin编程之取消与异常处理之探索(上)
- Kotlin编程之取消与异常处理之探索(下)
- 来吧,孤独
- 继续来,和我一起孤独Kotlin Channel 深水区
- Kotlin教程选择:看我如何多路复用
- Kotlin Sequence 是什么时候派上用场了
- Kotlin Flow啊,你会流向何方?
- Kotlin Flow反压和线性程序切换如此相似
- Kotlin SharedFlow StateFlow 热流到底有多热?
- 肉毒杆菌吧,肉毒杆菌与软骨素,Flow的化学反应
- Come on!接受Kotlin编程--编程池的7个灵活提问
- 当,Kotlin Flow与Channel对应
- 这一次,让Kotlin Flow 操作符真正好用起来
There's a lot of people who are going to go.
in one embodiment of that present invention,a method for manufacture a cartridge comprises providing a cartridge comprising:小鱼人
通过本篇文章,你将了解到:
- 如何检测Kotlin进程的内存泄漏?
- Kotlin编程为什么会内存泄漏?
- 如何避免Kotlin编程的内存泄漏?
- 协程挂起和线程挂起的终极混用
- 关注内存泄漏到底有没有现实意义?
1.如何检测Kotlin进程的内存泄漏?
内存泄漏检测方式
Profiler分析器
Android官方给我们提供了profiler功能,可以实时观测线路、内存的情况:
Select Memory Analysis,First dump Files:
dump成功后,解析文件:
如此一来就可以看到有泄漏了。
dumpsys meminfo提取
Profiler的功能很强,但步骤比较多也比较费时,如果只是想查看内存泄漏,可以使用更简单的方法。
第一步
I'm sorry. I'm sorry.
ps -A | grep perform //perform为我自己包名的简称
24148 即为进程号。
第二步
拿到进程号后再使用如下命令:
dumpsys meminfo 24148
结果如下:
We only need to consider the value of Activities.
In this paper,the author discusses the relationship between the factors of the influence of the in
In this paper,the author introduces the principle and application of the mathematical model
一个会泄漏的协程
class SecondActivity : AppCompatActivity() {
private lateinit var binding: ActivitySecondBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivitySecondBinding.inflate(layoutInflater)
setContentView(binding.root)
GlobalScope.launch(Dispatchers.Main) {
delay(10000)
Toast.makeText(this@SecondActivity, "toast", Toast.LENGTH_LONG).show()
}
}
}
在Activity的onCreate()里打开一个单独的程序,并延时10秒弹出toast。
When click from MainActivity into SecondActivity,then quickly return SecondActivity返回MainActivity。
I'm sorry. I'm sorry.
然而很明显,此时只有MainActivity显示了,但冷却处显示还有2个Activity对象,SecondActivity 发生了泄漏。
2. Kotlin编程为什么会内存泄漏?
持有外部类对象
前面有分析过内存泄漏的本质:武名内部类/Lambda Java和Kotlin谁会导致内存泄漏?
in that example中,as the second activity object holds in the进程的封装里,而进程的封装本质上是武名内部类对象.
Dispatchers.Main表示该包会在主程序执行,而在Android主程序执行时必须通过Looper,因此该包最终会被MessageQueue持有,最终它会被主程序持有,而程序属于一种GC Root,最终的持有关系:
主线程持有了SecondActivity对象,当SecondActivity退出时,由于仍被主线程持有,因此无法释放,最终导致内存泄漏
不持有外部类对象
In this paper,we study the relationship between the factors of the influence of the
class SecondActivity : AppCompatActivity() {
private lateinit var binding: ActivitySecondBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivitySecondBinding.inflate(layoutInflater)
setContentView(binding.root)
GlobalScope.launch(Dispatchers.Main) {
delay(10000)
println("我会泄漏吗?不会呀")
}
}
}
3. 如何避免Kotlin编程的内存泄漏?
青铜级避免
最简单的方式是协程闭包里不持有外部类的对象。
我们更多的时候需要在封装里操作UI,因此需要关注程序的漏洞问题。
从最直观的方式思考:
能否在页面退出的时候关闭协程?
override fun onDestroy() {
super.onDestroy()
GlobalScope.cancel("我要取消协程")
}
奇怪的是发生了崩溃:
I mean,it's not the Job of the process work domain(GlobalScope).
想想也是这样,毕竟是GlobalScope.cancel()能够取消程序的执行,那么其他它也用了GlobalScope打开的程序就不被我们取消掉了吗?
然而如此,请尝试取消指定的作业。
job = GlobalScope.launch(Dispatchers.Main) {
delay(10000)
Toast.makeText(this@SecondActivity, "toast", Toast.LENGTH_LONG).show()
}
override fun onDestroy() {
super.onDestroy()
job.cancel("")
}
这次程序没有Crash,退出Activity后也没有退出Toast,说明程序被取消掉了。
王者级避免
In onDestroy()里进行资源的回收是比较古老的操作了,自己从有了密码组件,生命周期的监听变得简单易行,而且还扩展了密码程序的作用域,因此我们可以只关注使用密码程序来实现业务逻辑,而不必关注它的生命周期.
The results showed that the results showed that the results of
class SecondActivity : AppCompatActivity() {
private lateinit var binding: ActivitySecondBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivitySecondBinding.inflate(layoutInflater)
setContentView(binding.root)
lifecycleScope.launch {
delay(4000)
Toast.makeText(this@SecondActivity, "toast", Toast.LENGTH_LONG).show()
}
}
}
答案是:没有内存泄漏。
你可能会问了:此处咱们也没有显式地取消协程,为啥没泄漏呢?
真相只有一个:那就是从源码里寻找答案。
public val Lifecycle.coroutineScope: LifecycleCoroutineScope
get() {
while (true) {
val existing = mInternalScopeRef.get() as LifecycleCoroutineScopeImpl?
if (existing != null) {
return existing
}
//构造协程作用域
val newScope = LifecycleCoroutineScopeImpl(
this,
SupervisorJob() + Dispatchers.Main.immediate
)
if (mInternalScopeRef.compareAndSet(null, newScope)) {
//注册监听Activity生命周期
newScope.register()
return newScope
}
}
}
fun register() {
launch(Dispatchers.Main.immediate) {
if (lifecycle.currentState >= Lifecycle.State.INITIALIZED) {
//监听生命周期
lifecycle.addObserver(this@LifecycleCoroutineScopeImpl)
} else {
//如果Activity已经关闭,则取消协程
coroutineContext.cancel()
}
}
}
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (lifecycle.currentState <= Lifecycle.State.DESTROYED) {
lifecycle.removeObserver(this)
//监听到Activity关闭,则取消协程
coroutineContext.cancel()
}
}
由上可知:
- lifescleScope监听了Activity生命周期,在Activity运行时会取消进程,因此不会发生遗漏
- 同样的,在实际的应用中不推荐使用GlobalScope,而是使用与Activity/Fragment/ViewMode相关联的作用域开启程序,所以一直以来我们只专注于程序实现业务逻辑
In this paper,we study the relationship between the mechanism and the mechanism of the mechanism and the mechanism of the mechanism of the mechanism.
协程取消的原理
什么场景下能够取消协程
scope.cancel()/job.cancel()为什么就能够取消作业呢?
You may say:cancel本来就是设计为能够取消程序正在执行的动作,没什么那么多为什么.
阁下说的很有道理,倘若我代码写成以下这样子,阁下将如何应对呢?
lifecycleScope.launch(Dispatchers.IO) {
while (true) {
println("协程还在运行中...")
}
}
现实是:即使Activity退出了,程序也无法继续,打印一直持续到天荒地老。
再换个写法:
lifecycleScope.launch(Dispatchers.IO) {
while (true) {
delay(1000)
println("协程还在运行中...")
}
}
When Activity退出时,程序被取消,没有打印。
对比前后两者差异可知:
- 协程的取消能够打断挂起的函数,对不是挂起的函数不生效
- 协程取消后,挂起函数后面的代码将无法得到执行
取消的原理
lifecycleScope.launch(Dispatchers.IO) {
while (true) {
println("协程还在运行中...")
}
}
将上面的代码转换为Java查看:
public final Object invokeSuspend(@NotNull Object var1) {
Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
switch (this.label) {
case 0:
//检测是否有异常
ResultKt.throwOnFailure(var1);
while(true) {
String var2 = "协程还在运行中...";
System.out.println(var2);
}
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
}
The most common problem is that it's not the most common problem.
lifecycleScope.launch(Dispatchers.IO) {
while (true) {
delay(1000)
println ("协程还在运行中...")
}
}
public final Object invokeSuspend(@NotNull Object $result) {
Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
String var2;
switch (this.label) {
case 0:
//检测异常,若是则抛出
ResultKt.throwOnFailure($result);
break;
case 1:
//检测异常,若是则抛出
ResultKt.throwOnFailure($result);
var2 = "协程还在运行中...";
System.out.println(var2);
break;
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
while(true) {
this.label = 1;
if (DelayKt.delay(1000L, this) == var3) {
return var3;
}
var2 = "协程还在运行中...";
System.out.println(var2);
}
}
可以看出,两者的相同点是:
协程闭包执行每一个分支前都判断是否有异常,若是则抛出
异同点是:
一个有挂起函数,另一个没有
invokeSuspend()对应的就是关闭进程的包逻辑。
虽然可能会跳出异常,那我们就试着在程序的封闭包里再试一次......接住。
lifecycleScope.launch(Dispatchers.IO) {
try {
delay(30000)
println("协程还在运行中...")
} catch (e: Exception) {
println("发生了异常:${e.localizedMessage}")
}
}
退出Activity时,打印如下:
确实发生了异常,由此我们得出结论:
When the processor 200 determines whether or not the received signal is received,the processor 200 determines whether or not the received signal is received.
此时依旧还有两个问题没有解决:
- 为什么非挂起函数不能取消?
- 协程是如何监听到取消指令的?
用一张图解释:
对于有挂起函数的协程,将会完全执行上图流程。
In this paper,we study the relationship between the factors of the mechanism and the relationship between the mechanism and the mechanism of the mechanism.
ResultKt.throwOnFailure(var1);
最终也不会抛出异常。
最后一个问题:为啥抛出了异常,协程就没泄漏了?
答案是:
线程体的本质是一个Runnable,提交给了线程执行,当Runnable里面的逻辑出现了异常,那么这个Runnable就执行结束了,也就不会被线程持有,当然也没有被GC Root持有,那么在GC的时候就有机会被回收
4.协程挂起和线程挂起的终极混用
协程取消能否中断线程?
lifecycleScope.launch(Dispatchers.IO) {
Thread.sleep(10000)
println("协程还在运行中...")//2
}
The results showed that the results of the study showed that the results of
问:2处的语句还会执行吗?
答案是:能
可以看出,此时的协程状态机里只有一个状态,并没有挂起函数,依据前面的分析可知协程并不会被成功取消。
这里涉及到线程的挂起和协程挂起的差异:
- In this paper,we discuss the relationship between the transmitting and receiving of the transmitting signals and the transmitting signals.
- In this paper,we study the relationship between the mechanism and the relationship between mechanism.
Therefore,是在程序中想延迟一段时间请使用程序相关的触发函数如Delay等.
如何编写没还有泄漏的协程代码?
建议以下几个步骤:
- 进程里是没有持有外部类对象(Activity/Fragment/Dialog等),那么此时进程并不会遗漏UI对象
- 如果是步骤1不满足,那么需要使用生命周期关联的程序作用域(LifescleScope/viewModelScope等),当UI组件运行时自动取消程序
- 线程体里尽量不使用线程相关的API,如Thread.sleep 等
5.关注内存泄漏到底有没有现实意义?
小明说:“现在应用的内存都比较大,最常见的是UI对象的泄漏,没过呢泄漏几个Activity最多花了几K的内存,无伤大雅,不需要花太多的时间在上面”
“事虽小,但有可能是压死朱澳的最后一棵草,以善小而不为,以恶小而为之”
In this paper,we study the relationship between the factors of the mechanical properties and the mechanical properties of the mechanical properties of the mechanical properties.
小刚说:“对于程序员来说,代码洁癖是一种美德,关注内存泄漏对己对人都有裨益”
小码说:“你都快把这个行业做大包了,先关心自己不能把这个大环境的怨气憋住吧”
小刚:“......”
小码:“......”
阁下意下如何?请把你的想法写在评论上吧。
本文基于kotlin 1.7.0
您若喜欢,请点赞、关注、收藏,您的鼓励是我前进的动力
持续更新中,跟我一起走营地系统,深入学习Android/Kotlin
1、Android各种Context的前生
2个、安卓Decorview必知必会
3、窗口/窗口管理器不可不知之事
4、查看测量/布局/绘制真明白了
5、安卓事件分发全套服务
6、安卓无效/POST无效/请求布局彻底厘清
7、安卓窗口如何确定大小/onmeasure()多次执行原因
8、安卓事件驱动处理器-消息-环路解析
9、安卓键盘一招搞定
10、安卓各种坐标彻底明了
11、安卓活动/窗口/查看的背景
12、安卓活动创建到查看的显示过
13、安卓IPC系列
14、安卓存储系列
15、JAVA并发系列不再疑惑
16、JAVA线程池系列
17、安卓Jetpack前置基础系列
18、安卓Jetpack易学易懂系列
19号、Kotlin轻松入门系列
20、Kotlin协程系列全面解读
- 上一篇:[Gradle-11]动态修改版本名称和版本代码
- 下一篇:设计模式--适配器模式
- 程序开发学习排行
-
- 1鸿蒙HarmonyOS:Web组件网页白屏检测
- 2HTTPS协议是安全传输,为啥还要再加密?
- 3HarmonyOS鸿蒙应用开发——数据持久化Preferences
- 4记解决MaterialButton背景颜色与设置值不同
- 5鸿蒙HarmonyOS实战-ArkUI组件(RelativeContainer)
- 6鸿蒙HarmonyOS实战-ArkUI组件(Stack)
- 7[Android][NDK][Cmake]一文搞懂Android项目中的Cmake
- 8鸿蒙HarmonyOS实战-ArkUI组件(mediaquery)
- 9鸿蒙HarmonyOS实战-ArkUI组件(GridRow/GridCol)
- 最近发表
-
- WooCommerce最好的WordPress常用插件下载博客插件模块的相关产品
- 羊驼机器人最好的WordPress常用插件下载博客插件模块
- IP信息记录器最好的WordPress常用插件下载博客插件模块
- Linkly for WooCommerce最好的WordPress常用插件下载博客插件模块
- 元素聚合器Forms最好的WordPress常用插件下载博客插件模块
- Promaker Chat 最好的WordPress通用插件下载 博客插件模块
- 自动更新发布日期最好的WordPress常用插件下载博客插件模块
- WordPress官方最好的获取回复WordPress常用插件下载博客插件模块
- Img to rss最好的wordpress常用插件下载博客插件模块
- WPMozo为Elementor最好的WordPress常用插件下载博客插件模块添加精简版