深入解析Android中View创建的全过程

深入解析Android中View创建的全过程,第1张

概述前言吸进这几天在看View的尺寸是怎样计算出来的,于是看了整个View被初始化的过程,结合系统源码总结了一下分享出来,方便需要的朋友或者自己以后有需要的时候看看,下面话不多说了,来看看详细的介绍吧。

前言

吸进这几天在看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的创建过程,如下图所示:

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创建的全过程所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/web/1146878.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-05-31
下一篇2022-05-31

发表评论

登录后才能评论

评论列表(0条)

    保存