关于华为inputMethodManager内存泄露

一个关于内存泄露的问题,相信使用华为手机检测内存泄露的时候都会发现inputMethodManager内存泄露这个问题。自己完成开发使用leakcanary检测的时候发现了inputMethodManager泄漏,华为手机特有的内存泄露,在6.0和7.0系统的华为手机上表现略有不同,准确的说在6.0的华为手机上泄露更为严重一些,


关于华为inputMethodManager内存泄露_第1张图片
device-2018-08-01-170051.png

关于华为inputMethodManager内存泄露_第2张图片
device-2018-08-01-152159.png

上面两张截图在6,0的系统上都会出现,在7.0的系统上自己测试过来发现只会出现第二张图中的情况。

关于华为手机上的内存泄露,自己把它归为两类,一类是hwPhoneWindow,另一类就是mLastSrvView

hwPhoneWindow内存泄露

这类泄露问题好像没什么好的办法解决掉,毕竟泄露的类在hwPhoneWindow中,华为自己挖的坑只能让他们自己来填上。而且从7.0的系统上没有发现这个泄露点也可以看出华为已经默默把这个问题给处理掉了

mLastSrvView内存泄露

这种内存泄露是我们能够处理的一类问题,mLastSrvView是InputMethodManager中的一个成员变量,通过反射我们可以得到该成员变量,将其设置为null就能避免掉内存泄露的风险,解决方案就是这么简单,至少我是这么解决的,目前测试来看没发现什么异常现象。代码不难等下给出,这里主要说下自己折腾一天多的经历

发现问题

事情是这样的,公司这边有几个app产品需要集成个推推送,顺便适配掉8.0的通知栏,基于个推的sdk进行二次开发封装成一个通用的模块,这个任务嘛光荣的落到了我的肩上,一顿开发之后完成,大家使用的东西质量还是要保证一下的嘛,万一出了什么问题不就尴尬了。利用leakcanary一检测报出了第二张截图中的问题,看完泄露根源在inputmethodmanager之后内心的懵逼的
关于华为inputMethodManager内存泄露_第3张图片
u=2059044631,2330943741&fm=27&gp=0.jpg

,模块当中根本没有使用到inputmethodmanager为什么最后的泄露点会跑到这里去

求解过程

方案一:网上搜一发相关inputmethodmanager内存泄露的问题,结果还真不少,看来踩坑的人不少,看了一圈之后发现和自己的问题有些出入,我这边是由mLastSrvView泄露最终引起的inputmethodmanager泄露问题,单纯搜inputmethodmanager泄露是找不到我想要的答案的。inputmethodmanager泄露的问题根据网上说法android sdk23版本之前存在的bug,好在leakcanary给出了泄露的解决方案,https://www.cnblogs.com/Jason-Jan/p/7899674.html 文章中的解决方法和官方是一致的。但是这种方法并不能解决掉华为手机的泄露问题,第一:我的测试机是24版本,已经超过了最高的bug版本23。第二:官方的泄露解决方案并没有涉及到mLastSrvView字段泄露问题。

方案二:看来这种方法行不通,改搜mLastSrvView泄露问题,还真有方案https://github.com/square/leakcanary/issues/572,可以看到这种问题是华为特有的问题,

QQ截图20180801180733.png

也不知道华为对inputmethodmanager做了什么手脚,然而结果是让人失望的官方并没有真正的解决方案,人家作者都说了
QQ截图20180801180939.png
,然后作者就把这个issue给关闭掉了。。。。。。。。

方案三:这篇文章是网上关于inputmethodmanager内存泄露被引用相当多的一篇文章了,InputMethodManager内存泄露现象及解决,但实际在测试的时候还是发现不能解决掉内存泄露的问题,这里就直接贴出作者的解决方案

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 = null;
    Object obj_get = null;
    for (int i = 0;i < arr.length;i ++) {
        String param = arr[i];
        try{
            f = imm.getClass().getDeclaredField(param);
            if (f.isAccessible() == false) {
                f.setAccessible(true);
            } // author: sodino mail:[email protected]
            obj_get = f.get(imm);
            if (obj_get != null && 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循环了
                    if (QLog.isColorLevel()) {
                        QLog.d(ReflecterHelper.class.getSimpleName(), QLog.CLR, "fixInputMethodManagerLeak break, context is not suitable, get_context=" + v_get.getContext()+" dest_context=" + destContext);
                    }
                    break;
                }
            }
        }catch(Throwable t){
            t.printStackTrace();
        }
    }
}

这里比较有问题的是if (v_get.getContext() == destContext)这句代码,至于为什么作者要加上这个判断可以去看作者的文章,但是自己实测发现,这句代码一直不可能被触发。测试的demo很简单,就两个activity,从A跳到B页面,当B销毁的时候会调用到
fixInputMethodManagerLeak,而此时v_get将会是一个decorview的类,decorview.getContext获取到的结果将和destContext永远不相等,所以f.set(imm, null);这句代码不可能被执行到。所以说使用作者的解决方案并不能真正解决掉这个问题,作者文章的底部也有人留言反应在7.0华为手机上任然有问题,说明和我的测试结果是一致的

mlastsrvview

还有一个问题就是我这边的泄露是由mlastsrvview引起的,作者的解决方案也没涉及到这个字段,参照作者的思路反射设置这个字段为null应该是可以的,关于这个字段自己经过测试发现是华为手机特有的一个字段,google原生系统并没有这个字段。这个字段的作用到底是什么,自己写了测试demo得出的结论就是该字段是用来存储上一个页面view的,举个简单的例子ABC三个页面当你从C页面回退到B页面时,此时mlastsrvview保存这的就是C的decorview,当从B退回到A页面此时mlastsrvview保存的就是B的decorview。

所以想要发生内存泄露很简单,当从B回到A页面后停留一段时间leakcanary就会报出文章第二张图中的泄露问题,因为decorview一直被mlastsrvview引用着无法释放内存,知道原因后那么解决方法就很简单了,就是通过反射在页面销毁的时候通过反射将mlastsrvview设置为null就可以了

public class FixMemLeak {

    private static Field field;
    private static boolean hasField = true;

    public static void fixLeak(Context context) {
        if (!hasField) {
            return;
        }
        InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
        if (imm == null) {
            return;
        }

        String[] arr = new String[]{"mLastSrvView"};
        for (String param : arr) {
            try {
                if (field == null) {
                    field = imm.getClass().getDeclaredField(param);
                }
                if (field == null) {
                    hasField = false;
                }
                if (field != null) {
                    field.setAccessible(true);
                    field.set(imm, null);
                }
            } catch (Throwable t) {
                t.printStackTrace();
            }
        }
    }
}

将这段代码放到页面销毁的地方执行,再次使用leakcanary检测你就会发现华为手机不会再报mlastsrvview泄露的问题了。

思考

关于mlastsrvview泄露的问题。其实严格来说也算不上很严重的问题,为什么这么说呢,华为在6.0系统的手机上存在hwPhoneWindow泄露问题,但在7.0之后至少我这边的华为测试机上已经没有发现这个问题,可以说明华为修复掉了这个问题,但是mlastsrvview泄露在6.0还是7.0都依然存在,说明华为可能认为这不算是一个泄露问题,因为就像文中所说的mlastsrvview只会泄露上一个页面,而手机在频繁使用的时候,上一个页面是变动比较快的,所以说造成的泄露应该只是短暂时间的泄露,算不上真正意义的内存无法回收,我想可能华为是基于这点考虑所以没有修复这个问题。但是对于有强迫症一定想解决这个问题的,那就不妨试试我说的反射方法

你可能感兴趣的:(关于华为inputMethodManager内存泄露)