Android InputMethodManager 导致的内存泄露

来源:互联网 发布:java ee下载安装 编辑:程序博客网 时间:2024/05/22 20:26

文章参考:https://zhuanlan.zhihu.com/p/20828861?refer=zmywly8866

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;                }            }        }    }
阅读全文
0 0
原创粉丝点击