Android- InputMethodManager Leaks Activity
InputMethodManager 内存泄漏问题
-What Happened
After putting in a new round of background images, I've noticed my app consistently crashing with out of memory exceptions if you exit and then restart the app. I've tracked this down to the fact that these background images are never being garbage collected, because the entire activity is being kept in memory by the InputMethodManager.
- Steps to reproduce the problem.
Create a new android project using Eclipse.
Create a launcher activity.
Add a finalizer:
protected void finalize()
{
Log.i("MainActivity", "Finalized!");
}
Install and launch the activity
Back out of the activity, in ddms, force garbage collection.
-- The log message will not activate.
Re-enter the activity, force garbage collection.
-- The log message will now print (for the first instance of the activity)
Exit the activity again. Force garbage collection. Dump the hprof profile. Examine it in the Eclipse Memory Analyzer Tool plugin.
You will see
InputMethodManager -> PhoneWindows$DecorView -> <YourActivityName>
Related * posts I've found:
http://*.com/questions/5038158/main-activity-is-not-garbage-collected-after-destruction-because-it-is-reference
http://*.com/questions/5769748/inputmethodmanager-holds-reference-to-the-tabhost-memory-leak-oom-error
最后解决方案为
在onDestory中插入调用此方法
private void fixInputMethodManager() {
try{
final Object imm = getSystemService(Context.INPUT_METHOD_SERVICE);
final Reflector.TypedObject windowToken = new Reflector.TypedObject(getWindow().getDecorView().getWindowToken(), IBinder.class);
Reflector.invokeMethodExceptionSafe(imm, "windowDismissed", windowToken);
final Reflector.TypedObject view = new Reflector.TypedObject(null, View.class);
Reflector.invokeMethodExceptionSafe(imm, "startGettingWindowFocus", view);
Reflector.fixInputMethodManagerViewLeak(ChartActivity.this, (InputMethodManager) imm);
}catch (Exception ignored){
}
}
添加一个inputmethod的处理类,通过反射进行
public class Reflector {
private static final String TAG = "Reflector";
public static final class TypedObject {
private final Object object;
private final Class type;
public TypedObject(final Object object, final Class type) {
this.object = object;
this.type = type;
}
Object getObject() {
return object;
}
Class getType() {
return type;
}
}
public static void invokeMethodExceptionSafe(final Object methodOwner, final String method, final TypedObject... arguments) {
if (null == methodOwner) {
return;
}
try {
final Class<?>[] types = null == arguments ? new Class[0] : new Class[arguments.length];
final Object[] objects = null == arguments ? new Object[0] : new Object[arguments.length];
if (null != arguments) {
for (int i = 0, limit = types.length; i < limit; i++) {
types[i] = arguments[i].getType();
objects[i] = arguments[i].getObject();
}
}
final Method declaredMethod = methodOwner.getClass().getDeclaredMethod(method, types);
declaredMethod.setAccessible(true);
declaredMethod.invoke(methodOwner, objects);
} catch (final Throwable ignored) {
}
}
public static void fixInputMethodManagerViewLeak(Context destContext, InputMethodManager imm) {
if (destContext == null) {
return;
}
String[] arr = new String[]{"mCurRootView", "mServedView", "mNextServedView"};
Field f = null;
Object obj_get = null;
for (String param : arr) {
try {
f = imm.getClass().getDeclaredField(param);
if (!f.isAccessible()) {
f.setAccessible(true);
}
obj_get = f.get(imm);
if (obj_get != null && obj_get instanceof View) {
View v_get = (View) obj_get;
if (v_get.getContext() == destContext) { // referenced context is held InputMethodManager want to destroy targets
f.set(imm, null); // set empty, destroyed node path to gc
} else {
// Not want to destroy the target, that is, again into another interface, do not deal with, to avoid affecting the original logic, there is nothing further for the cycle
Log.e(TAG, "fixInputMethodManagerLeak break, context is not suitable, get_context=" + v_get.getContext() + " dest_context=" + destContext);
break;
}
}
} catch (Throwable t) {
t.printStackTrace();
}
}
}
}