最近通過LeakCanary檢測內(nèi)存泄漏比勉,發(fā)現(xiàn)一個(gè)很莫名其妙的問題持隧,截圖如下所示:
從這里可以看到晒来,InputMethodManager的mNextServedView持有了一個(gè)RecyclerView的引用鹿响,而RecyclerView則持有了IndexActivity的引用肛度,從而導(dǎo)致了IndexActivity的內(nèi)存泄漏傻唾。
據(jù)說這是輸入法的一個(gè)bug,在LeakCanary上有個(gè)提到過這個(gè)問題(https://github.com/square/leakcanary/issues/572)承耿,但是該問題并不是一個(gè)普遍性的bug冠骄,而是部分機(jī)型上會(huì)出現(xiàn)。本人拿手頭上的幾臺(tái)設(shè)備測試過加袋,小米MIX2凛辣、小米紅米5、三星Note3职烧、三星Galaxy S7 edge扁誓、Vivo X9、Oppo R9還有一臺(tái)華為設(shè)備忘記型號(hào)了阳堕,只有三星 Galaxy S7 edge上頻繁出現(xiàn)InputMethodManager內(nèi)存泄漏的問題跋理。不得不說,谷歌的Android系統(tǒng)坑真是多恬总,各種機(jī)型前普、各種分辨率,但是沒辦法壹堰,有坑我們也得一個(gè)一個(gè)去填拭卿。
查看InputMethodManager的源碼可以看到以下定義:
/**
* This is the root view of the overall window that currently has input
* method focus.
*/
View mCurRootView;
/**
* This is the view that should currently be served by an input method,
* regardless of the state of setting that up.
*/
View mServedView;
/**
* This is then next view that will be served by the input method, when
* we get around to updating things.
*/
View mNextServedView;
通過LeakCanary的檢測發(fā)現(xiàn),發(fā)生內(nèi)存泄漏時(shí)贱纠,這三者都有可能被InputMethodManager持有依賴引用峻厚,而View則一直被Activity持有引用,進(jìn)而導(dǎo)致了Activity的內(nèi)存泄漏谆焊。
那么怎么解決這個(gè)問題呢惠桃?參考了不少方案,發(fā)現(xiàn)還是采用反射來將這些依賴的View置空,手動(dòng)切斷對(duì)象的引用鏈最有效辜王,具體代碼如下:
public class MemoryLeakUtil {
public static void fixInputMethodMemoryLeak(Context context) {
if (context == null)
return;
InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
if (inputMethodManager == null)
return;
String[] viewArr = new String[]{"mCurRootView", "mServedView", "mNextServedView"};
Field field;
Object fieldObj;
for (String view : viewArr) {
try {
field = inputMethodManager.getClass().getDeclaredField(view);
if (!field.isAccessible()) {
field.setAccessible(true);
}
fieldObj = field.get(inputMethodManager);
if (fieldObj != null && fieldObj instanceof View) {
View fieldView = (View) fieldObj;
if (fieldView.getContext() == context) {
//注意需要判斷View關(guān)聯(lián)的Context是不是當(dāng)前Activity劈狐,否則有可能造成正常的輸入框輸入失效
field.set(inputMethodManager, null);
} else {
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
在內(nèi)存泄漏的Activity里增加:
@Override
protected void onDestroy() {
//手動(dòng)切斷InputMethodManager里的View引用鏈
MemoryLeakUtil.fixInputMethodMemoryLeak(this);
super.onDestroy();
}