在目前的毕业设计中使用了LeakCanary来检查内存泄漏,但是每次从MainActivity退出程序时总会报InputMethodManager内存泄漏,原因系统中的InputMethodManager持有当前MainActivity的引用,导致了MainActivity不能被系统回收,从而导致了MainActivity的内存泄漏。查了很多资料,发现这是 Android SDK中输入法的一个Bug,在15<=API<=23中都存在,目前Google还没有解决这个Bug。
主要发生的场景是在MainActivity使用了FragmentStatePagerAdapter来管理Fragment,而Fragment中又使用了RecyclerView ,在Activity中的TreeObserver 或者其他系统类依赖了InputMethodManager。
在我的开发机小米3 原生6.0系统上LeakCanary报的内存泄漏引用链,从中可以看到时InputMethodManager的mNextServedView引用了MainActivity从而导致了内存泄漏。目前网上已经有好几个成熟的解决方法,了解3个解决此问题的比较好的方法,并进行了测试。总结如下:
- 方案一:通过反射修复
由于是InputMethodManager中的mNextServedView引用了MainActivity,所以最简单的就是通过java中的反射机制将mNextServedView置空,也就是赋值为null,切断了到MainActivity的引用链,从而使MainActivity进行内存回收。 -
参考链接:http://blog.youkuaiyun.com/sodino/article/details/32188809
/** * Created by Jason on 2017/1/12. * 防止InputMethodManager内存泄漏 */ public class CleanLeakUtils { public static void fixInputMethodManagerLeak(Context destContext) { if (destContext == null) { return; } InputMethodManager inputMethodManager = (InputMethodManager) destContext.getSystemService(Context.INPUT_METHOD_SERVICE); if (inputMethodManager == null) { return; } String [] viewArray = new String[]{"mCurRootView", "mServedView", "mNextServedView"}; Field filed; Object filedObject; for (String view:viewArray) { try{ filed = inputMethodManager.getClass().getDeclaredField(view); if (!filed.isAccessible()) { filed.setAccessible(true); } filedObject = filed.get(inputMethodManager); if (filedObject != null && filedObject instanceof View) { View fileView = (View) filedObject; if (fileView.getContext() == destContext) { // 被InputMethodManager持有引用的context是想要目标销毁的 filed.set(inputMethodManager, null); // 置空,破坏掉path to gc节点 } else { break;// 不是想要目标销毁的,即为又进了另一层界面了,不要处理,避免影响原逻辑,也就不用继续for循环了 } } }catch(Throwable t){ t.printStackTrace(); } } } }
创建了一个清除内存泄漏的工具类,然后在MainActivity中onDestroy 退出时调用上述方法就可以了:
public class MainActivity extends BaseActivity{ ...... @Override public void onDestroy(){ CleanLeakUtils.fixInputMethodManagerLeak(MainActivity.this); super.onDestroy(); } }
- 方案二:通过启动一个透明的Activity来修复
在搜索解决方案时,看到一个老外写了一篇文章,他通过大量实验总结发现如果在MainActivity调用finish( )后跳转一个新的Activity时,然后在新Activity中通过Handler发布一个delay延迟的消息来finish自身从而结束整个程序,通过这种方式就再也没有被InputMethodManager引用的Activity,所以也就没有内存泄漏发生了。 -
创建一个透明的CleanLeakActivity,不需要视图:
/** * Created by Jason on 2017/1/12. * 防止InputMethodManager内存泄漏 */ public class CleanLeakActivity extends AppCompatActivity{ @Override public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); new Handler().postDelayed(new Runnable() { @Override public void run() { finish(); } },500);//500毫秒后结束 } }
在res/vaules/styles中增加CleanLeakActivity 透明主题Style:
<resources> <style name="AppTheme.Transparent" parent="Theme.AppCompat.Light.NoActionBar"> <item name="android:windowIsTranslucent">true</item> <item name="android:windowBackground">@android:color/transparent</item> <item name="android:windowContentOverlay">@null</item> <item name="android:windowNoTitle">true</item> <item name="android:windowIsFloating">true</item> <item name="android:backgroundDimEnabled">false</item> </style> </resources>
在Application中声明CleanLeakActivity
<activity android:name="view.CleanLeakActivity" android:theme="@style/AppTheme.Transparent" android:screenOrientation="portrait"> </activity>
在MainActivity中调用Finish方法前启动CleanLeakActivity:
public class MainActivity extends BaseActivity{ ....... @Override public void onBackPressed() { super.onBackPressed(); startActivity(new Intent(this, CleanLeakActivity.class)); finish(); } }
由于CleanLeakActivity是透明,并且是悬浮窗口样式的,并且会在500毫秒内结束,所以用户察觉不到CleanLeakActivity的存在。老外就是流弊,这种方案也就是奇了。
- 方案3:LeakCanary官方给的解决方法 ,但是实验之后并没有效果,还是报内存泄漏。无解,仅供参考。