--视图布局的加载
--setContentView()
--LayoutInflater.inflate()是如何解析xml的?
--createViewFromTag() 创建View
------自定义View的创建
------系统View的创建
要分析View的创建过程,应该从视图布局的加载开始分析。
视图布局的加载
在开发中我们一般通过setContentView()加载Activity的布局,通过LayoutInflater.inflate()方法加载fragment、recyclerview里adapter加载item布局等等。
而setContentView()实际上使用的就是LayoutInflater.inflate()进行的布局加载,所以我们从setContentView()开始分析最好不过。
setContentView()
获取一个window实例,实际上是调用了PhoneWindow的setContentView()方法
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID); //重点
initWindowDecorActionBar();
}
public Window getWindow() {
return mWindow;
}
PhoneWindow的setContentView源码:
其实下边就可以看出实际上setContentView()是通过LayoutInflater.inflate()进行的布局加载。 也就是说,实际上加载xml布局的是LayoutInflater.inflate()方法。
@Override
public void setContentView(int layoutResID) {
//···忽略
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
//···忽略
} else {
//重点
// 调用LayoutInflater的inflate方法解析布局文件,并生成View树,
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets(); //mContentParent是View树的根节点
//回调Activity的onContentChanged方法通知视图发生改变
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
上面的mLayoutInflater是在PhoneWindow的构造方法中被实例的。
public PhoneWindow(Context context) {
super(context);
mLayoutInflater = LayoutInflater.from(context);
}
LayoutInflater.inflate()是如何解析xml的?
inflate()一共有三个重载方法,其中前两个实际上都是调用的第三个方法,在第三个方法中,通过上下文获取到Resource实例,再通过getLayout()方法传入layout的布局id获取到XmlResourceParser对象。 接着又调用了一个inflate()方法。
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {
return inflate(parser, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
//···省略
final XmlResourceParser parser = res.getLayout(resource); //重点
try {
return inflate(parser, root, attachToRoot); //重点
} finally {
parser.close();
}
}
LayoutInflater的实例实际上是通过getSystemService()创建的
深入到下一个inflate()方法中,首先遍历整个XML寻找merge标签,如果查到进行merge标签里的创建,如果没有则调用createViewFromTag()进行view的创建。 这段代码比较长,详细的信息写在注释里。
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
//···省略
try {
// 循环查找根节点
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
}
final String name = parser.getName();
//如果查找到是merge标签
//private static final String TAG_MERGE = "merge";
if (TAG_MERGE.equals(name)) {
//如果是merge标签,根节点不能为空attachToRoot不能为false,否则抛出异常
//因为merge标签需要依附在父布局里才能使用
if (root == null || !attachToRoot) {
throw new InflateException(" can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
//然后调用rInflate加载布局
rInflate(parser, root, inflaterContext, attrs, false);
//如果不是merge标签
} else {
//重点
//View是通过createViewFromTag()方法创建出来的
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
}
//···省略
} catch (XmlPullParserException e) {
//···省略
} finally {
//···省略
}
return result;
}
}
createViewFromTag() 创建View
我们知道了View是createViewFromTag() 创建的,那么看里边的实现,具体写在注释里。
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
//首先会使用mFactory2,mFactory,mPrivateFactory这三个对象按先后顺序创建view。
//如果这三个对象都为空的话,则会默认流程来创建View,最后返回View。
//通常来讲这三个Factory都为空,如果我们想要控制View的创建过程就可以利用这一机制来定制自己的factory。
try {
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);
}
//判断名字中是否有"." ,这主要是为了区分系统自带View和自定义View。
//因为系统View是直接使用类名不用写全包名的,而自定义View在使用的时候一定要写全包名
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
//如果是自定义View则调用createView来创建View,否则调用onCreateView方法。
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
}
}
从上面可以看出,mFactory2,mFactory其实都是可以hook的点,通过这里进行拦截,添加我们想要进行的操作。 而view的实际创建,系统进行判断,是自定义view还是系统自带view,通过是否有包名去判断。 我们再深入看 createView()方法。
自定义View的创建
从下面代码可以看出每个View都是通过反射进行创建的。
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
Constructor extends View> constructor = sConstructorMap.get(name);
if (constructor != null && !verifyClassLoader(constructor)) {
constructor = null;
sConstructorMap.remove(name);
}
Class extends View> clazz = null;
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
if (constructor == 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);
if (mFilter != null && clazz != null) {
boolean allowed = mFilter.onLoadClass(clazz);
if (!allowed) {
failNotAllowed(name, prefix, attrs);
}
}
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
sConstructorMap.put(name, constructor);
} else {
···
}
Object lastContext = mConstructorArgs[0];
if (mConstructorArgs[0] == null) {
// Fill in the context if not already within inflation.
mConstructorArgs[0] = mContext;
}
Object[] args = mConstructorArgs;
args[1] = attrs;
//view的创建
final View view = constructor.newInstance(args);
if (view instanceof ViewStub) {
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
mConstructorArgs[0] = lastContext;
return view;
} catch (NoSuchMethodException e) {
···
} finally {
···
}
}
系统View的创建
在LayoutInflater中我们找到了onCreateView()方法
protected View onCreateView(View parent, String name, AttributeSet attrs)
throws ClassNotFoundException {
return onCreateView(name, attrs);
}
protected View onCreateView(String name, AttributeSet attrs)
throws ClassNotFoundException {
return createView(name, "android.view.", attrs);
}
onCreateView方法创建系统View最终的实现也是交给了createView方法,只是传入了一个字符串android.view.,这样在创建构造器时就会与View的名字拼接到一起获取对应的Class对象,使最终能够成功创建对应的View。
在PhoneLayoutInflater里找到了createView()方法。
主要完成的是遍历一个存放了三个包名字符串的数组,然后调用createView方法创建View,只要这三次创建View有一次成功,那么就返回创建的View,否则最终返回的还是父类传入"android.view."时创建的View。
private static final String[] sClassPrefixList = {
"android.widget.",
"android.webkit.",
"android.app."
};
@Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
for (String prefix : sClassPrefixList) {
try {
//重点
View view = createView(name, prefix, attrs);
if (view != null) {
return view;
}
} catch (ClassNotFoundException e) {
}
}
return super.onCreateView(name, attrs);
}
createView的具体实现:
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
//从缓存器中获取构造器
Constructor extends View> constructor = sConstructorMap.get(name);
if (constructor != null && !verifyClassLoader(constructor)) {
constructor = null;
sConstructorMap.remove(name);
}
Class extends View> clazz = null;
try {
//没有缓存的构造器
if (constructor == null) {
//通过传入的prefix构造出完整的类名 并加载该类
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
//···省略
//从class对象中获取构造器
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
//存入缓存器中
sConstructorMap.put(name, constructor);
} else {
//···省略
}
//···省略
//这里可以看到,系统应用也是通过反射创建View
final View view = constructor.newInstance(args);
if (view instanceof ViewStub) {
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
mConstructorArgs[0] = lastContext;
return view;
}
}
结合前面的分析,我们可以清楚的看到view的创建过程。
经历了从 加载布局 — 到遍历xml各个节点 — 判断是否系统view — view的创建 的过程,这对于我们以后的开发大有帮助。