51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

测试开发精英班,通向高级软件测试工程师【周活动】 找茬--心里圈的故事 !【长期招募】博为峰网校招聘兼职讲师!横扫BAT,Python全栈测试开发技能大全
【专家100期】:如何走出自动化测试第一步 【专题】有远见的测试员已经开始学MySQL了 【征稿】新年伊始,越努力越优秀! 自学软件测试那点事
查看: 70|回复: 0

Leakcanary部分泄露警报无需修复

[复制链接]

该用户从未签到

发表于 2019-3-15 13:34:27 | 显示全部楼层 |阅读模式
前言

使用leakcanary检查内存泄露之后,由于他的工作原理,造成所有的在上下文关闭之后,还未被释放的资源就会引爆内存泄露通知。但是不是所有的泄露都需要修复的。下面总结几个我的血泪史,希望以后不要重蹈覆辙。

提示InputMethodManager.sInstance这个静态实例可能通过各种路径对context进行了泄露。具体的路径可能会不一样,但是归根到最后都是提示InputMethodManager.sInstance静态引用泄露。 通过网上搜索,可能有类似的修复,如下 public static final class TypedObject { private final Object object; private final Class type;

  1. public TypedObject(final Object object, final Class type)
  2.     {
  3.     this.object = object;
  4.     this.type = type;
  5.     }

  6.     Object getObject()
  7.     {
  8.         return object;
  9.     }

  10.     Class getType()
  11.     {
  12.         return type;
  13.     }
  14. }

  15. public static void invokeMethodExceptionSafe(final Object methodOwner, final String method, final TypedObject... arguments)
  16. {
  17.     if (null == methodOwner)
  18.     {
  19.         return;
  20.     }

  21.     try
  22.     {
  23.         final Class<?>[] types = null == arguments ? new Class[0] : new Class[arguments.length];
  24.         final Object[] objects = null == arguments ? new Object[0] : new Object[arguments.length];

  25.         if (null != arguments)
  26.         {
  27.             for (int i = 0, limit = types.length; i < limit; i++)
  28.             {
  29.                 types[i] = arguments[i].getType();
  30.                 objects[i] = arguments[i].getObject();
  31.             }
  32.         }

  33.         final Method declaredMethod = methodOwner.getClass().getDeclaredMethod(method, types);

  34.         declaredMethod.setAccessible(true);
  35.         declaredMethod.invoke(methodOwner, objects);
  36.     }
  37.     catch (final Throwable ignored)
  38.     {
  39.     }
  40. }

  41. public static void fixInputMethodManager(Activity activity)
  42. {
  43.     final Object imm = activity.getSystemService(Context.INPUT_METHOD_SERVICE);

  44.     final Reflector.TypedObject windowToken
  45.         = new Reflector.TypedObject(activity.getWindow().getDecorView().getWindowToken(), IBinder.class);

  46.     Reflector.invokeMethodExceptionSafe(imm, "windowDismissed", windowToken);

  47.     final Reflector.TypedObject view
  48.         = new Reflector.TypedObject(null, View.class);

  49.     Reflector.invokeMethodExceptionSafe(imm, "startGettingWindowFocus", view);
复制代码

主要通过反射,修复内存泄露。

但是,下面的话,非常重要,非常重要,非常重要,重要的事情说三遍:这个属于系统级别的泄露,也就是说,你不泄露,别人也会泄露,而且整个android系统,只保留一个static instance的引用,所以这个修复,对整个系统的内存没有太大的改善。而且这个修复的隐患是,你有可能会在页面跳转的时候,遇到各种不可预测的编辑框无法获取焦点的问题。所以,我建议,这个泄露,可以忽略。


AsyncQueryHandler 没有quit

有时候我们会遇到HandlerThread没有quit而爆出的泄露

而android系统中有个AsyncQueryWorker从源代码看

  1. public AsyncQueryHandler(ContentResolver cr) {
  2.     super();
  3.     mResolver = new WeakReference<ContentResolver>(cr);
  4.     synchronized (AsyncQueryHandler.class) {
  5.         if (sLooper == null) {
  6.             HandlerThread thread = new HandlerThread    ("AsyncQueryWorker");
  7.             thread.start();
  8.             sLooper = thread.getLooper();
  9.         }
  10.     }
  11.     mWorkerThreadHandler = createHandler(sLooper);
  12. }
复制代码

这个名为AsyncQueryWorker的HandlerThread自从start之后就没人管了。这个时候,leakcanary也会提示泄露。 于是我曾自作聪明,通过反射将这个sLooper强制退出,代码如下:

  1. //linlian@2015.06.01 release static sLooper in AsyncQueryHandler
  2. public static void fixAsyncQueryWorker(){
  3.     Field sLooperCached = null;
  4.     try {
  5.         sLooperCached = Class.forName("android.content.AsyncQueryHandler").getDeclaredField("sLooper");
  6.         sLooperCached.setAccessible(true);
  7.     } catch (Exception ex) {
  8.         ex.printStackTrace();
  9.     }
  10.     if (sLooperCached == null) return;
  11.     Looper looper = null;
  12.      try {
  13.         // Get reference to the sLooperCached
  14.         looper = (Looper)sLooperCached.get(null);
  15.         if(looper!=null){
  16.             looper.quit();
  17.             sLooperCached.set(null,null);
  18.         }
  19.     } catch (Exception ex) {
  20.         ex.printStackTrace();
复制代码

这个修复的惨痛后果是,有些后台线程直接结束了,所以,对于AsyncQueryHandler的泄露,我也是建议不修复。

TextLine.sCached 泄露和inputManager一样,这个泄露,属于系统的静态引用而造成的泄露,但是这个泄露的修复,目前没有发现什么不良的影响,但是泄露修复的价值意义不知道大不大,也是你不泄露,别人也会泄露,反正总有一个这样的引用存在的。修复的效果也不是很明显 修复代码如下
  1. public static void clearTextLineCache(){
  2.     Field textLineCached = null;
  3.     try {
  4.         textLineCached = Class.forName("android.text.TextLine").getDeclaredField("sCached");
  5.         textLineCached.setAccessible(true);
  6.     } catch (Exception ex) {
  7.         ex.printStackTrace();
  8.     }
  9.     if (textLineCached == null) return;
  10.     Object cached = null;
  11.     try {
  12.         // Get reference to the TextLine sCached array.
  13.         cached = textLineCached.get(null);
  14.     } catch (Exception ex) {
  15.         ex.printStackTrace();
  16.     }
  17.     if (cached != null) {
  18.         // Clear the array.
  19.         for (int i = 0, size = Array.getLength(cached); i < size; i ++) {
  20.             Array.set(cached, i, null);
  21.         }
  22.     }
  23. }
复制代码
总结

LeakCanary是一个很好检查内存泄露的工具,但不是所有的泄露都需要修复,有些事android系统的泄露bug,通过各种方式曲线救国之后,不一定会达到一个很好的内存改善结果,所以干脆不要去动他,以免引起不可预测的运行异常。只有自己非常肯定的,由于使用不规范等引起的内存泄露才是我们重点关注的部分





回复

使用道具 举报

本版积分规则

关闭

站长推荐上一条 /3 下一条

小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

GMT+8, 2019-3-26 14:27 , Processed in 0.057795 second(s), 25 queries .

Powered by Discuz! X3.2

© 2001-2019 Comsenz Inc.

快速回复 返回顶部 返回列表