Android内存泄漏分析及检测工具LeakCanary简介

  1. View

  2. Service

fun appDefaultWatchers(

application: Application,

reachabilityWatcher: ReachabilityWatcher = objectWatcher

): List {

return listOf(

ActivityWatcher(application, reachabilityWatcher),

FragmentAndViewModelWatcher(application, reachabilityWatcher),

RootViewWatcher(reachabilityWatcher),

ServiceWatcher(reachabilityWatcher)

)

}

首先我们来看一下ObjectWatcher ,它的关键代码如下:

@Synchronized fun watch(

watchedObject: Any,

description: String

) {

if (!isEnabled()) {

return

}

removeWeaklyReachableObjects()

val key = UUID.randomUUID()

.toString()

val watchUptimeMillis = clock.uptimeMillis()

val reference =

KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)

SharkLog.d {

"Watching " +

(if (watchedObject is Class<*>) watchedObject.toString() else “instance of ${watchedObject.javaClass.name}”) +

(if (description.isNotEmpty()) " ($description)" else “”) +

" with key $key"

}

watchedObjects[key] = reference

checkRetainedExecutor.execute {

moveToRetained(key)

}

}

主要是对watchedObject使用了弱引用,同时注意到里面使用了ReferenceQueue,这两者结合使用可以实现如果弱引用关联的对象被回收,就会把这个弱引用加入到queue中,以此来判断该对象是否被回收。

LeakCanary主要的检测对象是以上4种,以Activity为例进行分析,其他检测类型也是类似原理,不再赘述。

class ActivityWatcher(

private val application: Application,

private val reachabilityWatcher: ReachabilityWatcher

) : InstallableWatcher {

private val lifecycleCallbacks =

object : Application.ActivityLifecycleCallbacks by noOpDelegate() {

override fun onActivityDestroyed(activity: Activity) {

reachabilityWatcher.expectWeaklyReachable(

activity, “${activity::class.java.name} received Activity#onDestroy() callback”

)

}

}

}

ActivityWatcher中注册了ActivityLifecycleCallbacks,同时在onActivityDestroyed的时候,执行了一些操作,查看源码:

@Synchronized override fun expectWeaklyReachable(

watchedObject: Any,

description: String

) {

if (!isEnabled()) {

return

}

removeWeaklyReachableObjects()

val key = UUID.randomUUID()

.toString()

val watchUptimeMillis = clock.uptimeMillis()

val reference =

KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)

SharkLog.d {

"Watching " +

(if (watchedObject is Class<*>) watchedObject.toString() else “instance of ${watchedObject.javaClass.name}”) +

(if (description.isNotEmpty()) " ($description)" else “”) +

" with key $key"

}

watchedObjects[key] = reference

checkRetainedExecutor.execute {

moveToRetained(key)

}

}

上述代码的主要逻辑是:

  1. 移除弱可达的对象

  2. 将当前的watchedObject添加到KeyedWeakReference当中

  3. 将这个weakReference保存到数组中

  4. checkRetainedExecutor中执行moveToRetained方法

根据removeWeaklyReachableObjects方法中原理,如果这个对象除了由ObjectWatcher所添加的WeakReference以外,没有其他对象在引用它了,那么这个对象也就可以回收了,watchedObjects也就可以移除他了。

private fun removeWeaklyReachableObjects() {

var ref: KeyedWeakReference?

do {

ref = queue.poll() as KeyedWeakReference?

if (ref != null) {

watchedObjects.remove(ref.key)

}

} while (ref != null)

}

}

checkRetainedExecutor其实是个单例对象,里面会通过handler来延迟5s来执行方法。如果超过5s则会触发LeakCanary的泄漏检测机制。5s只是个经验值应该,因为GC并不是实时发生,因而预留5s交给GC操作。

触发了LeakCanary的泄漏检测之后,则会执行HeapDumpTriggerdumpHeap方法,在获取到了.hprof文件之后,调用HeapAnalyzerService.runAnalysis()给出分析结果。 关于.hprof文件的分析,不是本文重点,具体可以参考hprof文件协议。其分析基本也就是根据GC Root去寻找泄漏的对象,大体流程图如下。

在Android中常见的内存泄漏

单例

单例所导致的内存泄漏几乎是在android开发中最为常见的内存泄漏问题了。

public class Singleton {

private static Singleton singleton;

private Context context;

private Singleton(Context context) {

this.context = context;

}

public static Singleton getInstance(Context context) {

if (singleton == null) {

singleton = new Singleton(context);

}

return singleton;

}

}

在上面的代码中,如果在执行getInstance方法的时候,传入的是activity的对象,那么该activity对象就没法被及时回收,导致内存泄漏,可以考虑传入ApplicationContext,或者把context放入到方法变量中。

非静态内部类(包括匿名内部类)

非静态内部类会默认持有外部类的引用,如果它的生命周期长于外部类时,就会导致内存泄漏。 在android开发,这种情况常常见于Handler的使用。

尽可能避免使用静态变量

class MainActivity : AppCompatActivity() {

companion object {

@JvmStatic

private var info: StaticInfo? = null

}

override fun onCreate(savedInstanceState: Bundle?) {

info = StaticInfo(this)

}

class StaticInfo(activity: MainActivity) {

}

在上述代码中,info是一个静态变量,但是它持有了activity的引用,由于静态变量的生命周期要比activity的生命周期长,导致activity无法及时回收,造成内存泄漏。

资源未关闭造成的内存泄漏

诸如cursor、inputStream等对象一定要注意及时关闭

try {

}catch (e:Exception) {

}finally {

// 可以在finally方法里把cursor等对象进行关闭

}

集合中的对象未及时清理造成的内存泄漏

val list = ArrayList()

例如,如果一个list中存放的是activity对象,就会可能导致activity无法及时回收。如果该list是静态对象的话,不及时移除activity的话,就更会产生内存泄漏了。

webview造成的内存泄漏

因为webview在加载完网页后,它的callback会持有activity的引用,造成webview的内存无法释放。可以在activity的onDestroy()方法中移除该webview,并调用webview.destroy()。

未取消注册或回调造成的内存泄漏

在android中回调是使用非常多的,但如果在注册回调的时候,传入了context对象,则需要注 意及时取消回调,否则就可能会出现内存泄漏。例如eventbus和广播。

内存优化

  • 使用intentService代替Service,或者service执行完记得及时停止

  • 在系统资源紧张的时候,尽可能多释放一些非重要的资源(如图片的内存缓存)

class MyApp : Application() {

override fun onTrimMemory(level: Int) {

super.onTrimMemory(level)

// 可以在这里做些内存释放的工作

}

}

最后:学习总结——Android框架体系架构知识脑图(纯手绘xmind文档)

学完之后,若是想验收效果如何,其实最好的方法就是可自己去总结一下。比如我就会在学习完一个东西之后自己去手绘一份xmind文件的知识梳理大纲脑图,这样也可方便后续的复习,且都是自己的理解,相信随便瞟几眼就能迅速过完整个知识,脑补回来。

下方即为我手绘的Android框架体系架构知识脑图,由于是xmind文件,不好上传,所以小编将其以图片形式导出来传在此处,细节方面不是特别清晰。但可给感兴趣的朋友提供完整的Android框架体系架构知识脑图原件(包括上方的面试解析xmind文档)

除此之外,前文所提及的Alibaba珍藏版 Android框架体系架构 手写文档以及一本 《大话数据结构》 书籍等等相关的学习笔记文档,也皆可分享给认可的朋友!

——感谢大家伙的认可支持,请注意:点赞+点赞+点赞!!!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
自己去手绘一份xmind文件的知识梳理大纲脑图,这样也可方便后续的复习,且都是自己的理解,相信随便瞟几眼就能迅速过完整个知识,脑补回来。

下方即为我手绘的Android框架体系架构知识脑图,由于是xmind文件,不好上传,所以小编将其以图片形式导出来传在此处,细节方面不是特别清晰。但可给感兴趣的朋友提供完整的Android框架体系架构知识脑图原件(包括上方的面试解析xmind文档)
[外链图片转存中…(img-r7orwpyn-1714648782804)]

除此之外,前文所提及的Alibaba珍藏版 Android框架体系架构 手写文档以及一本 《大话数据结构》 书籍等等相关的学习笔记文档,也皆可分享给认可的朋友!

——感谢大家伙的认可支持,请注意:点赞+点赞+点赞!!!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

  • 24
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值