Android InputMethodManager 導致的內存泄露及解決方案

今天在使用LeakCanary檢查應用的內存泄露時苛白,報了一個這樣的錯誤射赛,并且還給出了參考鏈接,原來這是Android輸入法的一個bug遥椿,在15<=API<=23中都存在。

LeakCanary之所以能夠顯示參考鏈接是因為它有一個針對SDK已知內存泄露的列表淆储,放在AndroidExcludedRefs.java中冠场,比如輸入法的這個。

?這個問題很多人都遇到過本砰,網(wǎng)上已經(jīng)有比較成熟的方案碴裙,分析原因比較透徹的是這篇文章:[Android][Memory Leak] InputMethodManager內存泄露現(xiàn)象及解決,改善方案可以參考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;
            }
        }
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末还棱,一起剝皮案震驚了整個濱河市载慈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌诱贿,老刑警劉巖娃肿,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異珠十,居然都是意外死亡料扰,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進店門焙蹭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來晒杈,“玉大人,你說我怎么就攤上這事孔厉≌辏” “怎么了?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵撰豺,是天一觀的道長粪般。 經(jīng)常有香客問我,道長污桦,這世上最難降的妖魔是什么亩歹? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上小作,老公的妹妹穿的比我還像新娘亭姥。我一直安慰自己,他們只是感情好顾稀,可當我...
    茶點故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布达罗。 她就那樣靜靜地躺著,像睡著了一般静秆。 火紅的嫁衣襯著肌膚如雪粮揉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天诡宗,我揣著相機與錄音滔蝉,去河邊找鬼。 笑死塔沃,一個胖子當著我的面吹牛蝠引,可吹牛的內容都是我干的。 我是一名探鬼主播蛀柴,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼螃概,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了鸽疾?” 一聲冷哼從身側響起吊洼,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎制肮,沒想到半個月后冒窍,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡豺鼻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年综液,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片儒飒。...
    茶點故事閱讀 40,488評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡谬莹,死狀恐怖,靈堂內的尸體忽然破棺而出桩了,到底是詐尸還是另有隱情附帽,我是刑警寧澤,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布井誉,位于F島的核電站蕉扮,受9級特大地震影響,放射性物質發(fā)生泄漏颗圣。R本人自食惡果不足惜慢显,卻給世界環(huán)境...
    茶點故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一爪模、第九天 我趴在偏房一處隱蔽的房頂上張望欠啤。 院中可真熱鬧荚藻,春花似錦、人聲如沸洁段。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽祠丝。三九已至疾呻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間写半,已是汗流浹背岸蜗。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留叠蝇,地道東北人璃岳。 一個月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像悔捶,于是被迫代替她去往敵國和親铃慷。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,500評論 2 359

推薦閱讀更多精彩內容