深入解析Android中View創(chuàng)建的全過程
前言
吸進(jìn)這幾天在看View的尺寸是怎樣計(jì)算出來的,于是看了整個(gè)View被初始化的過程,結(jié)合系統(tǒng)源碼總結(jié)了一下分享出來,方便需要的朋友或者自己以后有需要的時(shí)候看看,下面話不多說了,來看看詳細(xì)的介紹吧。
從布局文件到LayoutParams
首先從Activity的setContentView(int)
方法開始,只要設(shè)置了R.layout的布局文件,那么界面上就會(huì)顯示出來對(duì)應(yīng)的內(nèi)容。所以以這個(gè)方法為初發(fā)點(diǎn),然后往后跟蹤代碼。
public void setContentView(@LayoutRes int layoutResID) { getWindow().setContentView(layoutResID); initWindowDecorActionBar(); }
通過以上代碼發(fā)現(xiàn)調(diào)用了Window類的setContentView方法,那么這個(gè)Window對(duì)象mWindow又是怎么初始化的?在Activity中搜索發(fā)現(xiàn)是在Activity的attach方法中初始化的,構(gòu)造了一個(gè)PhoneWindow對(duì)象。
如下代碼所示:
final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config, String referrer, IVoiceInteractor voiceInteractor) { attachBaseContext(context); mFragments.attachHost(null /*parent*/); mWindow = new PhoneWindow(this); // 這里創(chuàng)建了Window對(duì)象 mWindow.setCallback(this); mWindow.setOnWindowDismissedCallback(this); mWindow.getLayoutInflater().setPrivateFactory(this); // ... 中間部分代碼省略 mWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); if (mParent != null) { mWindow.setContainer(mParent.getWindow()); } mWindowManager = mWindow.getWindowManager(); mCurrentConfig = config; }
在PhoneWindow的setContentView(int)
方法中,發(fā)現(xiàn)是調(diào)用了LayoutInflater的inflate(int, View)
方法,對(duì)這個(gè)布局文件進(jìn)行轉(zhuǎn)換成View,并添加到后面View這個(gè)參數(shù)中。
@Override public void setContentView(int layoutResID) { // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window // decor, when theme attributes and the like are crystalized. Do not check the feature // before this happens. if (mContentParent == null) { installDecor(); } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); } if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { // ... } else { mLayoutInflater.inflate(layoutResID, mContentParent); } // ... }
這里面順帶穿插說一下View的根節(jié)點(diǎn)是怎樣初始化出來的。
這里有一個(gè)關(guān)鍵的地方是這個(gè)installDecor()
方法,在這個(gè)方法中通過調(diào)用generateDecor()
方法創(chuàng)建了這個(gè)mDecor的對(duì)象,通過調(diào)用generateLayout(DecorView)
方法初始化出來了mContentParent對(duì)象。其中,mDecor是PhoneWindow.DecorView
的類實(shí)例,mContentParent是展示所有內(nèi)容的,是通過com.android.internal.R.id.contentID
來找到這個(gè)View。
具體代碼如下所示:
protected ViewGroup generateLayout(DecorView decor) { // ... View in = mLayoutInflater.inflate(layoutResource, null); // layoutResource是根據(jù)對(duì)當(dāng)前顯示View的Activity的theme屬性值來決定由系統(tǒng)加載對(duì)應(yīng)的布局文件 decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); mContentRoot = (ViewGroup) in; ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); if (contentParent == null) { throw new RuntimeException("Window couldn't find content container view"); } // ... return contentParent; }
那么在哪里面可以看到這個(gè)DecorView呢?看下面圖。
下面這張圖是在debug模式下連接手機(jī)調(diào)試App,使用Layout Inspector工具查看得到的圖:
PhoneWindow.DecorView
其中從1位置可以看出,整個(gè)View的根節(jié)點(diǎn)的View是PhoneWindow.DecorView
實(shí)例;從2位置和3位置的mId可以推斷出來,上面的mContentParent就是ContentFrameLayout類的實(shí)例;位置4中的藍(lán)色區(qū)域是mContentParent所表示的位置和大小。
以上圖是在AS 2.2.3版本上使用Android Monitor Tab頁中的Layout Inspector工具(參考位置5)生成。
緊接著跟蹤上面LayoutInflater中的inflate()
方法中調(diào)用,發(fā)現(xiàn)最后調(diào)用到了
inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
方法中,在這個(gè)方法中構(gòu)造了XmlResourceParser對(duì)象,而這個(gè)parser對(duì)象構(gòu)造代碼如下所示:
XmlResourceParser loadXmlResourceParser(int id, String type) throws NotFoundException { synchronized (mAccessLock) { TypedValue value = mTmpValue; if (value == null) { mTmpValue = value = new TypedValue(); } getValue(id, value, true); if (value.type == TypedValue.TYPE_STRING) { return loadXmlResourceParser(value.string.toString(), id, value.assetCookie, type); } throw new NotFoundException( "Resource ID #0x" + Integer.toHexString(id) + " type #0x" + Integer.toHexString(value.type) + " is not valid"); } } XmlResourceParser loadXmlResourceParser(String file, int id, int assetCookie, String type) throws NotFoundException { if (id != 0) { // ... // These may be compiled... synchronized (mCachedXmlBlockIds) { // First see if this block is in our cache. final int num = mCachedXmlBlockIds.length; for (int i=0; i<num; i++) { if (mCachedXmlBlockIds[i] == id) { //System.out.println("**** REUSING XML BLOCK! id=" // + id + ", index=" + i); return mCachedXmlBlocks[i].newParser(); } } // Not in the cache, create a new block and put it at // the next slot in the cache. XmlBlock block = mAssets.openXmlBlockAsset( assetCookie, file); if (block != null) { int pos = mLastCachedXmlBlockIndex+1; if (pos >= num) pos = 0; mLastCachedXmlBlockIndex = pos; XmlBlock oldBlock = mCachedXmlBlocks[pos]; if (oldBlock != null) { oldBlock.close(); } mCachedXmlBlockIds[pos] = id; mCachedXmlBlocks[pos] = block; //System.out.println("**** CACHING NEW XML BLOCK! id=" // + id + ", index=" + pos); return block.newParser(); } } // ... } // ... }
其中getValue()
方法調(diào)用到本地frameworks/base/core/jni/android_util_AssetManager.cpp文件中的static jint android_content_AssetManager_loadResourceValue
函數(shù),而在這個(gè)函數(shù)中java層的TypeValue的類對(duì)象value對(duì)象屬性通過JNI形式被賦值。
在構(gòu)建這個(gè)parser時(shí),經(jīng)歷了很復(fù)雜的過程,從TypeValue中的assetCookie屬性,到XmlBlock中的xmlBlock屬性,再到XmlBlock.Parser中的mParseState屬性,都是用來保存JNI層的數(shù)據(jù),因?yàn)樽罱K操作這些資源數(shù)據(jù)是在native層,所以必不可少通過JNI這種方式,在java層和native層周旋。這里沒有深入到native層去分析資源是如何被加載解析的,后面有機(jī)會(huì)再說。
最后跟到了這個(gè)public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)
方法中,代碼如下所示:
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { final Context inflaterContext = mContext; final AttributeSet attrs = Xml.asAttributeSet(parser); Context lastContext = (Context) mConstructorArgs[0]; View result = root; // ... // Temp is the root view that was found in the xml final View temp = createViewFromTag(root, name, inflaterContext, attrs); // 這里將布局文件中的名稱反射成具體的View類對(duì)象 ViewGroup.LayoutParams params = null; if (root != null) { // Create layout params that match root, if supplied params = root.generateLayoutParams(attrs); // 這里將尺寸轉(zhuǎn)換成了LayoutParams if (!attachToRoot) { // Set the layout params for temp if we are not // attaching. (If we are, we use addView, below) temp.setLayoutParams(params); } } // Inflate all children under temp against its context. rInflateChildren(parser, temp, attrs, true); // We are supposed to attach all the views we found (int temp) // to root. Do that now. if (root != null && attachToRoot) { root.addView(temp, params); // 將布局文件中根的View添加到mContentParent中 } // ... return result; }
接著看View的generateLayoutParams(AttributeSet)
方法,因?yàn)檫@里返回了params。查看代碼最后發(fā)現(xiàn)LayoutParams的width和height屬性賦值的代碼如下所示:
public LayoutParams(Context c, AttributeSet attrs) { TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout); setBaseAttributes(a, R.styleable.ViewGroup_Layout_layout_width, R.styleable.ViewGroup_Layout_layout_height); a.recycle(); } protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) { width = a.getLayoutDimension(widthAttr, "layout_width"); height = a.getLayoutDimension(heightAttr, "layout_height"); }
通過查看TypedArray類中的getLayoutDimension()
方法發(fā)現(xiàn),獲取的值是通過index在mData這個(gè)成員數(shù)組中獲取的。這個(gè)mData的值是在創(chuàng)建TypedArray對(duì)象時(shí)被賦的值,具體參見TypedArray的obtain方法。這個(gè)數(shù)組是在Resources的obtainStyledAttributes()
方法中通過調(diào)用AssetManager.applyStyle()
方法被初始化值的。applyStyle()
方法是一個(gè)native方法,對(duì)應(yīng)frameworks/base/core/jni/android_util_AssetManager.cpp文件中的android_content_AssetManager_applyStyle
函數(shù)。在這個(gè)函數(shù)中發(fā)現(xiàn),傳入的Resrouces類中的mTheme成員以及XmlBlock.Parse
類的mParseState成員都是一個(gè)C++對(duì)象的指針,在java類中以整型或長(zhǎng)整型值保存。
至此,布局文件中的尺寸值已經(jīng)被轉(zhuǎn)換成了具體的int類型值。
從布局文件到View
從上面的public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)
方法中看到View是通過這行代碼final View temp = createViewFromTag(root, name, inflaterContext, attrs);
創(chuàng)建出來的,而這個(gè)name就是XML文件在解析時(shí)遇到的標(biāo)簽名稱,比如TextView。此時(shí)的attrs也就是上面分析的XmlBlock.Parser
的對(duì)象。最后發(fā)現(xiàn)View是在createViewFromTag()
方法中創(chuàng)建的,代碼如下所示:
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) { if (name.equals("view")) { name = attrs.getAttributeValue(null, "class"); } // Apply a theme wrapper, if allowed and one is specified. if (!ignoreThemeAttr) { final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME); final int themeResId = ta.getResourceId(0, 0); if (themeResId != 0) { context = new ContextThemeWrapper(context, themeResId); } ta.recycle(); } // ... View view; if (mFactory2 != null) { view = mFactory2.onCreateView(parent, name, context, attrs); } else if (mFactory != null) { view = mFactory.onCreateView(name, context, attrs); } else { view = null; } if (view == null && mPrivateFactory != null) { view = mPrivateFactory.onCreateView(parent, name, context, attrs); } if (view == null) { final Object lastContext = mConstructorArgs[0]; mConstructorArgs[0] = context; try { if (-1 == name.indexOf('.')) { view = onCreateView(parent, name, attrs); } else { view = createView(name, null, attrs); } } finally { mConstructorArgs[0] = lastContext; } } return view; // ... }
這里要注意一下,mConstructorArgs的第一個(gè)值是一個(gè)Context,而這個(gè)Context有可能已經(jīng)不是當(dāng)前Activity的Context。
看到這里,下面這樣的代碼片段是不是很熟悉?
if (name.equals("view")) { name = attrs.getAttributeValue(null, "class"); }
上面Factory這個(gè)接口對(duì)象在LayoutInflater類中就有三個(gè)屬性,分別對(duì)應(yīng):factory、factory2、mPrivateFactory。很明顯,弄清了這三個(gè)對(duì)象,也就知道了View的初始化流程。下面代碼是對(duì)這三個(gè)屬性的值的輸出:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); LayoutInflater inflater = getLayoutInflater(); LayoutInflater inflater1 = LayoutInflater.from(this); Field f = null; try { f = LayoutInflater.class.getDeclaredField("mPrivateFactory"); f.setAccessible(true); } catch (NoSuchFieldException e) { e.printStackTrace(); } Log.d("may", "the same object: " + (inflater == inflater1)); Log.d("may", "inflater factory: " + inflater.getFactory() + ", factory2: " + inflater.getFactory2()); Log.d("may", "inflater1 factory: " + inflater1.getFactory() + ", factory2: " + inflater1.getFactory2()); if (f != null) { try { Log.d("may", "inflater mPrivateFactory: " + f.get(inflater)); Log.d("may", "inflater1 mPrivateFactory: " + f.get(inflater1)); } catch (IllegalAccessException e) { e.printStackTrace(); } } } }
輸出的LOG如下所示:
// 當(dāng)前Activiy繼承的是android.support.v7.app.AppCompatActivity the same object: true inflater factory: android.support.v4.view.LayoutInflaterCompatHC$FactoryWrapperHC{android.support.v7.app.AppCompatDelegateImplV14@41fdf0b0}, factory2: android.support.v4.view.LayoutInflaterCompatHC$FactoryWrapperHC{android.support.v7.app.AppCompatDelegateImplV14@41fdf0b0} inflater1 factory: android.support.v4.view.LayoutInflaterCompatHC$FactoryWrapperHC{android.support.v7.app.AppCompatDelegateImplV14@41fdf0b0}, factory2: android.support.v4.view.LayoutInflaterCompatHC$FactoryWrapperHC{android.support.v7.app.AppCompatDelegateImplV14@41fdf0b0} inflater mPrivateFactory: com.jacpy.sb.MainActivity@41fd9e70 inflater1 mPrivateFactory: com.jacpy.sb.MainActivity@41fd9e70 // 當(dāng)前Activity繼承的是android.app.Activity the same object: true inflater factory: null, factory2: null inflater1 factory: null, factory2: null inflater mPrivateFactory: com.jacpy.sb.MainActivity@41fd9a28 inflater1 mPrivateFactory: com.jacpy.sb.MainActivity@41fd9a28
首先看到mPrivateFactory是當(dāng)前的Activity實(shí)例,因?yàn)锳ctivity也實(shí)現(xiàn)的Factory2接口。首先看LayoutInflater的創(chuàng)建過程,如下圖所示:
LayoutInflater初始化流程
而生成的PhoneLayoutInflater對(duì)象是緩存在ContextImpl類的屬性SYSTEM_SERVICE_MAP中,所以通過Context.LAYOUT_INFLATER_SERVIC去取,始終是同一個(gè)對(duì)象,當(dāng)然僅限于當(dāng)前Context中。
mPrivateFactory屬性的賦值是在Activity的attach()方法中,通過調(diào)用mWindow.getLayoutInflater().setPrivateFactory(this);
,因此調(diào)用Factory2的onCreateView()
方法時(shí),實(shí)際是調(diào)用Activity中的onCreateView()
方法。而Activity中的onCreateView()
實(shí)際返回的是null,所以最后創(chuàng)建View的是if判斷中的onCreateView(parent, name, attrs)
方法,最后View是在LayoutInflater類中的public final View createView(String name, String prefix, AttributeSet attrs) throws ClassNotFoundException, InflateException
方法中創(chuàng)建:
public final View createView(String name, String prefix, AttributeSet attrs) throws ClassNotFoundException, InflateException { Constructor<? extends View> constructor = sConstructorMap.get(name); Class<? extends View> clazz = null; // ... // Class not found in the cache, see if it's real, and try to add it clazz = mContext.getClassLoader().loadClass( prefix != null ? (prefix + name) : name).asSubclass(View.class); // prefix傳的值是android.view. // ... constructor = clazz.getConstructor(mConstructorSignature); // Class<?>[] mConstructorSignature = new Class[] { Context.class, AttributeSet.class}; constructor.setAccessible(true); sConstructorMap.put(name, constructor); // ... Object[] args = mConstructorArgs; args[1] = attrs; // 這個(gè)值是XmlBlock.Parser對(duì)象 final View view = constructor.newInstance(args); if (view instanceof ViewStub) { // Use the same context when inflating ViewStub later. final ViewStub viewStub = (ViewStub) view; viewStub.setLayoutInflater(cloneInContext((Context) args[0])); } return view; }
這里有沒有發(fā)現(xiàn)mConstructorSignature數(shù)組的長(zhǎng)度決定了調(diào)用了View的哪個(gè)構(gòu)造方法?
總結(jié)
好了,以上就是這篇文章的全部?jī)?nèi)容了,至此,View已經(jīng)創(chuàng)建成功。希望本文的內(nèi)容對(duì)各位Android開發(fā)者們能帶來一定的幫助,如果有疑問大家可以留言交流。
相關(guān)文章
Android開發(fā)之CheckBox的簡(jiǎn)單使用與監(jiān)聽功能示例
這篇文章主要介紹了Android開發(fā)之CheckBox的簡(jiǎn)單使用與監(jiān)聽功能,結(jié)合簡(jiǎn)單實(shí)例形式分析了Android使用CheckBox控件的布局與功能實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-07-07Android Studio創(chuàng)建AIDL文件并實(shí)現(xiàn)進(jìn)程間通訊實(shí)例
本篇文章主要介紹了Android Studio創(chuàng)建AIDL文件并實(shí)現(xiàn)進(jìn)程間通訊實(shí)例,具有一定的參考價(jià)值,有興趣可以了解一下。2017-04-04android實(shí)現(xiàn)session保持簡(jiǎn)要概述及實(shí)現(xiàn)
其實(shí)sesion在瀏覽器和web服務(wù)器直接是通過一個(gè)叫做name為sessionid的cookie來傳遞的,所以只要在每次數(shù)據(jù)請(qǐng)求時(shí)保持sessionid是同一個(gè)不變就可以用到web的session了,感興趣的你可以參考下本文或許對(duì)你有所幫助2013-03-03使用RoundedBitmapDrawable生成圓角圖片的方法
由于RoundedBitmapDrawable類沒有直接提供生成圓形圖片的方法,所以生成圓形圖片首先需要對(duì)原始圖片進(jìn)行裁剪,將圖片裁剪成正方形,最后再生成圓形圖片,具體實(shí)現(xiàn)方法,可以參考下本文2016-09-09CDC與BG-CDC的含義電容觸控學(xué)習(xí)整理
今天小編就為大家分享一篇關(guān)于CDC與BG-CDC的含義電容觸控學(xué)習(xí)整理,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2018-12-12Android ProgressDialog的實(shí)例詳解
這篇文章主要介紹了Android ProgressDialog的實(shí)例詳解的相關(guān)資料,Android 開發(fā)項(xiàng)目的時(shí)候經(jīng)常會(huì)遇到耗時(shí)的操作,這里就講下Android ProgressDialog的應(yīng)用,需要的朋友可以參考下2017-07-07