android inputmethodmanager内存泄露,Android InputMethodManager 导致的内存泄露及解决方案...

今天在使用LeakCanary检查应用的内存泄露时,报了一个这样的错误,并且还给出了参考链接,原来这是Android输入法的一个bug,在15<=API<=23中都存在。

a4ff117f33ef

软键盘导致内存泄漏

LeakCanary之所以能够显示参考链接是因为它有一个针对SDK已知内存泄露的列表,放在AndroidExcludedRefs.java中,比如输入法的这个。

a4ff117f33ef

这个问题很多人都遇到过,网上已经有比较成熟的方案,分析原因比较透彻的是这篇文章:[Android][Memory Leak] InputMethodManager内存泄露现象及解决,改善方案可以参考Leaknary给出的方案:InputMethodManager内存泄露修正方案**,在退出使用InputMethodManager的Activity时,调用fixFocusedViewLeak方法即可解决。

/**

* 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 (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1|| Build.VERSION.SDK_INT > 23) {

return;

}

final InputMethodManager inputMethodManager =

(InputMethodManager) application.getSystemService(Context.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 Application.ActivityLifecycleCallbacks() {

@Override

public void onActivityDestroyed(Activity activity){

}

@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 bundle){

}

@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);

}

});

}

static class ReferenceCleaner

implements 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;

}

}

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值