Android InputMethodManager 导致的内存

作者: 张明云 | 来源:发表于2016-05-02 08:07 被阅读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;
                }
            }
        }
    }

    相关文章

      网友评论

      • BrightVan:这个泄露并不是很严重,而且不是持续泄露。而且和ROM,Android版本都有关系。而且可能出现这个泄露的InputMethodManager出现的地方太多了。所以,一般也就是不管了。上面的代码也不一定有用。
      • MoonlightAniki:并没有用,荣耀八 API 24
      • GIndoc:发现该方法不管用,还是会报内存泄漏,测试机器是酷派大神android 4.4,而且https://gist.github.com/pyricau/4df64341cc978a7de414 上也有很多人留言还是会有泄漏的问题
        吉凶以情迁:@cwenhui 太流氓了 泄露存在每一个activity
        GIndoc:@情随事迁666 你可以尝试将泄漏的activity放到另一个进程中,然后在退出的时候kill掉该进程。
        吉凶以情迁:嗯 ,很多方案基本上各种各样的不管用,我的荣耀8 找的方法竟然无效了
      • 8bfb60d54805:正好最近排查内存泄露有,感谢分享

      本文标题:Android InputMethodManager 导致的内存

      本文链接:https://www.haomeiwen.com/subject/dtklrttx.html