热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

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

这篇文章主要给大家深入的解析了关于Android中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这个参数中。

@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的根节点是怎样初始化出来的。

这里有一个关键的地方是这个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
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类对象
 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, @Nullable ViewGroup root, boolean attachToRoot)方法中看到View是通过这行代码final View temp = createViewFromTag(root, name, inflaterContext, 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, 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的第一个值是一个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.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
// 当前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是当前的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, name, attrs)方法,最后View是在LayoutInflater类中的public final View createView(String name, String prefix, AttributeSet attrs) throws ClassNotFoundException, InflateException方法中创建:

public final View createView(String name, String prefix, AttributeSet attrs)
  throws ClassNotFoundException, InflateException {
 Constructor<&#63; extends View> cOnstructor= sConstructorMap.get(name);
 Class<&#63; 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 &#63; (prefix + name) : name).asSubclass(View.class); // prefix传的值是android.view.
 
 // ...
 
 cOnstructor= clazz.getConstructor(mConstructorSignature); // Class<&#63;>[] 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开发者们能带来一定的帮助,如果有疑问大家可以留言交流。


推荐阅读
  • Java EE 平台集成了多种服务、API 和协议,旨在支持基于 Web 的多层应用程序开发。本文将详细介绍 Java EE 中的 13 种关键技术规范,帮助开发者更好地理解和应用这些技术。 ... [详细]
  • Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面Android异步处理二:使用AsyncTask异步更新UI界面Android异步处理三:Handler+Loope ... [详细]
  • 短视频app源码,Android开发底部滑出菜单首先依赖三方库implementationandroidx.appcompat:appcompat:1.2.0im ... [详细]
  • 本文介绍了如何使用Postman构建和发送HTTP请求,包括四个主要部分:方法(Method)、URL、头部(Headers)和主体(Body)。特别强调了Body部分的重要性,并详细说明了不同类型的请求体。 ... [详细]
  • 本文主要介绍如何使用标签来优化Android应用的UI布局,通过减少不必要的视图层次,提高应用性能。 ... [详细]
  • 近年来,区块链技术备受关注,其中比特币(Bitcoin)功不可没。尽管数字货币的概念早在上个世纪就被提出,但直到比特币的诞生,这一概念才真正落地生根。本文将详细探讨比特币、以太坊和超级账本(Hyperledger)的核心技术和应用场景。 ... [详细]
  • java解析json转Map前段时间在做json报文处理的时候,写了一个针对不同格式json转map的处理工具方法,总结记录如下:1、单节点单层级、单节点多层级json转mapim ... [详细]
  • 可参照github代码:https:github.comrabbitmqrabbitmq-tutorialsblobmasterjavaEmitLogTopic.ja ... [详细]
  • 本文介绍了如何在Spring框架中使用AspectJ实现AOP编程,重点讲解了通过注解配置切面的方法,包括方法执行前和方法执行后的增强处理。阅读本文前,请确保已安装并配置好AspectJ。 ... [详细]
  • 在React中使用setState时遇到错误,本文将详细分析错误原因并提供解决方案。 ... [详细]
  • 本文讨论了在 Oracle 10gR2 和 Solaris 10 64-bit 环境下,从 XMLType 列中提取数据并插入到 VARCHAR2 列时遇到的性能问题,并提供了优化建议。 ... [详细]
  • 事件是程序各部分之间的一种通信方式,也是异步编程的一种实现形式。本文将详细介绍EventTarget接口及其相关方法,以及如何使用监听函数处理事件。 ... [详细]
  • 本文介绍了如何查看PHP网站及其源码的方法,包括环境搭建、本地测试、源码查看和在线查找等步骤。 ... [详细]
  • 本文通过古代物物交换的例子引出货币的诞生,进而探讨现代社会中虚拟货币的便利性,并将其类比为面向接口编程的核心思想。 ... [详细]
  • 本文介绍了如何使用Python爬取妙笔阁小说网仙侠系列中所有小说的信息,并将其保存为TXT和CSV格式。主要内容包括如何构造请求头以避免被网站封禁,以及如何利用XPath解析HTML并提取所需信息。 ... [详细]
author-avatar
鐘彦璋864175
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有