今年开发的一个项目发生了内存泄漏,在六七月份时就观察到即使退出了所有的activity,但是app在后台占用的内存还是没有降下来,保持到75M左右。一直以为是项目使用的代码出现问题,然后继续优化,可是无论怎么优化都达不到合理的内存占用值,最后挤挤牙膏也是降到了60M那样。当时优化了一周效果并不明显。好在60M对于我们应用来说还能够接受,就继续开发迭代功能。最近其他项目迭代开发完成比较有空了,就回到原来项目区看看还能不能继续优化。由于当时项目用的Android studio2.3作为开发工具所以内存调优工具没有那么好,于是就换到最新的Android studio3.2

Android studio3.2的内存工具是profiler,在Android studio的左下角可以打开

经过两天的debug准确定位了问题

这里就形成了变量循环链

可以看到因为InputMethodManager的生命周期非常长,导致即使activity被onDestroy()后也无法顺利被回收,进而导致更多变量无法回收的内存泄漏。

为此特地Google了一把,发现从Android4.4到Android6.0这个bug一直存在。

解决方案:这个问题在不少人遇到过,已经有成形的代码可以使用,具体代码下面的GitHub

https://gist.github.com/pyricau/4df64341cc978a7de414

当然这份代码有两个地方需要修改

public static void fixFocusedViewLeak(Application application) {// Don't know about other versions yet.if (SDK_INT < KITKAT || SDK_INT > 22) {return;}//......
}

修改原因

1. InputMethodManager内存泄漏的bug在Android6.0还存在;

2. SDK_INT > 22使用了魔鬼数字,可以更新最新的SDK将魔鬼数字改为Version代号

修改结果如下:

public static void fixFocusedViewLeak(Application application) {// Don't know about other versions yet.if (SDK_INT < Build.VERSION_CODES.KITKAT || SDK_INT > Build.VERSION_CODES.M) {return;}//......
}

以下代码解决了在activity退出后立即将view与InputMethodManager解绑

package com.lucky.utils;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.ContextWrapper;
import android.os.Build;
import android.os.Bundle;
import android.os.Looper;
import android.os.MessageQueue;
import android.util.Log;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.inputmethod.InputMethodManager;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;import static android.content.Context.INPUT_METHOD_SERVICE;
import static android.os.Build.VERSION.SDK_INT;
import static android.os.Build.VERSION_CODES.KITKAT;public class IMMLeaks {static class ReferenceCleanerimplements MessageQueue.IdleHandler, View.OnAttachStateChangeListener,ViewTreeObserver.OnGlobalFocusChangeListener {private final InputMethodManager inputMethodManager;private final Field mHField;private final Field mServedViewField;private final Method finishInputLockedMethod;ReferenceCleaner(InputMethodManager inputMethodManager, Field mHField, Field mServedViewField,Method finishInputLockedMethod) {this.inputMethodManager = inputMethodManager;this.mHField = mHField;this.mServedViewField = mServedViewField;this.finishInputLockedMethod = finishInputLockedMethod;}@Override public void onGlobalFocusChanged(View oldFocus, View newFocus) {if (newFocus == null) {return;}if (oldFocus != null) {oldFocus.removeOnAttachStateChangeListener(this);}Looper.myQueue().removeIdleHandler(this);newFocus.addOnAttachStateChangeListener(this);}@Override public void onViewAttachedToWindow(View v) {}@Override public void onViewDetachedFromWindow(View v) {v.removeOnAttachStateChangeListener(this);Looper.myQueue().removeIdleHandler(this);Looper.myQueue().addIdleHandler(this);}@Override public boolean queueIdle() {clearInputMethodManagerLeak();return false;}private void clearInputMethodManagerLeak() {try {Object lock = mHField.get(inputMethodManager);// This is highly dependent on the InputMethodManager implementation.synchronized (lock) {View servedView = (View) mServedViewField.get(inputMethodManager);if (servedView != null) {boolean servedViewAttached = servedView.getWindowVisibility() != View.GONE;if (servedViewAttached) {// The view held by the IMM was replaced without a global focus change. Let's make// sure we get notified when that view detaches.// Avoid double registration.servedView.removeOnAttachStateChangeListener(this);servedView.addOnAttachStateChangeListener(this);} else {// servedView is not attached. InputMethodManager is being stupid!Activity activity = extractActivity(servedView.getContext());if (activity == null || activity.getWindow() == null) {// Unlikely case. Let's finish the input anyways.finishInputLockedMethod.invoke(inputMethodManager);} else {View decorView = activity.getWindow().peekDecorView();boolean windowAttached = decorView.getWindowVisibility() != View.GONE;if (!windowAttached) {finishInputLockedMethod.invoke(inputMethodManager);} else {decorView.requestFocusFromTouch();}}}}}} catch (IllegalAccessException | InvocationTargetException unexpected) {Log.e("IMMLeaks", "Unexpected reflection exception", unexpected);}}private Activity extractActivity(Context context) {while (true) {if (context instanceof Application) {return null;} else if (context instanceof Activity) {return (Activity) context;} else if (context instanceof ContextWrapper) {Context baseContext = ((ContextWrapper) context).getBaseContext();// Prevent Stack Overflow.if (baseContext == context) {return null;}context = baseContext;} else {return null;}}}}/*** Fix for https://code.google.com/p/android/issues/detail?id=171190 .** When a view that has focus gets detached, we wait for the main thread to be idle and then* check if the InputMethodManager is leaking a view. If yes, we tell it that the decor view got* focus, which is what happens if you press home and come back from recent apps. This replaces* the reference to the detached view with a reference to the decor view.** Should be called from {@link Activity#onCreate(android.os.Bundle)} )}.*/public static void fixFocusedViewLeak(Application application) {// Don't know about other versions yet.if (SDK_INT < Build.VERSION_CODES.KITKAT || SDK_INT > Build.VERSION_CODES.M) {return;}final InputMethodManager inputMethodManager =(InputMethodManager) application.getSystemService(INPUT_METHOD_SERVICE);final Field mServedViewField;final Field mHField;final Method finishInputLockedMethod;final Method focusInMethod;try {mServedViewField = InputMethodManager.class.getDeclaredField("mServedView");mServedViewField.setAccessible(true);mHField = InputMethodManager.class.getDeclaredField("mServedView");mHField.setAccessible(true);finishInputLockedMethod = InputMethodManager.class.getDeclaredMethod("finishInputLocked");finishInputLockedMethod.setAccessible(true);focusInMethod = InputMethodManager.class.getDeclaredMethod("focusIn", View.class);focusInMethod.setAccessible(true);} catch (NoSuchMethodException | NoSuchFieldException unexpected) {Log.e("IMMLeaks", "Unexpected reflection exception", unexpected);return;}application.registerActivityLifecycleCallbacks(new LifecycleCallbacksAdapter() {@Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {ReferenceCleaner cleaner =new ReferenceCleaner(inputMethodManager, mHField, mServedViewField,finishInputLockedMethod);View rootView = activity.getWindow().getDecorView().getRootView();ViewTreeObserver viewTreeObserver = rootView.getViewTreeObserver();viewTreeObserver.addOnGlobalFocusChangeListener(cleaner);}});}
}

LifeCycleCallbacksAdapter.java

package com.lucky.utils;import android.app.Activity;
import android.app.Application;
import android.os.Bundle;/** Helper to avoid implementing all lifecycle callback methods. */
public class LifecycleCallbacksAdapter implements Application.ActivityLifecycleCallbacks {@Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {}@Override public void onActivityStarted(Activity activity) {}@Override public void onActivityResumed(Activity activity) {}@Override public void onActivityPaused(Activity activity) {}@Override public void onActivityStopped(Activity activity) {}@Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}@Override public void onActivityDestroyed(Activity activity) {}
}

最后在项目的Application中注册回调即可

public class MyApp extends Application {@Overridepublic void onCreate() {super.onCreate();InputMethodManagerLeak.fixFocusedViewLeak(this);}
}

终于解决了问题,一直以为是自己的代码有问题,没想到是一个系统的bug——Android4.4到Android6.0,真是不容易。

InputMethodManager内存泄漏的原因及解决方案相关推荐

  1. Android 中内存泄漏的原因和解决方案

    之前研究过一段时间关于 Android 内存泄漏的知识,大致了解了导致内存泄漏的一些原因,但是没有深入去探究,很多细节也理解的不够透彻,基本上处于一种似懂非懂的状态,最近又研究了一波,发现有很多新的收 ...

  2. Java内存泄露系列--内存泄露的原因及解决方案(大全)

    原文网址:Java内存泄露系列--内存泄露的原因及解决方案(大全)_IT利刃出鞘的博客-CSDN博客 简介 简介 本文介绍Java中内存泄露的一些原因与解决方案. 如果内存泄露的空间足够大,就会导致内 ...

  3. UWP开发入门(十六)——常见的内存泄漏的原因

    原文:UWP开发入门(十六)--常见的内存泄漏的原因 本篇借鉴了同事翔哥的劳动成果,在巨人的肩膀上把稿子又念了一遍. 内存泄漏的概念我这里就不说了,之前<UWP开发入门(十三)--用Diagno ...

  4. 内存泄漏的3个解决方案与原理实现,掌握一个轻松应对开发丨内存池|mtrace||API实现|框架封装|中间件|异步请求|连接池

    内存泄漏的3个解决方案与原理实现,掌握一个轻松应对开发 视频讲解如下,点击观看: 内存泄漏的3个解决方案与原理实现,掌握一个轻松应对开发丨内存池|mtrace||API实现|框架封装|中间件|异步请求 ...

  5. android inputmethodmanager内存泄露,Android InputMethodManager内存泄漏

    最近通过LeakCanary检测内存泄漏,发现一个很莫名其妙的问题,截图如下所示: 内存泄漏 从这里可以看到,InputMethodManager的mNextServedView持有了一个Recycl ...

  6. 内存泄漏和内存溢出以及原因和解决方案

    1.什么是内存泄漏? Java 中的内存泄漏是指应用程序不再需要的对象在 Java 虚拟机 (JVM) 中仍然存在的状态.通俗来讲就是生命周期长的对象持有生命周期短的对象,导致GC无法回收本该需要回收 ...

  7. 内存泄漏的原因及解决办法_探索内存碎片化 - 第288篇

    相关历史文章(阅读本文之前,您可能需要先看下之前的系列 ) 色谈Java序列化:女孩子慎入 - 第280篇 烦不烦,别再问我时间复杂度了:这次不色,女孩子进来吧 - 第281篇 双向链表,比西天还远? ...

  8. OOM(内存溢出)造成原因及解决方案

    一.概念 内存溢出(Out Of Memory,简称OOM)是指应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于能提供的最大内存. 二.造成原因 2.1.内存泄漏 由于长 ...

  9. 内存泄漏的原因及解决办法_浅谈 JS 内存泄漏问题

    什么是内存泄漏? 程序的运行需要内存.只要程序提出要求,操作系统或者运行时(runtime)就必须供给内存. 对于持续运行的服务进程(daemon),必须及时释放不再用到的内存.否则,内存占用越来越高 ...

  10. 什么是内存泄漏,常见引起引起内存泄漏的原因,及解决办法

    一:什么是内存泄露 内存泄露是指:内存泄漏也称作"存储渗漏",用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元.直到程序结束.(其实说白了就是该内存 ...

最新文章

  1. 豪赌 ARM 梦碎:63 岁孙正义的「花甲历险记」
  2. mysql的慢查询日志功能_MySQL 慢查询日志
  3. python 爬虫 记录
  4. 理解JS中的this的指向
  5. 太白教你学python---博客分类目录
  6. Spring开发人员知道的一件事
  7. 前端学习(530):等分布局得间距方案第二种方式
  8. 【转】WPF调用图片路径,或资源图片
  9. python之变量的私密处理
  10. terminal mysql 停止_转载MySQL之终端(Terminal)管理MySQL
  11. VS 2005部署应用程序提示“应用程序无法正常启动( 0x0150002)” 解决方案
  12. 动态向客户端注册脚本文件
  13. Redis基础6(Redis6管道)
  14. 粉红噪音测试软件,爱卡音响测试(59) Levante和B&W音响
  15. EXCEL自定义功能区Ribbon
  16. 职中心得体会300字高一计算机,职高毕业生自我鉴定范文300字
  17. 网络推广有哪些常见的推广方法?
  18. Unity3d发布WebGL打包AssetBundle的材质球丢失问题
  19. 程序员与代码之间的搞笑日常,笑的人肚子痛!
  20. SSR在天猫优品大促会场的探索实践

热门文章

  1. 搭建springmvc项目遇到的问题
  2. html中父子元素的解释,CSS 子绝父相 理解
  3. html帮助文档看不了,Service Log按照文档设置之后,在web页面看不到,帮助文档的图片有点问题(看不到了),能不能处理一下...
  4. Windows 安装两个MYSQL实例
  5. Swagger入参为List
  6. 《30天学习30种新技术》-Day 15:Meteor —— 从零开始创建一个 Web 应用
  7. AJAX 数据库实例
  8. FOR XML PATH 应用及其反向分解
  9. POJ-1191 棋盘分割 记忆化搜索
  10. Repeater思路整理