InputMethodManager内存泄漏的原因及解决方案
今年开发的一个项目发生了内存泄漏,在六七月份时就观察到即使退出了所有的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内存泄漏的原因及解决方案相关推荐
- Android 中内存泄漏的原因和解决方案
之前研究过一段时间关于 Android 内存泄漏的知识,大致了解了导致内存泄漏的一些原因,但是没有深入去探究,很多细节也理解的不够透彻,基本上处于一种似懂非懂的状态,最近又研究了一波,发现有很多新的收 ...
- Java内存泄露系列--内存泄露的原因及解决方案(大全)
原文网址:Java内存泄露系列--内存泄露的原因及解决方案(大全)_IT利刃出鞘的博客-CSDN博客 简介 简介 本文介绍Java中内存泄露的一些原因与解决方案. 如果内存泄露的空间足够大,就会导致内 ...
- UWP开发入门(十六)——常见的内存泄漏的原因
原文:UWP开发入门(十六)--常见的内存泄漏的原因 本篇借鉴了同事翔哥的劳动成果,在巨人的肩膀上把稿子又念了一遍. 内存泄漏的概念我这里就不说了,之前<UWP开发入门(十三)--用Diagno ...
- 内存泄漏的3个解决方案与原理实现,掌握一个轻松应对开发丨内存池|mtrace||API实现|框架封装|中间件|异步请求|连接池
内存泄漏的3个解决方案与原理实现,掌握一个轻松应对开发 视频讲解如下,点击观看: 内存泄漏的3个解决方案与原理实现,掌握一个轻松应对开发丨内存池|mtrace||API实现|框架封装|中间件|异步请求 ...
- android inputmethodmanager内存泄露,Android InputMethodManager内存泄漏
最近通过LeakCanary检测内存泄漏,发现一个很莫名其妙的问题,截图如下所示: 内存泄漏 从这里可以看到,InputMethodManager的mNextServedView持有了一个Recycl ...
- 内存泄漏和内存溢出以及原因和解决方案
1.什么是内存泄漏? Java 中的内存泄漏是指应用程序不再需要的对象在 Java 虚拟机 (JVM) 中仍然存在的状态.通俗来讲就是生命周期长的对象持有生命周期短的对象,导致GC无法回收本该需要回收 ...
- 内存泄漏的原因及解决办法_探索内存碎片化 - 第288篇
相关历史文章(阅读本文之前,您可能需要先看下之前的系列 ) 色谈Java序列化:女孩子慎入 - 第280篇 烦不烦,别再问我时间复杂度了:这次不色,女孩子进来吧 - 第281篇 双向链表,比西天还远? ...
- OOM(内存溢出)造成原因及解决方案
一.概念 内存溢出(Out Of Memory,简称OOM)是指应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于能提供的最大内存. 二.造成原因 2.1.内存泄漏 由于长 ...
- 内存泄漏的原因及解决办法_浅谈 JS 内存泄漏问题
什么是内存泄漏? 程序的运行需要内存.只要程序提出要求,操作系统或者运行时(runtime)就必须供给内存. 对于持续运行的服务进程(daemon),必须及时释放不再用到的内存.否则,内存占用越来越高 ...
- 什么是内存泄漏,常见引起引起内存泄漏的原因,及解决办法
一:什么是内存泄露 内存泄露是指:内存泄漏也称作"存储渗漏",用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元.直到程序结束.(其实说白了就是该内存 ...
最新文章
- 豪赌 ARM 梦碎:63 岁孙正义的「花甲历险记」
- mysql的慢查询日志功能_MySQL 慢查询日志
- python 爬虫 记录
- 理解JS中的this的指向
- 太白教你学python---博客分类目录
- Spring开发人员知道的一件事
- 前端学习(530):等分布局得间距方案第二种方式
- 【转】WPF调用图片路径,或资源图片
- python之变量的私密处理
- terminal mysql 停止_转载MySQL之终端(Terminal)管理MySQL
- VS 2005部署应用程序提示“应用程序无法正常启动( 0x0150002)” 解决方案
- 动态向客户端注册脚本文件
- Redis基础6(Redis6管道)
- 粉红噪音测试软件,爱卡音响测试(59) Levante和B&W音响
- EXCEL自定义功能区Ribbon
- 职中心得体会300字高一计算机,职高毕业生自我鉴定范文300字
- 网络推广有哪些常见的推广方法?
- Unity3d发布WebGL打包AssetBundle的材质球丢失问题
- 程序员与代码之间的搞笑日常,笑的人肚子痛!
- SSR在天猫优品大促会场的探索实践