InputMethodManager記憶體洩漏引發對View載入的探究
轉載自:https://www.jianshu.com/p/bc79e01da6b0
本文主要以InputMethodManager記憶體洩漏為引,來探究在不同系統版本中View是如何被載入的,涉及以下幾個方面 :
(1)如何解決InputMethodManager記憶體洩漏;
(2)為何View.getContext() 是TintContextWrapper;
(3)不同系統版本中View是如何被載入的。
一、如何解決InputMethodManager記憶體洩漏
在企鵝FM最新版本開發中,正好負責專案效能監控(主要是ANR、記憶體洩漏等等)這一塊,在記憶體洩漏這塊發現有很多InputMethodManager洩漏的上報,引用鏈如下:
在很早之前就聽說過InputMethodManager存在洩漏問題,查閱了一下相關資料,大多數是InputMethodManger中mServedView存在洩漏,而非圖1中的mLastSrvView ,不太應該啊 ,難道是某個版本rom的特殊機型,對照相關郵件發現全部都是華為機型。
專案中記憶體洩漏這塊,使用的是MagnifierSDK(
從上述解決方法中可以看出,主要針對的是mCurRootView、mServedView、mNextServedView,為什麼新增這些,本文在這裡就不展開講解了。
後面在同事的提醒下發現專案中有專門針對華為機型進行處理的方法,方法原理簡單粗暴,直接置空,破壞掉path to gc節點(同MagnifierSDK中ActivityLeakSolution::fixInputMethodManager處理方式一樣)。
既然同MagnifierSDK中ActivityLeakSolution::fixInputMethodManager處理方式一樣,為何沒生效了,回頭再去圖1中的引用鏈,從中發現了蛛絲馬跡,RadioSearchActivity 是TintContextWrapper 中的mBase 引用,而TintContextWrapper::mContext 又是被SearchView$SearchAutoComplete引用,由此猜想圖3中的view.getContext()==destContext條件可能失效。後續驗證發現上圖中View.getContenxt 確實是 TintContextWrapper 而非引用鏈中的RadioSearchActivity ,如下圖4所示:
圖4 檢視 view.getContext()二、為何View.getContext()是TintContextWrapper
為何View.getContext()是TintContextWrapper,而不是RadioSearchActivity。帶著疑問去檢視SearchView$SearchAutoComplete(這裡的SearchView是使用support v7庫)為何物,發現SearchAutoComplete直接繼承AppCompatAutoCompleteTextView。
圖5 SearchAutoComplete在AppCompatAutoCompleteTextView 的建構函式中,會將傳入的context(這裡指的是RadioSearchActivity)wrap成TintContextWrapper,可以方便的實現Android Material Design 中的Tint。
圖6 AppCompatAutoCompleteTextView現在雖然弄懂了這個案例中View.getContext()是TintContextWrapper的原因,那是不是所有的View都存在這樣的情況了,在什麼時候會轉化了,帶著這些疑問去探究一下View是如何載入的。
三、不同系統版本中View是如何被載入的
總所周知,Activity 載入佈局時,呼叫的是activity的setContentView()方法來載入佈局;而在Fragment中,是直接通過LayoutInflater來載入佈局的。如果大家對setContentView()內部實現機制比較清楚的話(如果不清楚,可參看 從原始碼角度剖析 setContentView() 背後的機制),一定知道Activity載入佈局也是使用LayoutInflater,因此可以認為Activity和Fragment載入佈局方式一致。
由於目前官方推薦使用AppCompatActivity代替Activity,當前企鵝FM專案已全部替換成了AppCompatActivity,因此在這裡的探究是基於AppCompatActivity來講解的。
圖7 AppCompatActivity從圖7可知,在AppCompatActivity::onCreate()中有一個AppCompat的代理類AppCompatDelegate(一個抽象類),其具體實現如下,對應著不同版本的具體實現類。
圖8 AppCompatDelegate實現類接著看一下delegate.installViewFactory 內部實現(專案中存在support v22 和 v23,兩者存在差異),
圖9 support v22 圖10 support v23從上可以看出v22 和v23 installViewFactory 實現存在微小差別,在使用Factory的時候要特別注意,這裡不展開,具體可參見 從原始碼角度深入理解LayoutInflater.Factory 。installViewFactory中主要進行的是setFactory操作,其中上面的this 指的就是 LayoutInflaterFactory ,那LayoutInflaterFactory 又是什麼了 ?
LayoutInflaterFactory 是一個介面 ,提供了一個耳熟能詳的方法onCreateView 如下:
圖11 LayoutInflaterFactoryonCreateView 這個回撥是在createViewFormTag進行的,熟悉setContentView的同學一定清楚,在 public View inflate(XmlPullParser parser,@Nullable ViewGroup root, booleanattachToRoot)中會通過createViewFromTag()方法來建立View。
圖12 createViewFromTag()到此大家應該明白了吧 ,View 替換是在inflate 的createViewFromTag() 進行的,不同版本的實現又是通過setFactory 來實現的。
由上文知在installViewFactory 中 使用的是AppCompatDelegateImplV7,那AppCompatDelegateImplV7::onCreateView() 實現又是怎樣的,如下圖所示:
在createView 裡面建立了AppCompatViewInflater
AppCompatViewInflater::createView沒錯系統就是在AppCompatViewInflater中將部分系統View 全部替換成了 AppCompatView ,而在AppCompatxxx中會將普通的Context wrap 成TintContextWrapper ,到此整個View的載入過程講完。
另外,這裡補充一下 LayoutInflaterFactory 介面用途(實際開發中很少會使用):
1)自定義的View,而不是讓系統去建立,避免反射過程,提高效能;
2)在xml使用自定義的View時,可以不宣告全限定名稱;
3)更換系統View為自己定義的View(Appcompat庫替換預設的系統View的方式)
作者:freddyyao
連結:https://www.jianshu.com/p/bc79e01da6b0
來源:簡書
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。