android m 内存泄漏,Android内存泄露之InputMethodManager mNextServedView导致的内存泄露

import android.app.Activity;

import android.app.Application;

import android.content.Context;

import android.content.ContextWrapper;

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

}

}

}

}

/**

* 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 < KITKAT || SDK_INT > 22) {

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

}

});

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值