80

记一次Android内存泄漏的优化经历

 5 years ago
source link: http://www.10tiao.com/html/169/201806/2650825627/1.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

本文作者


作者:黄俊彬

链接:

https://www.jianshu.com/p/c8e691a69086

本文由作者授权发布。


文章不难,但为真实的一次优化经历,值得借鉴。


1 背景


通过线上收集的日志分析,存在部分OOM的日志,故通过leakcanary进行内存泄漏追踪。

引用链日志


在开发的过程中,leakcanary报出了内存泄漏,详细的日志如下:



并且全局有其他Activity也存在相同引用链的内存泄漏,日志如下:



使用MAT进行分析,引用链也相同,日志如下:



影响:


通过AS自带的Profiler进行分析,发现此内存泄漏非常严重,进入多次Activity,页面Finish掉后,GC均无法进行回收,实例会一直存在。这样如果用户多次操作页面,那么很容易触发OOM的异常。



2 分析


FileMainActivity大致的结构如下,一个Activity里面,包含了一个Fragment,Fragment是一个列表。


通过引用链可以发现为ViewRootImpl$ViewRootHandler导致Activity没有回收。初步怀疑是否由于handler导致。但通过排查FileMainActivity代码,内部的handler是使用static内部类,同时也使用了软引用持有,并没有导致触发的原因。


通过搜索引擎,也无相关的博文介绍。问题一度陷入停滞。


但鉴于问题的严重性,决定采用排除法进行验证,当然过程耗时长,主要的思路如下:


1、Activity里面不集成Fragment,然后运行,重复进入退出,分析GC,发现不会存在内存泄漏;
(问题初步定为为Fragment里的代码存在内存泄漏)


2、恢复Fragment代码,onActivityCreate中的代码大致为,初始化控件、请求网络数据、显示列表。通过依次屏蔽3个方法,再进入验证
(问题发现在屏蔽了显示列表的方法后,不会存在内存泄漏。问题定位在显示列表的方法中)


3、通过依次屏蔽显示列表的方法中,定位问题出在一句代码,引起了内存泄漏

mPullToRefreshLayout.refreshComplete();


4、mPullToRefreshLayout是一个第三方的下拉刷新控件,PtrFrameLayout。refreshComplete的源码如下:


https://github.com/liaohuqiu/android-Ultra-Pull-To-Refresh

final public void refreshComplete() {
   if (DEBUG) {
       PtrCLog.i(LOG_TAG, "refreshComplete");
   }
   if (mRefreshCompleteHook != null) {
       mRefreshCompleteHook.reset();
   }
   int delay = (int) (mLoadingMinTime - (System.currentTimeMillis() - mLoadingStartTime));
   if (delay <= 0) {
       if (DEBUG) {
           PtrCLog.d(LOG_TAG, "performRefreshComplete at once");
       }
       performRefreshComplete();
   } else {
       postDelayed(new MyRunnable(this, 1), delay);
       if (DEBUG) {
           PtrCLog.d(LOG_TAG, "performRefreshComplete after delay: %s", delay);
       }
   }
}


5、里面存在关键代码postDelayed,故怀疑可能此处代码产生内存泄漏。通过断点打印发现如下:



最终定位问题如下:


由于PtrFrameLayout的refreshComplete方法中的postDelayed导致了内存泄漏,由于delay很长,导致Activity不会被回收。


3 处理


几经波折,终于定位到问题。那么现在就好处理了。通过分析我们发现这里主要的问题就是delay值很大导致。我们分析发现如下代码:

  int delay = (int) 
(mLoadingMinTime - (System.currentTimeMillis() - mLoadingStartTime));


发现了long强制转成了int,这里面导致了数据溢出,从而影响了delay最后的运算。这里通过如下处理则可修复内存泄漏的问题:



修复后,通过proflier分析,Activity已正常被GC回收,至此解决问题。


结论


1、分析内存泄漏需要多用工具,leakcanary、AS自带的Profile及Mat工具

2、需要耐心及细心,优化内存泄漏是一个挺麻烦,但也很重要的事情


Android性能优化-内存泄漏(上)

https://www.jianshu.com/p/402225fce4b2

Android性能优化-内存泄漏(下)

https://www.jianshu.com/p/2c9fc4e871a4


推荐阅读

必知必会 | Android 性能优化的方面方面都在这儿

全方位带你彻底搞懂Android内存泄露 | 案例分析

手把手教你在Android Studio 3.0上分析内存泄漏

带你了解Android常见的内存缓存算法

Android 强、软、弱、虚引用 区别和使用场景



扫一扫 关注我的公众号

如果你想要跟大家分享你的文章,欢迎投稿~


┏(^0^)┛明天见!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK