? ? ? ? ?本文主要以InputMethodManager內(nèi)存泄漏為引,來探究在不同系統(tǒng)版本中View是如何被加載的谭网,涉及以下幾個方面 :
(1)如何解決InputMethodManager內(nèi)存泄漏教藻;
(2)為何View.getContext() 是TintContextWrapper冠骄;
(3)不同系統(tǒng)版本中View是如何被加載的锁摔。
一损离、如何解決InputMethodManager內(nèi)存泄漏
? ? ? ? 在企鵝FM最新版本開發(fā)中,正好負責項目性能監(jiān)控(主要是ANR禁谦、內(nèi)存泄漏等等)這一塊胁黑,在內(nèi)存泄漏這塊發(fā)現(xiàn)有很多InputMethodManager泄漏的上報,引用鏈如下:
? ? ? ? 在很早之前就聽說過InputMethodManager存在泄漏問題州泊,查閱了一下相關資料丧蘸,大多數(shù)是InputMethodManger中mServedView存在泄漏,而非圖1中的mLastSrvView 遥皂,不太應該啊 ?力喷,難道是某個版本rom的特殊機型刽漂,對照相關郵件發(fā)現(xiàn)全部都是華為機型。
? ? ? ? ?項目中內(nèi)存泄漏這塊弟孟,使用的是MagnifierSDK(【SNGAPM】Magnifier SDK介紹)贝咙,其中ActivityLeakSolution中有專門解決InputMethodManager泄漏的解決方法,如下圖2所示:
從上述解決方法中可以看出拂募,主要針對的是mCurRootView庭猩、mServedView、mNextServedView陈症,為什么添加這些蔼水,本文在這里就不展開講解了。
? ? ? ? 后面在同事的提醒下發(fā)現(xiàn)項目中有專門針對華為機型進行處理的方法录肯,方法原理簡單粗暴徙缴,直接置空,破壞掉path to gc節(jié)點(同MagnifierSDK中ActivityLeakSolution::fixInputMethodManager處理方式一樣)嘁信。
? ? ? ? 既然同MagnifierSDK中ActivityLeakSolution::fixInputMethodManager處理方式一樣于样,為何沒生效了,回頭再去圖1中的引用鏈潘靖,從中發(fā)現(xiàn)了蛛絲馬跡穿剖,RadioSearchActivity 是TintContextWrapper 中的mBase 引用,而TintContextWrapper::mContext 又是被SearchView$SearchAutoComplete引用卦溢,由此猜想圖3中的view.getContext()==destContext條件可能失效糊余。后續(xù)驗證發(fā)現(xiàn)上圖中View.getContenxt ?確實是 TintContextWrapper 而非引用鏈中的RadioSearchActivity ,如下圖4所示:
二单寂、為何View.getContext()是TintContextWrapper
? ? ? ? 為何View.getContext()是TintContextWrapper贬芥,而不是RadioSearchActivity。帶著疑問去查看SearchView$SearchAutoComplete(這里的SearchView是使用support v7庫)為何物宣决,發(fā)現(xiàn)SearchAutoComplete直接繼承AppCompatAutoCompleteTextView蘸劈。
? ? ? 在AppCompatAutoCompleteTextView 的構造函數(shù)中,會將傳入的context(這里指的是RadioSearchActivity)wrap成TintContextWrapper尊沸,可以方便的實現(xiàn)Android Material Design 中的Tint威沫。
? ? ? ?現(xiàn)在雖然弄懂了這個案例中View.getContext()是TintContextWrapper的原因,那是不是所有的View都存在這樣的情況了洼专,在什么時候會轉化了棒掠,帶著這些疑問去探究一下View是如何加載的。
三屁商、不同系統(tǒng)版本中View是如何被加載的
? ? ? 總所周知烟很,Activity 加載布局時,調(diào)用的是activity的setContentView()方法來加載布局;而在Fragment中雾袱,是直接通過LayoutInflater來加載布局的恤筛。如果大家對setContentView()內(nèi)部實現(xiàn)機制比較清楚的話(如果不清楚,可參看 從源碼角度剖析 setContentView() 背后的機制)谜酒,一定知道Activity加載布局也是使用LayoutInflater,因此可以認為Activity和Fragment加載布局方式一致妻枕。
? ? ? ? 由于目前官方推薦使用AppCompatActivity代替Activity僻族,當前企鵝FM項目已全部替換成了AppCompatActivity,因此在這里的探究是基于AppCompatActivity來講解的屡谐。
從圖7可知述么,在AppCompatActivity::onCreate()中有一個AppCompat的代理類AppCompatDelegate(一個抽象類),其具體實現(xiàn)如下愕掏,對應著不同版本的具體實現(xiàn)類度秘。
接著看一下delegate.installViewFactory 內(nèi)部實現(xiàn)(項目中存在support v22 和 v23,兩者存在差異)饵撑,
從上可以看出v22 和v23 installViewFactory 實現(xiàn)存在微小差別剑梳,在使用Factory的時候要特別注意,這里不展開滑潘,具體可參見 從源碼角度深入理解LayoutInflater.Factory 垢乙。installViewFactory中主要進行的是setFactory操作,其中上面的this 指的就是 LayoutInflaterFactory 语卤,那LayoutInflaterFactory 又是什么了 追逮?
LayoutInflaterFactory 是一個接口 ,提供了一個耳熟能詳?shù)姆椒╫nCreateView 如下:
onCreateView 這個回調(diào)是在createViewFormTag進行的粹舵,熟悉setContentView的同學一定清楚钮孵,在 public View inflate(XmlPullParser parser,@Nullable ViewGroup root, booleanattachToRoot)中會通過createViewFromTag()方法來創(chuàng)建View。
? ? ? ?到此大家應該明白了吧 眼滤,View 替換是在inflate 的createViewFromTag() 進行的巴席,不同版本的實現(xiàn)又是通過setFactory 來實現(xiàn)的。
由上文知在installViewFactory 中 使用的是AppCompatDelegateImplV7诅需,那AppCompatDelegateImplV7::onCreateView() 實現(xiàn)又是怎樣的情妖,如下圖所示:
在createView 里面創(chuàng)建了AppCompatViewInflater
? ? ? ? 沒錯系統(tǒng)就是在AppCompatViewInflater中將部分系統(tǒng)View 全部替換成了 AppCompatView ,而在AppCompatxxx中會將普通的Context wrap 成TintContextWrapper ,到此整個View的加載過程講完诱担。
另外毡证,這里補充一下 LayoutInflaterFactory 接口用途(實際開發(fā)中很少會使用):
1)自定義的View,而不是讓系統(tǒng)去創(chuàng)建蔫仙,避免反射過程料睛,提高性能;
2)在xml使用自定義的View時,可以不聲明全限定名稱恤煞;
3)更換系統(tǒng)View為自己定義的View(Appcompat庫替換默認的系統(tǒng)View的方式)