说起性能优化,大多数脑海里便会出现启动优化、UI绘制优化、内存优化等等。之前我也一直在做这些优化,一直没有勇气看内存泄漏的问题,觉得太难,但是,再难也要看啊,跑不了,那么就来吧(做完发现其实也不难)。

内存泄漏的分析很简单,Android Studio中就自带了工具profile:

Android游戏掉帧的原因 安卓玩游戏掉帧_Android游戏掉帧的原因

直接点击这个按钮就会运行应用并进入相应界面:

Android游戏掉帧的原因 安卓玩游戏掉帧_Android游戏掉帧的原因_02

点击图片中的MEMORY就进入内存的页面:

Android游戏掉帧的原因 安卓玩游戏掉帧_Mac_03

然后点击左上角的下载按钮(Record旁边),进入详细页面,

Android游戏掉帧的原因 安卓玩游戏掉帧_环境变量_04

可以选为查看包内容,查看下当前运行的活动,然后右击选中内容,选择export,自定导出位置。

Android游戏掉帧的原因 安卓玩游戏掉帧_android_05

导出后需要使用SDK中的hprof-conv工具,这里windows的用户可以直接进入安卓的sdk文件夹下运行cmd即可,Mac用户可以配置一个全局变量,由于我的系统版本是10.15,这里我就写zsh的配置方法了。

1.打开终端
2.打开 ~/.zshrc文件(如果无,则自动新建)
命令:

// 打开 ~/.zshrc文件(如果无,则自动新建)
open ~/.zshrc

3.设置环境变量

export PATH=${PATH}:/Users/zhujiang/Library/Android/sdk/platform-tools

4.保存并退出编辑
5.刷新环境变量生效

source ~/.zshrc

到这里就都可以使用hprof-conv工具了,需要输入以下命令将你导出的hprof文件转换成mat-hprof文件,命令如下:

hprof-conv -z 导出的文件.hprof 转换文件-mat.hprof

完成之后会出现的转换的文件,接下来需要下载能分析MAT的工具,一般使用Memory Analyzer,下面是下载地址:

https://www.eclipse.org/mat/downloads.php

下载完毕后双击文件如果报错的话,这是eclipse的一个bug。

解决办法: 右键mat显示包内容,进入Contents->MacOS下面,会有一个MemoryAnalyzer的命令。

打开终端,进入此路径找到MemoryAnalyzer,运行以下命令:

./MemoryAnalyzer -data dump文件所在文件夹路径

这样即可启动成功:

Android游戏掉帧的原因 安卓玩游戏掉帧_android_06

打开之后选择如下选项打开刚才转换完成的文件:

Android游戏掉帧的原因 安卓玩游戏掉帧_Android游戏掉帧的原因_07

打开之后选择我下面的选项,然后点击finish,如果出错别管它,继续进行:

Android游戏掉帧的原因 安卓玩游戏掉帧_内存泄漏_08

进入之后选择Overview,然后点击Histogram。

Android游戏掉帧的原因 安卓玩游戏掉帧_android_09

之后就可以进入类的页面,可以在里面进行筛选:

Android游戏掉帧的原因 安卓玩游戏掉帧_环境变量_10

下面是筛选的方法:

Android游戏掉帧的原因 安卓玩游戏掉帧_环境变量_11

找见需要优化的类,按照下面的方法打开:

Android游戏掉帧的原因 安卓玩游戏掉帧_环境变量_12

打开之后是如下的页面:

Android游戏掉帧的原因 安卓玩游戏掉帧_android_13

发现有一个mContext持有活动的引用,导致活动无法销毁。找到原因之后进行解决,咱们需要短了它的引用链,使GC可以清理调它。代码不多,在活动的onDestory()中进行销毁就行。

public static void fixInputMethodManagerLeak(Context destContext) {
        if (destContext == null) {
            return;
        }

        InputMethodManager imm = (InputMethodManager) destContext.getSystemService(Context.INPUT_METHOD_SERVICE);
        if (imm == null) {
            return;
        }

        String [] arr = new String[]{"mCurRootView", "mServedView", "mNextServedView"};
        Field f;
        Object obj_get;
        for (String param : arr) {
            try {
                f = imm.getClass().getDeclaredField(param);
                if (!f.isAccessible()) {
                    f.setAccessible(true);
                }
                obj_get = f.get(imm);
                if (obj_get instanceof View) {
                    View v_get = (View) obj_get;
                    if (v_get.getContext() == destContext) { // 被InputMethodManager持有引用的context是想要目标销毁的
                        f.set(imm, null); // 置空,破坏掉path to gc节点
                    } else {
                        // 不是想要目标销毁的,即为又进了另一层界面了,不要处理,避免影响原逻辑,也就不用继续for循环了
                        break;
                    }
                }
            } catch (Throwable t) {
                t.printStackTrace();
            }
        }
    }

好了,这就是一个简单的内存泄漏问题。如果你的页面中有其他页面没有销毁,那就证明你的页面存在内存泄漏,就需要来这样进行查看。使用Mat工具方法有很多,大家可以多搜索一下。先到这里吧。