记录一次内存泄漏的解决过程

最近在跑阿里云兼容性测试的时候,发现在部分机器上,内存普遍偏高,查看日志确定有内存泄漏的情况,开始用LeakCanary并且定位到了有问题的GC Root:

image.png
是framework的泄漏,按照常理判断应为是app层非法调用导致,解决的思路是:

  1. 找出持有者.
  2. 找出持有者的理论释放点.
  3. 找出理论释放点未执行的路径.

根据这个思路进行排查:

  1. 很显然持有者是ActivityThread里mNewActivities属性,是个ActivityClientRecord类型的对象,数据结构是链表.
  2. 这一步才是比例费力的地方,需要翻阅framework层的源代码,消耗了一些时间之后,发现了理论释放点,而且这个执行点应该会在ActivityThread对象在执行handleResumeActivity函数的时候发起调用的.

image.png
3. 再联想到ActivityClientRecord的数据结构,可以推断出正常的执行路径,假设有2个activity,a1和a2,a1在a2的下边,这个时候mNewActivities的链式结构应该是a1->a2,现在a2执行退出操作,ActivityManagerServer会调度a1和a2的生命周期,会先调用handleResumeActivity执行a1的OnResume,同时会把mNewActivities末端的a2移除(就是Idler里执行的逻辑).那么这是正常的逻辑,很显然这个流程没有走通,那么是在哪里出了问题呢,
在回到Idler执行的地方,

image.png
一种猜测是主线程的Looper执行的任务比较多,一直没有时间执行addIdleHandler里的任务,那么我就看看Looper现在忙不忙,在a1的onCreate里添加代码

image.png
果然海量的任务

image.png
接下来就是查找是哪里频繁的发生任务,由于没有好的办法定位是哪里通过Choreographer里的FrameHandler发生任务的,我只能通过注释掉代码来定位,最终是CollapsingToolbarLayout构造函数里的这个部分代码导致的,

image.png
这个代码是注册了一个windowInset事件,windowInset事件在其它文章里会介绍,这里我们只关注这个事件的注册方式,注册是通过ViewCompat注册的,在看一下ViewCompat是怎么注册的,

image.png

image.png
显然ViewCompat在注册的时候,做了一层代理,在这个代理里有个逻辑就是,如果4772行的判断没有命中则会调用4789函数,而4789的代码会一路调用到ViewRootImp的requestFitSystemWindows函数

image.png

而requestFitSystemWindows调用scheduleTraversals函数,

image.png

而scheduleTraversals函数里会向Choreographer发布任务,执行任务就是Choreographer里的FrameHandler,FrameHandler里会让ViewRootImp再次执行performTraversals,performTraversals里会调用dispatchApplyInsets

image.png
执行dispatchApplyInsets这个函数会重新派发windowInset事件,派发之后会再次执行到ViewCompat里的注册函数,如果4772行的if还没有命中,那么就会再次循环一下,如果一直没有命中,就会一直循环下去,looper也就会一直处理消息,经过调试发现就是这个一直在循环调用.

最后总结一下,CollapsingToolbarLayout在构造函数里,注册了WindowInsets事件,在注册的代理里一直会经过一些列的调用向Choreographer里的FrameHandler发生异步消息,导致主线的looper繁忙,无法处理ActivityThread里的mNewActivities指空末端节点的逻辑,导致内存泄漏.

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享