美文网首页
Android LeakCanary InputConnetio

Android LeakCanary InputConnetio

作者: 唔笛plk | 来源:发表于2019-07-25 15:54 被阅读0次

    一、键盘内存泄漏问题表现

    在Android 系统版本6.0以下,EditText InputConnetionWrapper 内存泄漏,具体表现如下图


    ba6626c-f7c8db2-114-0.png

    原因分析也很简单,主要是Activty结束时,输入法仍然持有Activty,

    二、解决办法

    • 方法一:
      自定义EditText 在,初始化EditText中使用Application.context,即使用全局context,采用这种方法导致会,不能继承Activity的样式,只能自己在去定义EditText的样式
    **
     * @author Wudi
     * @date 2019/7/11
     */
    @SuppressLint("AppCompatCustomView")
    public class BaseEditText extends EditText {
        private static Field mParent;
    
        static {
            try {
                mParent = View.class.getDeclaredField("mParent");
                mParent.setAccessible(true);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            }
        }
    
        public BaseEditText(Context context) {
            super(context.getApplicationContext());
        }
    
        public BaseEditText(Context context, AttributeSet attrs) {
            super(context.getApplicationContext(), attrs);
        }
    
        public BaseEditText(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context.getApplicationContext(), attrs, defStyleAttr);
        }
    
        @SuppressLint("NewApi")
        public BaseEditText(Context context, AttributeSet attrs, int defStyleAttr,
                            int defStyleRes) {
            super(context.getApplicationContext(), attrs, defStyleAttr, defStyleRes);
        }
    
        @Override
        protected void onDetachedFromWindow() {
            try {
                if (mParent != null)
                    mParent.set(this, null);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            }
            super.onDetachedFromWindow();
        }
    
    }
    
    • 方法二:
      使用Square公司LeakCannry的官方修复方法(推荐)
    package com.gzjiequan.rescuewithyou.util;
    
    /**
     * @author Wudi
     * @date 2019/7/25
     */
    
    import android.app.Activity;
    import android.app.Application;
    import android.content.Context;
    import android.content.ContextWrapper;
    import android.os.Build;
    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;
    
    /**
     * 修复Android skd 15 到 23 键盘内存
     */
    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);
                    if(lock==null){
                        lock=new Object();
                    }
                    // 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  )}.
         */
        public static void fixFocusedViewLeak(Application application) {
    
            // Don't know about other versions yet.
            if (Build.VERSION.SDK_INT < 15 || Build.VERSION.SDK_INT > 23) {
                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);
                }
            });
        }
    
        /** Helper to avoid implementing all lifecycle callback methods. */
        public static 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) {
    
            }
        }
    }
    

    在Application初始化onCreate中使用

    IMMLeaks.fixFocusedViewLeak(this);
    

    https://gist.github.com/pyricau/4df64341cc978a7de414

    相关文章

      网友评论

          本文标题:Android LeakCanary InputConnetio

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