我们都知道 MVC,在Android中,这个 V 即指View,那我们今天就来探探View的究竟。

在onCreate方法中,可以调用this.setContentView(layout_id),来设置这个Activity的视图,今天就从setContentView(...)说起吧。

先编写一个简单的Activity:

public class ViewDemoActivity extends Activity {

    @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.setContentView(R.layout.view_demo);
}
}

查看父类Activity的setContentView方法:

 public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
...
}

这个getWindow()指向哪里?我们在onCreate中打个log瞧瞧:

D/ViewDemoActivity(3969): com.android.internal.policy.impl.PhoneWindow

原来是PhoneWindow,来看看它的setContentView方法:

    @Override
public void setContentView(int layoutResID) {
// 1
if (mContentParent == null) {
installDecor();
} else {
mContentParent.removeAllViews();
} // 2
mLayoutInflater.inflate(layoutResID, mContentParent); // 3
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}

我们分三步来介绍这个方法:

第一步:判断父容器是否为空

为空:生成Decor; (Decor是什么?参看这篇文章)
不为空:删除 contentParent 所有的子控件。

第二步:解析layoutResID所代表的xml文件

直接看LayoutInflater.inflate(...)方法:

    // 为节省篇幅,删除一些调试代码
public View inflate(int resource, ViewGroup root) {
return inflate(resource, root, root != null);
} public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
XmlResourceParser parser = getContext().getResources().getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
} public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context)mConstructorArgs[0];
mConstructorArgs[0] = mContext;
View result = root; try {
// Look for the root node.
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
} if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription() + ": No start tag found!");
}
final String name = parser.getName(); // 根标签为merge时,root不能为空
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
} rInflate(parser, root, attrs, false);
} else {
// Temp is the root view that was found in the xml
View temp;
if (TAG_1995.equals(name)) {
temp = new BlinkLayout(mContext, attrs);
} else {
temp = createViewFromTag(root, name, attrs);
} ViewGroup.LayoutParams params = null; if (root != null) {
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not attaching.
// (If we are, we use addView, below)
temp.setLayoutParams(params);
}
} // 解析temp的所有子View
rInflate(parser, temp, attrs, true); // 将temp添加到root中
if (root != null && attachToRoot) {
root.addView(temp, params);
} // 如果未指定root或者不附加到root,则返回xml所代表的view;
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException e) {
InflateException ex = new InflateException(e.getMessage());
ex.initCause(e);
throw ex;
} catch (IOException e) {
InflateException ex = new InflateException(parser.getPositionDescription()+ ": " + e.getMessage());
ex.initCause(e);
throw ex;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
}
return result;
}
}

(这几个inflate方法内部的逻辑,请参看注释)
我们注意到,inflate(...)最终会调用rInflate(...),继续挖:

    void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
final int depth = parser.getDepth();
int type; while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { if (type != XmlPullParser.START_TAG) {
continue;
} final String name = parser.getName(); if (TAG_REQUEST_FOCUS.equals(name)) {
parseRequestFocus(parser, parent);
} else if (TAG_INCLUDE.equals(name)) {
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
throw new InflateException("<merge /> must be the root element");
} else if (TAG_1995.equals(name)) {
final View view = new BlinkLayout(mContext, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflate(parser, view, attrs, true);
viewGroup.addView(view, params);
} else {
final View view = createViewFromTag(parent, name, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflate(parser, view, attrs, true);
viewGroup.addView(view, params);
}
} if (finishInflate) parent.onFinishInflate();
}

rInflate(...)先特殊处理了几个xml标签

    TAG_MERGE = "merge";    TAG_INCLUDE = "include";    TAG_1995 = "blink";    TAG_REQUEST_FOCUS = "requestFocus";

然后在else分支,会 递归 调用rInflate(...),将xml的子控件添加到parent中,生成完整的 contentView
看了这里,同学们就会明白,为什么不建议在布局文件中做过多地View嵌套了吧,层层递归啊:(

第三步:通知Callback,ContentView发生改变

这个getCallback()是什么?打个Log看看:

    @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.setContentView(R.layout.view_demo);
Log.d("ViewDemo", getWindow().getCallback().toString());
}

输出结果为:

D/ViewDemoActivity( 7228): cn.erhu.android.view.ViewDemoActivity@6562f2c0

这不就是ViewDemoActivity吗?
在Activity.java中,我们在attach()方法中发现了蛛丝马迹:

    final void attach(...) {
...
mWindow = PolicyManager.makeNewWindow(this);
mWindow.setCallback(this);
...
}
public void onContentChanged() {
}

我们发现原来PhoneWindow的callback原来就是Activity,并且Activity的onContentChanged()方法是空的,所以我们可以在自己的Activity中重写这个方法,来监听ContentView发生改变的事件。

OK,setContentView()大体就是这么回事,下面再补充几个知识点。


补充知识点

1、PhoneWindows的mLayoutInflater是哪里来的?

PhoneWindow.java

public PhoneWindow(Context context) {
super(context);
mLayoutInflater = LayoutInflater.from(context);
}

LayoutInflater.java

public static LayoutInflater from(Context context) {
LayoutInflater LayoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}

ContextImpl.java

@Override
public Object getSystemService(String name) {
ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
return fetcher == null ? null : fetcher.getService(this);
}
private static void registerService(String serviceName, ServiceFetcher fetcher) {
if (!(fetcher instanceof StaticServiceFetcher)) {
fetcher.mContextCacheIndex = sNextPerContextServiceCacheIndex++;
}
SYSTEM_SERVICE_MAP.put(serviceName, fetcher);
}
static {
...
registerService(LAYOUT_INFLATER_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
return PolicyManager.makeNewLayoutInflater(ctx.getOuterContext());
}});
...
}

原来,mLayoutInflater是ContextImpl.java在加载的时候,调用PolicyManager.makeNewLayoutInflater生成的

PoliceManager.java

    private static final String POLICY_IMPL_CLASS_NAME = "com.android.internal.policy.impl.Policy";
private static final IPolicy sPolicy; static {
// Pull in the actual implementation of the policy at run-time
try {
Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
sPolicy = (IPolicy)policyClass.newInstance();
...
}
public static LayoutInflater makeNewLayoutInflater(Context context) {
return sPolicy.makeNewLayoutInflater(context);
}

最终定位到Policy.java

    public LayoutInflater makeNewLayoutInflater(Context context) {
return new PhoneLayoutInflater(context);
}

mLayoutInflater是一个PhoneLayoutInflater,这便是mInflaterLayout的由来。

android开发:深入理解View(一):从setContentView谈起的更多相关文章

  1. 深入Android开发之--理解View#onTouchEvent

    一:前言 View是Android中最基本的UI单元. 当一个View接收到了触碰事件时,会调用其onTouchEvent方法.方法声明如下: ? 1 2 3 4 5 6 7 /**  * Imple ...

  2. Android开发进阶——自定义View的使用及其原理探索

    在Android开发中,系统提供给我们的UI控件是有限的,当我们需要使用一些特殊的控件的时候,只靠系统提供的控件,可能无法达到我们想要的效果,这时,就需要我们自定义一些控件,来完成我们想要的效果了.下 ...

  3. android开发架构理解

    1. android 开发和普通的PC程序开发的,我觉得还是不要过度设计,因为手机开发,项目相对传统软件开发就小很多,而且手机的性能有限,过度设计代码mapping需要消耗的能相对就高,而且手机开发的 ...

  4. Android 自定义View修炼-Android开发之自定义View开发及实例详解

    在开发Android应用的过程中,难免需要自定义View,其实自定义View不难,只要了解原理,实现起来就没有那么难. 其主要原理就是继承View,重写构造方法.onDraw,(onMeasure)等 ...

  5. Android 开发中的View事件监听机制

    在开发过程中,我们常常根据实际的需要绘制自己的应用组件,那么定制自己的监听事件,及相应的处理方法是必要的.我们都知道Android中,事件的监听是基于回调机制的,比如常用的OnClick事件,你了解它 ...

  6. android开发学习 ------- 自定义View 圆 ,其点击事件 及 确定当前view的层级关系

    我需要实现下面的效果:   参考文章:https://blog.csdn.net/halaoda/article/details/78177069 涉及的View事件分发机制 https://www. ...

  7. android开发之自定义View 详解 资料整理 小冰原创整理,原创作品。

    2019独角兽企业重金招聘Python工程师标准>>> /** * 作者:David Zheng on 2015/11/7 15:38 * * 网站:http://www.93sec ...

  8. android开发_view和view属性

    一.view视图的宽度和高度属性,属性值:固定和浮动两种状态 1属性为固定值 <View android:layout_width="30dp" android:layout ...

  9. Android 开发 深入理解Handler、Looper、Messagequeue 转载

    转载请注明出处:http://blog.csdn.net/vnanyesheshou/article/details/73484527 本文已授权微信公众号 fanfan程序媛 独家发布 扫一扫文章底 ...

  10. Android 开发 -------- 自己定义View 画 五子棋

    自己定义View  实现 五子棋 配图: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbG92ZV9KYXZjX3lvdQ==/font/5a6L5L2T ...

随机推荐

  1. c++ 接口和抽象类

    其实对抽象类与接口的区别一直是搞不太清楚,最近正在学习<设计模式>,期间用到了很多c++多态的知识.这是才恍然发现,是应该整理下这方面的知识了.但在翻阅书本.上网查阅资料之际,发现有篇文章 ...

  2. mysql_config 问题

    1 .you should have mysql_config available in $PATH For CentOS: yum install mysql-devel For openSUSE: ...

  3. inline-block和float

    Inline-block: 1.使块元素在一行显示 2.使内联支持宽高 3.换行被解析 4.不设置宽度,宽度由内容撑开 5.在IE6/7下不支持块标签 Float: 1.使块元素在一行显示 2.使内联 ...

  4. MySQL 磁盘I/O问题

    一.使用磁盘阵列:RAID,廉价磁盘冗余阵列,可靠性,性能好. 二.使用 Symbolic Links 分布I/O 利用操作系统的符号链接将不同的数据库或表.索引指向不同的物理磁盘,达到分布磁盘I/O ...

  5. Kali 开机报错解决方案

    问题一: piix4_smbus ::007.3: Host SMBus controller not enabled 解决:打开 /etc/modprobe.d/blacklist.conf 末尾加 ...

  6. 解决mysql Table ‘xxx’ is marked as crashed and should be repaired的问题。

    解决mysql Table 'xxx' is marked as crashed and should be repaired的问题. 某个表在进行数据插入和更新时突然出现Table 'xxx' is ...

  7. iOS SpriteKit 问题

    今天偶然发现 向SKShapeNode添加子 node时,子node参考的是 SKShapeNode的parent的坐标系,但是如果使用SKSpriteNode却是使用自己的坐标系,带研究.而且sha ...

  8. Angular2 组件通信

    1. 组件通信 我们知道Angular2应用程序实际上是有很多父子组价组成的组件树,因此,了解组件之间如何通信,特别是父子组件之间,对编写Angular2应用程序具有十分重要的意义,通常来讲,组件之间 ...

  9. linux系统root用户忘记密码的重置方法

    如果不小心忘记了新安装的lCentOS7的root密码,现在将找回过程分享给大家. 1.首先,在启动grub菜单,选择编辑选项启动: 2.然后,按e 进入编辑模式: 3.将'linux 16'行'ro ...

  10. 查看sqlserver被锁的表以及如何解锁

    select request_session_id spid,OBJECT_NAME(resource_associated_entity_id) tableName from sys.dm_tran ...