安卓开发笔记Android日记Android开发者俱乐部

Android InputMethodManager 导致的内存

2016-05-02  本文已影响5536人  张明云

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

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

 这个问题很多人都遇到过,网上已经有比较成熟的方案,分析原因比较透彻的是这篇文章:[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;
            }
        }
    }
}
上一篇下一篇

猜你喜欢

热点阅读