今天这篇文章,分析探究布局文件如何加载;既LayoutInflater如何加载布局文件的过程。在分析此过程中,也解决自定义View中构造方法的调用,以及单例模式的应用。
单例模式获取layoutInflater
获取layoutInflater
LayoutInflater.from(context);
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;
}
我们发现其内部调用了context的getSystemService去获取的。接下里我们看一下context里面相关的实现。
context获取服务
context之前将activity添加view时,其的创建以及分析过,我们知道其实现是ContextImpl,接下来,我们直接查看给类。
- 服务获取
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
@Override
public String getSystemServiceName(Class<?> serviceClass) {
return SystemServiceRegistry.getSystemServiceName(serviceClass);
}
最终是通过SystemserviceRegistry去获取的,那SystemserviceRegistry是什么,其何时创建的?
2. SystemserviceRegistr
- 其内部有两个静态的hashMap进行服务名何fetch的存放
- 内部的静态代码块会注册服务
private static final HashMap<Class<?>, String> SYSTEM_SERVICE_NAMES =
new HashMap<Class<?>, String>();
private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
new HashMap<String, ServiceFetcher<?>>();
static {
registerService(Context.ACCESSIBILITY_SERVICE, AccessibilityManager.class,
new CachedServiceFetcher<AccessibilityManager>() {
@Override
public AccessibilityManager createService(ContextImpl ctx) {
return AccessibilityManager.getInstance(ctx);
}});
...
}
- 注册服务时,分别加入两个集合中
- 获取服务时,首先获取ServiceFetch,然后再利用Servicefetch获取具体的服务对象
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
public final T getService(ContextImpl ctx) {
synchronized (StaticServiceFetcher.this) {
if (mCachedInstance == null) {
try {
mCachedInstance = createService();
} catch (ServiceNotFoundException e) {
onServiceNotFound(e);
}
}
return mCachedInstance;
}
}
layoutInflater的创建
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
这样我们知道其实现类为PhoneLayoutInflater
- 其只是对onCreateView方法进行了重写,进行前缀的添加
private static final String[] sClassPrefixList = {
"android.widget.",
"android.webkit.",
"android.app."
};
- 这就是我们android中ui的所在包
总结
- 所以我们后期获取系统提供的服务都是通过从这个Map中去查询,保证了其唯一行,
- 而静态代码块,只会在第一次加载类是调用,保证后面不会重复创建
- 获取时,通过DCL,去获取对象,空的化,根据注册时,不同fetch对cratefetch的重写逻辑去创建,不为空就直接返回
- 给实现是容器单例模式的稍微变化了一下,容器里面去获取响应的类,在给类的方法在利用懒汉模式实现单例。我们时间开发中需要根据不同情况创建不同的对象,但还须保证唯一,可使用此中设计方法
LayoutInflater加载布局
这里我们直接从加载一个布局进行分析,既inflate(resource,root,boolean). 因为setContentView通过逐级调用,其内部也是调用的该方法.
//Phonewindow
public void setContentView(int layoutResID) {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
inlfate加载布局
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];
mConstructorArgs[0] = inflaterContext;
View result = root;
try {
// 查找根节点
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
...
//判断如果是Merge标签,将其里面的view,添加到根view中
if (TAG_MERGE.equals(name)) {
//对root进行校验
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// 依次查找root下的标签创建view
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
//如果设置了布局参数,创建布局参数,匹配rootview,
params = root.generateLayoutParams(attrs);
//根据是否要将布局中的view添加到根view的等级中,如果false的或,不添加上去,只是用来创建view,然后返回
if (!attachToRoot) {
temp.setLayoutParams(params);
}
}
//加载tempView下的子view
rInflateChildren(parser, temp, attrs, true);
将tempview添加到root上
if (root != null && attachToRoot) {
root.addView(temp, params);
}
// attachToRoot false,不添加到根上,返回view
if (root == null || !attachToRoot) {
result = temp;
}
}
}
return result;
}
}
根据上面的代码总结为下面几个步骤:
- 解析xml中的根标签
- 处理merge标签,如果是,将其里面的view,解析到根view。通过rInflate方法
- 普通标签,调用createViewFromTag方法创建view,
- 判断是否要添加到根view,设置布局参数
- 通过
rInflateChildren,加载其下面的子view。 - 最后根据是否attachRoot,返回root,或自己。
创建View(createViewFromTag发起)
这里我们可以找到view构造方法的调用,在布局中具体调用那个构造方法
createViewFromTag(...){
...
try {
View view
//我们知道创建的是PhoneLayout,而起调用的构造方法为super(context)
//所以默认factory都为空,我们可以通过setfactory方法设置
//这段代码可以忽略
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
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
//系统包下的view
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
//自定义view
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
}
通过分析该方法,我们知道了自定义view时,为什么要写完整的路径,,这是因为系统的View,其会自动添加,在三个包下去查找。
createView
这里最终会创建类,即会调用不同的构造方法
- 获取缓存中的构造方法
Constructor<? extends View> constructor = sConstructorMap.get(name);
- 如果有,且可用,
- 判断是否设置了过滤规则,如有进行响应的过滤,发现禁止加载,抛出异常。
if (constructor != null && !verifyClassLoader(constructor)) {
constructor = null;
sConstructorMap.remove(name);
}
if (mFilter != null) {
Boolean allowedState = mFilterMap.get(name);
if (allowedState == null) {
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
mFilterMap.put(name, allowed);
if (!allowed) {
failNotAllowed(name, prefix, attrs);
}
} else if (allowedState.equals(Boolean.FALSE)) {
failNotAllowed(name, prefix, attrs);
}
}
- 用缓存中的构造方法创建实例
final View view = constructor.newInstance(args);
接下来分析没有缓存,既第一次加载,
- 首先加载给类
- 然后也是判断过滤规则,这里不分析了
- 然后其调用getConstructor方法创建实例,该方法就是反射创建类,其会根据参数调用不同的方法,因此我们只要知道其参数是什么,就知道布局中的view实例调用那个构造方法了
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);
}
//构造传递的参数
static final Class<?>[] mConstructorSignature = new Class[] {
Context.class, AttributeSet.class};
总结
通过分析布局View的创建,我们知道其默认调用的是两个参数的方法。因此我们在自定义的view构造方法,需要重写,让其最终调用第四个方法。最全的那个方法。我们看系统源码也是。例如TextView
public TextView(Context context) {
this(context, null);
}
public TextView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.textViewStyle);
}
public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
reinlfate加载视图树
无论是merge标签下,还是加载子view最终都是调用该方法,因此我们可以分析该方法得到如何加载出视图树的。
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
//获取树的深度
final int depth = parser.getDepth();
int type;
boolean pendingRequestFocus = false;
//依次遍历每一个元素
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();
//处理设置了requestfocuse的标签,这个没用过
if (TAG_REQUEST_FOCUS.equals(name)) {
pendingRequestFocus = true;
consumeChildElements(parser);
} else if (TAG_TAG.equals(name)) { //处理tag标签
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) { //处理include标签
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) { //merge标签只能在根布局下使用
throw new InflateException("<merge /> must be the root element");
} else {
//更加特定的标签创建view
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
//递归调用加载子view
rInflateChildren(parser, view, attrs, true);
viewGroup.addView(view, params);
}
}
总结
到这里,我们知道merge只能在根视图下使用,视图树的加载是深度优先递归调用
结束语
layoutInflater加载就分析完了,下一篇研究一下AsyncLayoutInflater,看一下这个如何使用,可以优化我们的应用启动。