2024年Android最新都2024年了Android开发者,别再忽视LeakCanary了(1),学android的入门基础知识

总结

其实要轻松掌握很简单,要点就两个:

  1. 找到一套好的视频资料,紧跟大牛梳理好的知识框架进行学习。
  2. 多练。 (视频优势是互动感强,容易集中注意力)

你不需要是天才,也不需要具备强悍的天赋,只要做到这两点,短期内成功的概率是非常高的。

对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。下面资料部分截图是我花费几个月时间整理的,诚意满满:特别适合有3-5年开发经验的Android程序员们学习。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

今天我们先学习内存优化中的一个小知识点,就是内存泄露的检测和解决。当然,如何解决是大多数中级工程师都要去学习的东西,网上也有大量的资料,所以我这里不会详解。而是主要着眼于内存泄露的检测。Square公司出品的大名鼎鼎的LeakCanary,就是业界知名的内存泄露检测的利器。

阅读本篇文章,预计需要20分钟,你将会学习到:

  • LeakCanary检测内存泄露的原理

  • 使用ContentProvider进行三方库初始化的方法

原理概述

==================================================================

关于LeakCanary的原理,官网上已经给出了详细的解释。翻译过来就是:1.LeakCanary使用ObjectWatcher来监控Android的生命周期。当Activity和Fragment被destroy以后,这些引用被传给ObjectWatcher以WeakReference的形式引用着。如果gc完5秒钟以后这些引用还没有被清除掉,那就是内存泄露了。2.当被泄露掉的对象达到一个阈值,LeakCanary就会把java的堆栈信息dump到.hprof文件中。3.LeakCanary用Shark库来解析.hprof文件,找到无法被清理的引用的引用栈,然后再根据对Android系统的知识来判定是哪个实例导致的泄露。4.通过泄露信息,LeakCanary会将一条完整的引用链缩减到一个小的引用链,其余的因为这个小的引用链导致的泄露链都会被聚合在一起。

通过官网的介绍,我们很容易就抓住了学习LeakCanary这个库的重点:

  • LeakCanary是如何使用ObjectWatcher 监控生命周期的?

  • LeakCanary如何dump和分析.hprof文件的?

看官方原理总是感觉不过瘾,下面我们从代码层面上来分析。本文基于LeakCanary 2.0 beta版。

基本使用

==================================================================

LeakCanary的使用相当的简单。只需要在module的build.gradle添加一行依赖,代码侵入少。

ependencies {

// debugImplementation because LeakCanary should only run in debug builds.

debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0-beta-3'

}

就这样,应用非常简单就接入了LeakCanary内存检测功能。当然还有一些更高级的用法,比如更改自定义config,增加监控项等,大家可以参考官网

源码分析

==================================================================

1. 初始化

=====================================================================

和之前的1.x版本相比,2.0甚至都不需要再在Application里面增加install的代码。可能很多的同学都会疑惑,LeakCanary是如何插入自己的初始化代码的呢? 其实这里LeakCanary是使用了ContentProvider来进行初始化。我之前在介绍Android插件化系列三:技术流派和四大组件支持的时候曾经介绍过ContentProvider的特点,即在打包的过程中来自不同module的ContentProvider最后都会merge到一个文件中,启动app的时候ContentProvider是自动安装,并且安装会比Application的onCreate还早。LeakCanary就是依据这个原理进行的设计。具体可以参考【译】你的Android库是否还在Application中初始化?

我们可以查看LeakCanary源码,发现它在leakcanary-object-watcher-android的AndroidManifest.xml中有一个ContentProvider。

 

然后我们查看AppWatcherInstaller的代码,发现内部是使用InternalAppWatcher进行的install。

 // AppWatcherInstaller

override fun onCreate(): Boolean {

val application = context!!.applicationContext as Application

InternalAppWatcher.install(application)

return true

}



// InternalAppWatcher

fun install(application: Application) {

// 省略部分代码

checkMainThread()

if (this::application.isInitialized) {

return

}

InternalAppWatcher.application = application



val configProvider = { AppWatcher.config }

ActivityDestroyWatcher.install(application, objectWatcher, configProvider)

FragmentDestroyWatcher.install(application, objectWatcher, configProvider)

onAppWatcherInstalled(application)

}

可以看到这里主要把Activity和Fragment区分了开来,然后分别进行注册。Activity的生命周期监听是借助于Application.ActivityLifecycleCallbacks。

fecycleCallbacks =

object : Application.ActivityLifecycleCallbacks by noOpDelegate() {

override fun onActivityDestroyed(activity: Activity) {

if (configProvider().watchActivities) {

objectWatcher.watch(activity)

}

}

}







application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)



而Fragment的生命周期监听是借助了Activity的ActivityLifecycleCallbacks生命周期回调,当Activity创建的时候去调用FragmentManager.registerFragmentLifecycleCallbacks方法注册Fragment的生命周期监听。





override fun onFragmentViewDestroyed(

fm: FragmentManager,

fragment: Fragment

) {

val view = fragment.view

if (view != null && configProvider().watchFragmentViews) {

objectWatcher.watch(view)

}

}



override fun onFragmentDestroyed(

fm: FragmentManager,

fragment: Fragment

) {

if (configProvider().watchFragments) {

objectWatcher.watch(fragment)

}

}

}

最终,Activity和Fragment都将自己的引用传入了ObjectWatcher.watch()进行监控。从这里开始进入到LeakCanary的引用监测逻辑。

题外话:LeakCanary 2.0版本和1.0版本相比,增加了Fragment的生命周期监听,每个类的职责也更加清晰。但是我个人觉得使用 (Activty)->Unit 这种lambda表达式作为类的写法不是很优雅,倒不如面向接口编程。完全可以设计成ActivityWatcher和FragmentWatcher都继承自某个接口,这样也方便后续扩展。

2. 引用监控

======================================================================

2.1 引用和GC

  1. 引用

首先我们先介绍一点准备知识。大家都知道,java中存在四种引用:

  • 强引用:垃圾回收器绝不会回收它,当内存空间不足,Java虚拟机宁愿抛出OOM

  • 软引用:只有在内存不足的时候JVM才会回收仅有软引用指向的对象所占的空间

  • 弱引用:当JVM进行垃圾回收时,无论内存是否充足,都会回收仅被弱引用关联的对象。

  • 虚引用:和没有任何引用一样,在任何时候都可能被垃圾回收。

一个对象在被gc的时候,如果发现还有软引用(或弱引用,或虚引用)指向它,就会在回收对象之前,把这个引用加入到与之关联的引用队列(ReferenceQueue)中去。如果一个软引用(或弱引用,或虚引用)对象本身在引用队列中,就说明该引用对象所指向的对象被回收了。

当软引用(或弱引用,或虚引用)对象所指向的对象被回收了,那么这个引用对象本身就没有价值了,如果程序中存在大量的这类对象(注意,我们创建的软引用、弱引用、虚引用对象本身是个强引用,不会自动被gc回收),就会浪费内存。因此我们这就可以手动回收位于引用队列中的引用对象本身。

比如我们经常看到这种用法

WeakReference weakReference = new WeakReference(list);

还有也有这样一种用法

WeakReference weakReference = new WeakReference(list, new ReferenceQueue

这样就可以把对象和ReferenceQueue关联起来,进行对象是否gc的判断了。另外我们从弱引用的特征中看到,弱引用是不会影响到这个对象是否被gc的,很适合用来监控对象的gc情况。

2.GCjava中有两种手动调用GC的方式。

System.gc();

// 或者

Runtime.getRuntime().gc();

2.2 监控

====================================================================

我们在第一节中提到,Activity和Fragment都依赖于响应的LifecycleCallback来回调销毁信息,然后调用了ObjectWatcher.watch添加了销毁后的监控。接下来我们看ObjectWatcher.watch做了什么操作

@Synchronized fun watch(

watchedObject: Any,

name: String

) {

removeWeaklyReachableObjects()

val key = UUID.randomUUID().toString()

val watchUptimeMillis = clock.uptimeMillis()

val reference =

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

watchedObjects[key] = reference

checkRetainedExecutor.execute {

moveToRetained(key)

}

}



private fun removeWeaklyReachableObjects() {

// WeakReferences are enqueued as soon as the object to which they point to becomes weakly

// reachable. This is before finalization or garbage collection has actually happened.

var ref: KeyedWeakReference?

do {

ref = queue.poll() as KeyedWeakReference?

if (ref != null) {

watchedObjects.remove(ref.key)

}

} while (ref != null)

}



@Synchronized private fun moveToRetained(key: String) {

removeWeaklyReachableObjects()

val retainedRef = watchedObjects[key]

if (retainedRef != null) {

retainedRef.retainedUptimeMillis = clock.uptimeMillis()

onObjectRetainedListeners.forEach { it.onObjectRetained() }

}

}

这里我们看到,有一个存储着KeyedWeakReference的ReferenceQueue对象。在每次增加watch object的时候,都会去把已经处于ReferenceQueue中的对象给从监控对象的map即watchObjects中清理掉,因为这些对象都已经被回收了。然后再去生成一个KeyedWeakReference,这个对象就是一个持有了key和监测开始时间的WeakReference对象。最后再去调用moveToRetained,相当于记录和回调给监控方这个对象正式开始监测的时间。

那么我们现在已经拿到了需要监控的对象了,但是又是怎么去判断这个对象已经内存泄露的呢?这就要继续往下面看。我们主要到前面在讲解InternalAppWatcher的install方法的时候,除了install了Activity和Fragment的检测器,还调用了onAppWatcherInstalled(application)方法,看代码发现这个方法就是InternalLeakCanary的invoke方法。

 override fun invoke(application: Application) {

this.application = application



AppWatcher.objectWatcher.addOnObjectRetainedListener(this)



val heapDumper = AndroidHeapDumper(application, leakDirectoryProvider)



val gcTrigger = GcTrigger.Default



val configProvider = { LeakCanary.config }



val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)

handlerThread.start()

val backgroundHandler = Handler(handlerThread.looper)



heapDumpTrigger = HeapDumpTrigger(

application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,

configProvider

)



## **最后**

针对于上面的问题,我总结出了互联网公司Android程序员面试涉及到的绝大部分面试题及答案,并整理做成了文档,以及系统的进阶学习视频资料。
(包括Java在Android开发中应用、APP框架知识体系、高级UI、全方位性能调优,NDK开发,音视频技术,人工智能技术,跨平台技术等技术资料),希望能帮助到你面试前的复习,且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。
![Android进阶视频+面试资料部分截图](https://img-blog.csdnimg.cn/img_convert/13ad17515950d545b089336cffae2035.webp?x-oss-process=image/format,png)




**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化学习资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618156601)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

gProvider

)



## **最后**

针对于上面的问题,我总结出了互联网公司Android程序员面试涉及到的绝大部分面试题及答案,并整理做成了文档,以及系统的进阶学习视频资料。
(包括Java在Android开发中应用、APP框架知识体系、高级UI、全方位性能调优,NDK开发,音视频技术,人工智能技术,跨平台技术等技术资料),希望能帮助到你面试前的复习,且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。
[外链图片转存中...(img-qFj7cRzx-1715682292326)]




**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化学习资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618156601)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值