[Android][Memory Leak] InputMethodManager記憶體泄露現象及解決

來源:互聯網
上載者:User

[Android][Memory Leak] InputMethodManager記憶體泄露現象及解決

現象:

在特定的機型天語k_touch_v9機型上,某個介面上出現InputMethodManager持有一Activity,導致該Activity無法回收.如果該Activity再次被開啟,則舊的會釋放掉,但新開啟的會被繼續持有無法釋放回收.MAT顯示Path to gc如下:

圖1. Leak path

天語k_touch_v9手機版本資訊:

圖2. K_touch_v9

一番搜尋後,已經有人也碰到過這個問題(見文章最後引用連結),給出的方法是:

@Overridepublic void onDestory() {    //Fix memory leak: http://code.google.com/p/android/issues/detail?id=34731    InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);    imm.windowDismissed(this.getWindow().getDecorView().getWindowToken()); // hide method    imm.startGettingWindowFocus(null); // hide method    super.onDestory();}


但在實踐中使用後,沒有真正解決,Activity仍存在,但path to gc指向為unknown.如:

圖3. Unknownpath

搜尋來的代碼不管用,就再想辦法.

要想讓Activity釋放掉,思路就是將path togc這個鏈路剪斷就可以.在這個bug中這個鏈路上有兩個節點mContext(DecorView)和 mCurRootView(InputMethodManager)可供考慮.下面思路就是從這兩個節點中選擇一個入手剪斷path to gc即可.

閱讀源碼可知, DecorView繼承自FrameLayout,mContext是其上下文環境,牽涉太多,不適合操作入手.mCurRootView在InputMehtodManager中的使用就簡單得多了,在被賦值初始化後,被使用的情境只有一次判斷及一次日誌列印.所以這裡選中mCurRootView為突破口.剪斷其path to gc的操作為通過Java Reflection方法將mCurRootView置空即可(見文後代碼).

編碼實現後,再測,發現仍有泄露,但泄露情況有所變化,如:

圖4. Leak path

新的泄露點為mServedView/mNextServedView,可以通過同樣的JavaReflection將其置空,剪斷path to gc.但這裡有個問題得小心,這裡強制置空後,會不會引起InputMethodManager的NullPointerException呢?會不會引起系統內部邏輯崩潰?再次查閱源碼,發現mServedView及mNextServedView在代碼邏輯中一直有判空邏輯,所以這時就可以放心的強制置空來解決問題了.


圖5. 判空邏輯

最後貼出代碼實現:

              public static void fixInputMethodManagerLeak(Context context) {                            if (context == null) {                                          return;                            }                            try {                                          // 對 mCurRootView mServedView mNextServedView 進行置空...                                          InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);                                          if (imm == null) {                                                        return;                                          }// author:sodino mail:sodino@qq.com                                           Object obj_get = null;                                          Field f_mCurRootView = imm.getClass().getDeclaredField("mCurRootView");                                          Field f_mServedView = imm.getClass().getDeclaredField("mServedView");                                          Field f_mNextServedView = imm.getClass().getDeclaredField("mNextServedView");                                           if (f_mCurRootView.isAccessible() == false) {                                                        f_mCurRootView.setAccessible(true);                                          }                                          obj_get = f_mCurRootView.get(imm);                                          if (obj_get != null) { // 不為null則置為空白                                                        f_mCurRootView.set(imm, null);                                          }                                           if (f_mServedView.isAccessible() == false) {                                                        f_mServedView.setAccessible(true);                                          }                                          obj_get = f_mServedView.get(imm);                                          if (obj_get != null) { // 不為null則置為空白                                                        f_mServedView.set(imm, null);                                          }                                           if (f_mNextServedView.isAccessible() == false) {                                                        f_mNextServedView.setAccessible(true);                                          }                                          obj_get = f_mNextServedView.get(imm);                                          if (obj_get != null) { // 不為null則置為空白                                                        f_mNextServedView.set(imm, null);                                          }                            } catch (Throwable t) {                                          t.printStackTrace();                            }              }


在Activity.onDestory()方法中執行以上方法即可解決.

              public  void onDestroy() {                            super.ondestroy();                            fixInputMethodManagerLeak(this);              }

事情看上去圓滿的解決了,但真的是嗎?

經過以上處理後,記憶體泄露是不存在了,但出現另外一個問題,就是有輸入框的地方,點擊輸入框後,卻無法出現IME介面了!

事故現場複現的操作步驟為:

ActivityA介面,點擊進入Activity B介面,B有輸入框,點擊輸入框後,沒有IME彈出。原因是InputMethodManager的關聯View已經被上面的那段代碼置空了。

事故原因得從Activity間的生命週期方法調用順序說起:

從Activity A進入Activity B的生命週期方法的調用順序是:

A.onCreate()→A.onResume()→B.onCreate()→B.onResume()→A.onStop()→A.onDestroy() 

也就是說,Activity B已經建立並顯示了,ActivityA這裡執行onDestroy()將InputMethodManager的關聯View置空了,導致IME無法彈出。

原因發現了,要解決也就簡單了。

fixInputMethodManagerLeak(ContextdestContext)方法參數中將目標要銷毀的Activity A作為參數傳參進去。在代碼中,去擷取InputMethodManager的關聯View,通過View.getContext()與Activity A進行對比,如果發現兩者相同,就表示需要回收;如果兩者不一樣,則表示有新的介面已經在使用InputMethodManager了,直接不處理就可以了。

修改後,最終代碼如下:

public static void fixInputMethodManagerLeak(Context destContext) {if (destContext == null) {return;}InputMethodManager imm = (InputMethodManager) destContext.getSystemService(Context.INPUT_METHOD_SERVICE);if (imm == null) {return;}String [] arr = new String[]{"mCurRootView", "mServedView", "mNextServedView"};Field f = null;Object obj_get = null;for (int i = 0;i < arr.length;i ++) {String param = arr[i];try{f = imm.getClass().getDeclaredField(param);if (f.isAccessible() == false) {f.setAccessible(true);} // author: sodino mail:sodino@qq.comobj_get = f.get(imm);if (obj_get != null && obj_get instanceof View) {View v_get = (View) obj_get;if (v_get.getContext() == destContext) { // 被InputMethodManager持有引用的context是想要目標銷毀的f.set(imm, null); // 置空,破壞掉path to gc節點} else {// 不是想要目標銷毀的,即為又進了另一層介面了,不要處理,避免影響原邏輯,也就不用繼續for迴圈了if (QLog.isColorLevel()) {QLog.d(ReflecterHelper.class.getSimpleName(), QLog.CLR, "fixInputMethodManagerLeak break, context is not suitable, get_context=" + v_get.getContext()+" dest_context=" + destContext);}break;}}}catch(Throwable t){t.printStackTrace();}}}


引用:

l InputMethodManager:googlecode

l InputMethodManger導致的Activity泄漏

l MainActivity is not garbage collected after destruction because it is referenced byInputMethodManager indirectly

l InputMethodManagerholds reference to the tabhost - Memory Leak - OOM Error

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.