
前言
吸进这几天在看VIEw的尺寸是怎样计算出来的,于是看了整个VIEw被初始化的过程,结合系统源码总结了一下分享出来,方便需要的朋友或者自己以后有需要的时候看看,下面话不多说了,来看看详细的介绍吧。
从布局文件到LayoutParams
首先从Activity的setContentVIEw(int)方法开始,只要设置了R.layout的布局文件,那么界面上就会显示出来对应的内容。所以以这个方法为初发点,然后往后跟踪代码。
public voID setContentVIEw(@LayoutRes int layoutResID) { getwindow().setContentVIEw(layoutResID); initwindowDecorActionbar();}通过以上代码发现调用了Window类的setContentVIEw方法,那么这个Window对象mWindow又是怎么初始化的?在Activity中搜索发现是在Activity的attach方法中初始化的,构造了一个PhoneWindow对象。
如下代码所示:
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); // 这里创建了Window对象 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)方法中,发现是调用了LayoutInflater的inflate(int,VIEw)方法,对这个布局文件进行转换成VIEw,并添加到后面VIEw这个参数中。
@OverrIDepublic 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的根节点是怎样初始化出来的。
这里有一个关键的地方是这个installDecor()方法,在这个方法中通过调用generateDecor()方法创建了这个mDecor的对象,通过调用generateLayout(DecorVIEw)方法初始化出来了mContentParent对象。其中,mDecor是PhoneWindow.DecorVIEw的类实例,mContentParent是展示所有内容的,是通过com.androID.internal.R.ID.contentID来找到这个VIEw。
具体代码如下所示:
protected VIEwGroup generateLayout(DecorVIEw decor) { // ... VIEw in = mLayoutInflater.inflate(layoutResource,null); // layoutResource是根据对当前显示VIEw的Activity的theme属性值来决定由系统加载对应的布局文件 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;}那么在哪里面可以看到这个DecorVIEw呢?看下面图。
下面这张图是在deBUG模式下连接手机调试App,使用Layout Inspector工具查看得到的图:
PhoneWindow.DecorVIEw
其中从1位置可以看出,整个VIEw的根节点的VIEw是PhoneWindow.DecorVIEw实例;从2位置和3位置的mID可以推断出来,上面的mContentParent就是ContentFrameLayout类的实例;位置4中的蓝色区域是mContentParent所表示的位置和大小。
以上图是在AS 2.2.3版本上使用AndroID Monitor Tab页中的Layout Inspector工具(参考位置5)生成。
紧接着跟踪上面LayoutInflater中的inflate()方法中调用,发现最后调用到了inflate(@LayoutRes int resource,@Nullable VIEwGroup root,boolean attachToRoot)方法中,在这个方法中构造了XmlResourceParser对象,而这个parser对象构造代码如下所示:
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()方法调用到本地frameworks/base/core/jni/androID_util_AssetManager.cpp文件中的static jint androID_content_AssetManager_loadResourceValue函数,而在这个函数中java层的TypeValue的类对象value对象属性通过JNI形式被赋值。
在构建这个parser时,经历了很复杂的过程,从TypeValue中的assetcookie属性,到XmlBlock中的xmlBlock属性,再到XmlBlock.Parser中的mParseState属性,都是用来保存JNI层的数据,因为最终 *** 作这些资源数据是在native层,所以必不可少通过JNI这种方式,在java层和native层周旋。这里没有深入到native层去分析资源是如何被加载解析的,后面有机会再说。
最后跟到了这个public VIEw inflate(XmlPullParser parser,boolean attachToRoot)方法中,代码如下所示:
public VIEw inflate(XmlPullParser parser,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类对象 VIEwGroup.LayoutParams params = null; if (root != null) { // Create layout params that match root,if supplIEd params = root.generateLayoutParams(attrs); // 这里将尺寸转换成了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)方法,因为这里返回了params。查看代码最后发现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()方法发现,获取的值是通过index在mData这个成员数组中获取的。这个mData的值是在创建TypedArray对象时被赋的值,具体参见TypedArray的obtain方法。这个数组是在Resources的obtainStyledAttributes()方法中通过调用AssetManager.applyStyle()方法被初始化值的。applyStyle()方法是一个native方法,对应frameworks/base/core/jni/androID_util_AssetManager.cpp文件中的androID_content_AssetManager_applyStyle函数。在这个函数中发现,传入的Resrouces类中的mtheme成员以及XmlBlock.Parse类的mParseState成员都是一个C++对象的指针,在java类中以整型或长整型值保存。
至此,布局文件中的尺寸值已经被转换成了具体的int类型值。
从布局文件到VIEw
从上面的public VIEw inflate(XmlPullParser parser,boolean attachToRoot)方法中看到VIEw是通过这行代码final VIEw temp = createVIEwFromTag(root,attrs);创建出来的,而这个name就是XML文件在解析时遇到的标签名称,比如TextVIEw。此时的attrs也就是上面分析的XmlBlock.Parser的对象。最后发现VIEw是在createVIEwFromTag()方法中创建的,代码如下所示:
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,context,attrs); } else if (mFactory != null) { vIEw = mFactory.onCreateVIEw(name,attrs); } else { vIEw = null; } if (vIEw == null && mPrivateFactory != null) { vIEw = mPrivateFactory.onCreateVIEw(parent,attrs); } if (vIEw == null) { final Object lastContext = mConstructorArgs[0]; mConstructorArgs[0] = context; try { if (-1 == name.indexOf('.')) { vIEw = onCreateVIEw(parent,attrs); } else { vIEw = createVIEw(name,null,attrs); } } finally { mConstructorArgs[0] = lastContext; } } return vIEw; // ...}这里要注意一下,mConstructorArgs的第一个值是一个Context,而这个Context有可能已经不是当前Activity的Context。
看到这里,下面这样的代码片段是不是很熟悉?
if (name.equals("vIEw")) { name = attrs.getAttributeValue(null,"class");}上面Factory这个接口对象在LayoutInflater类中就有三个属性,分别对应:factory、factory2、mPrivateFactory。很明显,弄清了这三个对象,也就知道了VIEw的初始化流程。下面代码是对这三个属性的值的输出:
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如下所示:
// 当前Activiy继承的是androID.support.v7.app.AppCompatActivitythe same object: trueinflater 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@41fd9e70inflater1 mPrivateFactory: com.jacpy.sb.MainActivity@41fd9e70// 当前Activity继承的是androID.app.Activitythe same object: trueinflater factory: null,factory2: nullinflater1 factory: null,factory2: nullinflater mPrivateFactory: com.jacpy.sb.MainActivity@41fd9a28inflater1 mPrivateFactory: com.jacpy.sb.MainActivity@41fd9a28首先看到mPrivateFactory是当前的Activity实例,因为Activity也实现的Factory2接口。首先看LayoutInflater的创建过程,如下图所示:
而生成的PhoneLayoutInflater对象是缓存在ContextImpl类的属性SYstem_SERVICE_MAP中,所以通过Context.LAYOUT_INFLATER_SERVIC去取,始终是同一个对象,当然仅限于当前Context中。
mPrivateFactory属性的赋值是在Activity的attach()方法中,通过调用mWindow.getLayoutInflater().setPrivateFactory(this); ,因此调用Factory2的onCreateVIEw()方法时,实际是调用Activity中的onCreateVIEw()方法。而Activity中的onCreateVIEw()实际返回的是null,所以最后创建VIEw的是if判断中的onCreateVIEw(parent,attrs)方法,最后VIEw是在LayoutInflater类中的public final VIEw createVIEw(String name,String prefix,AttributeSet attrs) throws ClassNotFoundException,InflateException方法中创建:
public final VIEw createVIEw(String name,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; // 这个值是XmlBlock.Parser对象 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;}这里有没有发现mConstructorSignature数组的长度决定了调用了VIEw的哪个构造方法?
总结
好了,以上就是这篇文章的全部内容了,至此,VIEw已经创建成功。希望本文的内容对各位AndroID开发者们能带来一定的帮助,如果有疑问大家可以留言交流。
以上是内存溢出为你收集整理的深入解析Android中View创建的全过程全部内容,希望文章能够帮你解决深入解析Android中View创建的全过程所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)