知识点汇总:
一:LeakCanary项目概述
二:内存泄漏对象的监听实现
三:内存泄漏数据的磁盘写入实现
四:内存泄漏文件的解析实现
五:内存泄漏对象的引用链展示
六:项目扩展知识点汇总
七:扩展阅读
一:LeakCanary项目概述
概述:本项目的源码分析基于1.6.3版本,后面的版本采用kotlin,并添加了自定义ContentProvider、ViewModel内存泄漏监听等实现,并使用了kotlin实现的shark库,有兴趣的同学可以查看最新的版本代码,在了解了LeakCanary项目的整体源码设计后,本人把项目的整体设计实现分为4个部分,用8个字总结就是:监听、写入、解析、展示,下面我就大概解释一下这8个字分别在项目中的含义。
第一步:内存泄漏的监听
项目中通过注册ActivityLifecycleCallbacks和FragmentLifecycleCallbacks的回调监听,在Activity和Fragment销毁时,通过RefWatcher.watch()函数实现对引用的对象的弱引用关联,在两次内存垃圾回收后,最后成功销毁的对象会进入ReferenceQueue队列中,没有进入队列的对象就会被判定为内存泄漏对象。
第二步:内存泄漏对象数据的磁盘写入
通过Set<String> retainedKeys判断是否有内存泄漏的对象出现,如果发现有内存泄漏的对象存在,会通过AndroidHeapDumper类的dumpHeap()函数,并最终通过Debug.dumpHprofData()函数把内存泄漏相关的内存快照数据写入磁盘文件中,文件名的后缀为.hprof。
第三步:内存泄漏磁盘文件的解析
在磁盘中生成了内存泄漏相关文件后,通过启动HeapAnalyzerService服务开始解析内存快照文件的信息,在解析的过程中,需要使用square的另外一个开源框架Haha辅助解析内存泄漏文件的数据,并最后通过ShortestPathFinder类找出内存泄漏对象的最短引用链,把解析的结果写入AnalysisResult对象中。
第四步:内存泄漏对象的展示
得到了解析内存泄漏的数据并存放在AnalysisResult对象后,再把HeapDump堆数据对象一起传递给DisplayLeakService服务,并最后通过Notification把相关数据展示在用户面前。
二:内存泄漏对象的监听实现
首先我们知道LeakCanary是主要是监听Acitivty和Fragment的内存泄漏,即需要在Activity和Fragment销毁时判断对象是否正常被回收了,这时候就需要监听Acitivty和Fragment的生命周期,这时候需要用到ActivityLifecycleCallbacks和FragmentLifecycleCallbacks实现注册监听,在初始化代码时,需要调用LeakCanary.install(this)函数,下面看看具体代码:
解析:通过函数注解了解到该创建的对象开始实现对Activity的引用监听,使用建造者模式创建了RefWatcher对象,配置的信息中可以看到配置了现实内存泄漏服务类和排除一些SDK和部分手机厂商的系统内存泄漏信息,关键的代码在最后的buildAndInstall()中,下面我们继续看看。
解析:通过注解我们可以看到调用完该函数后就开始了界面的监听了,而主要的监听行为就在Watcher对象的函数install中,下面我们看看ActivityRefWatcher对象的install函数实现。
解析:通过上面代码了解到,其实是使用了registerActivityLifecycleCallbacks注册了界面生命周期的监听,并在界面销毁时执行的监听对象的watch的函数调用,下面的对象泄漏的具体实现就要看watch函数具体做了什么了,下面忽略部分代码,
备注:这里由于ActivityLifecycleCallbacks的生命周期接口函数太多,为了方便调用这实现,这里使用的适配器模式实现了类ActivityLifecycleCallbacksAdapter。(接口的适配器)
解析:通过注解我们了解到该函数通过参数传递进来的对象,可以检测该对象是否会被垃圾回收,而具体的判断逻辑是通过KeyWeakReference辅助实现的,这里需要了解一个知识点,Java中的WeakReference是弱引用类型,每当发生GC时,它所持有的对象如果没有被其他强引用所持有,那么它所引用的对象就会被回收,同时或者稍后的时间这个WeakReference会被入队到ReferenceQueue中,也就是说如果对象被正常回收了,就会进入ReferenceQueue队列中,如果在经过垃圾回收后,还是没有进去队列,那就判定该对象发生了内存泄漏,下面我们就看看具体实现代码。
解析:通过上面的代码我们大体了解到通过二次removeWeaklyReachableReferences函数去除对正常垃圾回收的对象,中间手动的执行了Gc垃圾回收操作,如果在两次执行后,对象的key还是存在retainedKeys数据结构中,就判定对象发生了内存泄漏了。
三:内存泄漏数据的磁盘写入实现
通过监听对象,发现如果对象判定发生了内存泄漏后,这时就需要把内存的快照信息写入到磁盘中,下面我们先看看判定内存泄漏后的相关代码,该代码片段来着上面的ensureGone函数。
解析:当判定有对象发生内存泄漏了,会执行heapDumper.dumpHeap函数,并把相关的内存信息组合生成HeapDump对象传递给heapdumpListener接口进行解析,注意这里的内存快照数据的写入主要在heapDumper.dumpHeap函数中,下面我看看具体代码实现。
解析:通过上面代码了解到发生内存泄漏后,会弹出一个我们常常看到的吐司,并初始化顶部通知栏的一些信息,这里磁盘写入中,最核心的函数是Debug.dumpHprofData(heapDumpFile.getAbsolutePath())函数,内存快照数据的写入主要就是通过该函数写入的。
四:内存泄漏文件的解析实现
上面的步骤已经在磁盘中生成了内存泄漏的快照文件,下面就是要如何解析磁盘的内存快照文件了,这就需要查看HeapAnalyzerService服务类的onHandleIntentInForeground函数,下面是具体代码实现:
解析:通过Intent传递内存相关对象HeapDump,并通过heapAnalyzer.checkForLeak函数解析内存快照相关信息,并把解析的结果保存到AnalysisResult对象中,下面我们看看checkForLeak具体做了什么。
解析:通过注释了解到,该函数可以通过关联key找到相关的内存泄漏对象,并计算出强引用对象的最短引用链,这里需要注意解析需要用到square的另外一个开源库HaHa解析内存泄漏快照信息,并最后把解析的结果保存在对象AnalysisResult中返回,解析过程通过回调接口一一记录下来。
备注:对象最短引用链示意图
五:内存泄漏对象的引用链展示
通过对内存快照文件的解析,并把结果存储在对象AnalysisResult对象中,这时就需要了解项目是如何提取出最后的分析结果,并最终展示相关数据到通知栏中,具体实现在类DisplayLeakService的onHeapAnalyzed中,具体代码如下:
解析:通过参数获取到AnalysisResult和HeapDump对象,解析出对象中的部分有用数据,并添加在字符串中,通过Notification展示相关解析的内存泄漏字符串数据在顶部通知栏中。
下面总结一下项目中用到的重要的类和方法:
六:项目扩展知识点汇总
问题一:判断当前执行的进程是否为服务进程?
解析:项目中LeakCanaryInternals的isInServiceProcess函数判断当前进程是否为主进程,实现逻辑是遍历设备中当前运行的进程,获取到的进程进程名是否有与我们当前正常运行的进程有相同的进程id,如果有就保存下来,在获取服务组件的运行进程名,如果两个进程名相等,证明了当前运行的是服务进程,具体代码如下:
问题二:Intent与PendingIntent的区别
解析:
2.1、Intent是一个意图,一个描述了想要启动一个Activity、Broadcast或是Service的意图。它主要持有的信息是它想要启动的组件(Activity、Broadcast或是Service)。
2.2、PendingIntent可以看作是对Intent的包装。供当前App之外的其他App调用。有点“被动”或是“Callback”的意思,但不是严格意义上的“被动”或是“Callback”。总之,当前App不能用它马上启动它所包裹的Intent。而是在外部App执行这个PendingIntent时,间接地、实际地调用里面的Intent。PendingIntent主要持有的信息是它所包装的Intent和当前App的Context。正由于PendingIntent中保存有当前App的Context,使它赋予外部App一种能力,使得外部App可以如同当前App一样的执行PendingIntent里的Intent,就算在执行时当前App已经不存在了,也能通过存在PendingIntent里的Context照样执行Intent。
2.3、intent英文意思是意图,pending表示即将发生或来临的事情,PendingIntent这个类用于处理即将发生的事情。比如在通知Notification中用于跳转页面,但不是马上跳转,Intent是及时启动,intent随所在的activity消失而消失。
2.4、PendingIntent 可以看作是对intent的包装,通常通过getActivity,getBroadcast ,getService来得到pendingintent的实例,当前activity并不能马上启动它所包含的intent,而是在外部执行 pendingintent时,调用intent的。正由于pendingintent中 保存有当前App的Context,使它赋予外部App一种能力,使得外部App可以如同当前App一样的执行pendingintent里的 Intent, 就算在执行时当前App已经不存在了,也能通过存在pendingintent里的Context照样执行Intent。另外还可以处理intent执行 后的操作。常和alermanger 和notificationmanager一起使用。
2.5、Intent一般是用作Activity、Sercvice、BroadcastReceiver之间传递数据,而Pendingintent一般用在 Notification上,可以理解为延迟执行的intent,PendingIntent是对Intent一个包装。
这里是PendingIntent的官方解析:
问题三:Service、Intentservice、foregroundService的区别
解析:下面的IntentService的官方注解,我们先看看。
分析:上面的意思大概是说Intentservice是在Service的基础上处理异步任务的,并在需要的使用实现相关的异步任务,当任务执行完后就自动停止执行,其实现逻辑其实非常简单,就是通过Handler把相关的异步任务发送到相关的功能线程,再通过异步线程HandlerThread执行Service中的异步任务,下面是IntentService的核心实现代码,可以让我们更好的理解。
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
}
@Override
public void onCreate() {
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
而ForegroundService类是项目中实现的,主要是在启动前端服务时,初始化通知栏相关的信息,下面看看核心代码实现吧:
七:扩展阅读
1、https://blog.csdn.net/chennai1101/article/details/103799424(Android LeakCanary原理分析)
2、https://zhuanlan.zhihu.com/p/336448803(死磕Android_LeakCanary原理赏析)
3、https://zhuanlan.zhihu.com/p/73675401(看完这篇 LeakCanary 原理分析,又可以虐面试官了)
4、https://blog.csdn.net/ihrthk/article/details/7316404(Android Intent和PendingIntent的区别详细分析)
网友评论