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); | |
} | |
}); | |
} | |
} |
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) { | |
} | |
} |
This comment has been minimized.
This comment has been minimized.
Using this fix and launching a Google Interstitial Ad causes the app to crash, I added a condition to avoid adding the cleaner where activity is instance of com.google.android.gms.ads.AdActivity. |
This comment has been minimized.
This comment has been minimized.
Here's how I fix: Use The most common reason why "requestFeature() must be called before adding content" thrown is because we call In The method name in getApplication().dispatchActivityCreated(this, savedInstanceState); Now, everything is clear.
We can call Hope this helpful. |
This comment has been minimized.
This comment has been minimized.
In Genymotion emulator Nexus 4 Android 5.1.0 Object lock = mHField.get(inputMethodManager); returned null, which caused a NPE when trying to lock on it. |
This comment has been minimized.
This comment has been minimized.
@pyricau should the function I'm a bit confused since in the function you register for every activity that was created. |
This comment has been minimized.
This comment has been minimized.
is it a typo, it should be
I think. |
This comment has been minimized.
This comment has been minimized.
|
This comment has been minimized.
This comment has been minimized.
Simple solution to solve this |
This comment has been minimized.
This comment has been minimized.
Sometimes, when screen is off, I'm getting a couple of such consecutive errors in log, but other than that, app works fine.
|
This comment has been minimized.
This comment has been minimized.
Sometimes Sometimes the null pointer will be |
This comment has been minimized.
This comment has been minimized.
@trey I found the familiar problem InputMethodManager#mNextServedView leaks.It happened at Android 6.0 MIUI 8.0 |
This comment has been minimized.
This comment has been minimized.
@MerlinYu I also millet 5 frequently found this problem,You solve the above method? |
This comment has been minimized.
This comment has been minimized.
@pyricau it happens on API 23 also |
This comment has been minimized.
This comment has been minimized.
@MerlinYu same here |
This comment has been minimized.
This comment has been minimized.
I have the same screen as MerlinYu on Android emulator API 23. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
https://gist.github.com/pyricau/4df64341cc978a7de414#file-immleaks-java-L137 |
This comment has been minimized.
This comment has been minimized.
I'm still seeing this issue on API 25 (7.1.2) |
This comment has been minimized.
This comment has been minimized.
@rodgavila, that's pretty weird considering I saw the commit that Google used to fix this and which was part of Android N first release. I even tested it on multiple devices and it indeed solved the leak, so you might be having some other leak in reference to other objects. |
This comment has been minimized.
This comment has been minimized.
int the custom Application onCreate() method, invoke IMMLeaks.fixFocusedViewLeak(this) |
This comment has been minimized.
This comment has been minimized.
thank you @TreyCai |
This comment has been minimized.
This comment has been minimized.
Thanks a lot for this fix! Can I kindly ask you to replace
with
in the source code? Also, I tried to reproduce on different Android version (using Genymotion emulators): 4.3 (18) : could reproduce. Your code fixed the leak. 4.4.4 (19) : could reproduce. Your code fixed the leak. 5.0 (21) : could reproduce, your code created an exception with a null 'lock' object here:
Debugger showed that InputMethodManager.isActive() returns false and all fields of InputMethodManager are null, so lock being 'null' seems normal in this case.
So I added
and no more exception I am a bit puzzled, as reproducing the leak is not easy (I have to try many time the same 'scenario) and it's even less easy to reproduce the exception about null lock object. But I think my simple experiments could be of some interest for you, so I am adding the outcome here. Thanks a lot for this fix anyway :-) |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
@pyricau To use this fix i just need to call IMMLeaks.fixFocusedViewLeak(this); from my custom Application.onCreate() ? |
This comment has been minimized.
This comment has been minimized.
line 151 should be |
This comment has been minimized.
This comment has been minimized.
I've been experiencing following crash:
Solution is to make parameter |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This fix will be automatically running in LeakCanary 2.6 ! Here's the port: square/leakcanary#2001 |
This comment has been minimized.
This comment has been minimized.
thanks,it's helpful |
This comment has been minimized.
any ideas for this crash -->Caused by: android.util.AndroidRuntimeException: requestFeature() must be called before adding content
which was caused after calling fixFocusedViewLeak in activity's OnCreate? Tried to add it before and after setcontentview