内存走漏简介
关于内存走漏的界说,想必咱们已经烂熟于心了,简单一点的说,便是程序占用了不再运用的内存空间。
那在 Android 里面,怎样的内存空间是程序不再运用的内存空间呢?
这就不得不提到内存走漏相关的检测了,拿 LeakCanary 的检测举例,销毁的 Activity
、Fragment
、fragment View
、ViewModel
假如没有被收回,当时应用被判定为产生了内存走漏。LeakCanary 会生成引证链相关的日志信息。
有了引证链的日志信息,咱们就能够开开心心的处理内存走漏问题了。可是除了检查引证链还有更好的处理办法吗?答案是有的,那便是经过画图来处理,会愈加的直观形象~
一个简单的比方
如下是一个 Handler 产生内存走漏的比方:
class MainActivity : ComponentActivity() {
private val handler = LeakHandler(Looper.getMainLooper())
override fun onCreate(savedInstanceState: Bundle?) {
// other code
// 发送了一个 100s 的推迟音讯
handler.sendMessageDelayed(Message.obtain(), 100_000L)
}
private fun doLog() {
Log.d(TAG, "doLog")
}
private inner class LeakHandler(looper: Looper): Handler(looper) {
override fun handleMessage(msg: Message) {
doLog()
}
}
}
因为 LeakHandler
是一个内部类,持有了外部类 MainActivity
的引证。
其在如下场景会产生内存走漏:onCreate
履行之后发送了一个 100s 的推迟音讯,在 100s 以内旋转屏幕,MainActivity
进行了重建,上一次的 MainActivity
还被 LeakHandler
持有无法开释,导致内存走漏的产生。
引证链图示
如下是履行完 onCreate()
办法之后的引证链图示:
简单阐明一下引证链 0 的位置,这儿为了简化,直接运用 GCRoot 代替了,实际上存在这样的引证联系:GCRoot → ActivityThread → Handler → MessageQueue → Message。简单了解一下即可,不太清楚的话也不会影响接下来的问题处理。 一起,为了简单明晰,文中还简化了一些相关可是不重要的引证链联系,比方
Handler
对MessageQueue
的引证。
100s 以内旋转屏幕之后,引证链图示变成这样了:
之前的 Activity
被开释,引证链 4 被堵截了。咱们能够很清晰的看到,因为 LeakHandler
对 MainActivity
的强引证(引证链2),LeakHandler
直接被 GCRoot 节点强引证,导致 MainActivity
没办法开释。
MainActivity
指的是旋转屏幕之前的Activity
,不是旋转屏幕之后新建的
那么很显然,接下来咱们就需要对引证链 0、1 或 2 进行一些操作了,这样才能够让 MainActivity
得到开释。
处理方案
方案一:
在 onDestroy()
的时分调用 removeCallbacksAndMessages(null)
,该办法会进行两步操作:移除该 Handler
发送的所有音讯,并将 Message
收回到 MessagePool
:
override fun onDestroy() {
super.onDestroy()
handler.removeCallbacksAndMessages(null)
}
此时,在图示上的表现,便是移除了引证链 0 和 1。如此 MainActivity
就能够正常收回了。
方案二:
运用弱引证 + 静态内部类的办法,咱们同样也能够处理这个内存走漏问题,想必咱们已经十分熟悉了。
这儿再简单阐明一下弱引证 + 静态内部类的原理: 弱引证的收回机制:在垃圾收回器线程扫描它所统辖的内存区域的过程中,一旦发现了只被弱引证引证的目标,不管当时内存空间足够与否,都会收回它的内存。 静态内部类:静态内部类不会持有外部类的引证,也就不会有 LeakHandler 直接引证 MainActivity 的状况出现
代码完成上,只需要传入 MainActivity
的弱引证给 NoLeakHandler
即可:
private val handler = NoLeakHandler(WeakReference(this), Looper.getMainLooper())
private class NoLeakHandler(
private val activity: WeakReference<MainActivity>,
looper: Looper
): Handler(looper) {
override fun handleMessage(msg: Message) {
activity.get()?.doLog()
}
}
下图中,2.1 表明的是 NoLeakHandler
对 WeakReference
的强引证,NoLeakHandler
经过 WeakReference
直接引证到了 MainActivity
。咱们能够很清楚的看到,在旋转屏幕之后,MainActivity
此时只被一个弱引证引证了(引证链 2.2,运用虚线表明),是能够正常被收回的。
另一个简单的比方
再来一个静态类持有 Activity 的比方,如下是要害代码:
object LeakStaticObject {
val activityCollection: MutableList<Activity> = mutableListOf()
}
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// other code
activityCollection.add(this)
}
}
正常运行的状况下,存在如下的引证联系:
在旋转屏幕之后,会产生内存走漏,因为之前的 MainActivity
还被 GCRoot
节点(LeakStaticObject
)引证着。那要怎么处理呢?信任咱们已经比较清楚了,要么堵截引证链 0,要么将引证链 0 替换成一个弱引证。因为比较简单,这儿就不再独自画图阐明晰。
总结
本文介绍了一种运用画图来处理常见内存走漏的办法,会比直接检查引证链愈加清晰具体。一起,其相比于一些概括常见内存走漏的办法,会愈加的通用,很大程度上摆脱了对内存走漏场景的强行回忆。
经过画图,找到引证途径之后,在引证链的某个节点上进行操作,堵截强引证或许将强引证替换成弱引证,以此来处理问题。
总的来说,对于常见的内存走漏场景,咱们都能够经过画图来处理,本文为了介绍简便,运用了比较简单常见的比方,实际上,遇到杂乱的内存走漏,也能够经过画图的办法来处理。当然,熟练之后,省略画图的操作,也是能够的。
REFERENCE
Excalidraw — Collaborative whiteboarding made easy