MAT 使用入门 (1).

     公司新开发的项目, 前两天测试童鞋跑过来说, 开发的兄弟们, 你们开发的项目中有内存泄漏啊.

     what! 是谁在写往项目中写 bug! 作为一个有洁癖的程序员, 优化代码是必须的(其实是想把这个写 bug 的童鞋找出来.. = = ) 所以决定好好研究一下 MAT 这个超高门槛的内存分析工具.
     感谢之前在某视网一起工作的旺神, 让我接触到这个工具.

     首先需要安装配置, 直接科学上网, 下载就好, 但是 Mac 版本的 MAT 工具可能需要对配置文件做一定的修改, 否侧会提示启动错误. Eclipse Memory Analyzer在Mac启动报错

真的有泄漏

     跑了一下项目, 随便点了点, 还真的有泄漏. 在设置页面, ViewPager + TabLayout + Fragment 的方式中, 来回不断的切换页面, 内存上升, 而且手动 GC 之后, 内存不会降低. 就像是这个样子乱点一通.



     纳尼!? 居然是我负责的设置页面, 还好一起开发的小伙伴们都还不知道, 赶紧搞定.
     于是乎, 导出内存 .hprof 文件, 这里可以直接使用 Android Studio, 不需要命令行.



MAT 操练起来

     赶紧的 MAT, 导入走起来. 说真的, 这个 MAT 用起来还真的费劲, 不过没办法, 为了追赶大神的脚步, 硬着头皮上吧.
     先来看两张 Histogram 和 Dominator_tree 这两张表(其实目前我也就会看这两张.. = = ).



     通过 Histogram 可以看到, HotKeyAdapter 占用了大量的存储空间. 而 RecyclerView 居然有莫名的 27 个之多.



     通过 Dominator_tree, HotKeyAdapter 占用的约 56% 的内存. 铁定是它或者是它相关的类 leak memory 了. 挨个找吧.
     先来看一下有什么引用了, HotKeyAdapter 造成其无法释放.
     右键 HotkeyAdapter 这一项. 选择 Merge short key path to GD root –> exclude all plantform/weak/soft reference. etc 只看强引用.



     乍一看, 只有一个 DecorView 的引用, 没有什么问题.
     那就应该是是 HotKeyAdapter 持有了其他资源, 没法释放. 导致内存泄漏. 右键 HotKeyAdapter 这一项. 选择 List Object –> with outgoing Objects.



     这里有一个 mObserver 持有了大量的对象, 无法释放, 不过让人头疼的是这个是 adapter 的源码, 无奈, 只能硬着头皮自己看(突然发现自己的头皮好硬).

偷窥源码.

     先去源码中找一下这个 Observer.

1
2
3
4
5
6
7
8
9
10
11
/**
* Base class for an Adapter
*
* <p>Adapters provide a binding from an app-specific data set to views that are displayed
* within a {@link RecyclerView}.</p>
*
*/
public abstract static class Adapter<VH extends ViewHolder> {
private final AdapterDataObservable mObservable = new AdapterDataObservable();
// ..省略无关源码

     然后接着点进去, 找到对应的数组.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Provides methods for registering or unregistering arbitrary observers in an {@link ArrayList}.
*
* This abstract class is intended to be subclassed and specialized to maintain
* a registry of observers of specific types and dispatch notifications to them.
*/
public abstract class Observable<T> {
/**
* The list of observers. An observer can be in the list at most
* once and will never be null.
*/
protected final ArrayList<T> mObservers = new ArrayList<T>();
// 省略无关代码...
}

     就是这个 mObserver 持有了大量的对象. 但是我们需要知道他持有的什么对象.
     不知道是晚生运气不错, 还是 google 的大神们设计的牛逼, 居然被我发现了 registerObserver(T observer) 这个方法, 会向其中添加数据.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* Adds an observer to the list. The observer cannot be null and it must not already
* be registered.
* @param observer the observer to register
* @throws IllegalArgumentException the observer is null
* @throws IllegalStateException the observer is already registered
*/
public void registerObserver(T observer) {
if (observer == null) {
throw new IllegalArgumentException("The observer is null.");
}
synchronized(mObservers) {
if (mObservers.contains(observer)) {
throw new IllegalStateException("Observer " + observer + " is already registered.");
}
mObservers.add(observer);
}
}

     然后一路追踪上去. 看是那里调用了.
     RecyclerView$Adapter#registerAdapterDataObserver() –> RecyclerView#setAdapterInternal() –> RecyclerView#setAdapter()
     我去, 原来是调用了疯狂的调用了, setAdapter() 方法.
     然后再去瞅一眼项目中的代码.

总结与修复

1
2
3
4
5
6
7
// adapter 采用 Dagger2 框架初始化.
@Inject
HotKeyAdapter hotKeyAdapter;
// RecyclerView 通过 BufferKnife 初始化.
@BindView(R.id.recycler_hot_list)
RecyclerView recycler_hotKeyList;

     在默认的情况下, Dagger2 框架初始化的变量是 Activity 单例的, 可能整个项目的生命周期只有一个. 而 RecyclerView 每次切换到 Fragment 界面就会初始化一次.
     每次调用 RecyclerView.setAdapter() 方法的时, 会向 adapter 中添加一个 Observer, 每次添加会判断是否包含, 但是 Observer 的实现类 RecyclerView$AdapterDataObserver 并没有实现 equals() 方法, 也就是说, 只要是新的对象, 就会添加到集合中.
     所以, 每次滑动到该界面时, 就会添加一个Observer. Adapter 持有该引用, 也就间接持有了 RecyclerView 的引用, 导致 RecyclerView 无法释放, 所以就会内存中就会出现大量的 RecyclerView. Leak memory 了.
     解决方案很是简单, HotKeyAdapter 直接初始化就好, 不使用 dagger2 框架.

~感谢捧场,您的支持将鼓励我继续创作~