LayoutInflater通过inflate方法组装一个指定layout的布局View
<pre>
public void inflate(int resource, ViewGroup root, boolean attachToRoot) {
final Resource res = getContext().getResources();
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
</pre>
整个方法可以分为两个流程分析,第一个部分是由res.getLayout(id)方法获取指定布局id文件在编译后的XmlResourceParser对象;第二部分是通过inflate(parser, root, attachToRoot)方法完成组装View的任务。
首先android加载资源时通过
<pre>
public xmlResourceParser getLayout(int id) {
return loadXmlResourceParser(id, "layout");
}
public XmlResourceParser getAnimation(int id) {
return loadXmlResourceParser(id, "anim");
}
public XmlResourceParser getXml(int id) {
return loadXmlResourceParser(id, "xml");
}
</pre>
这些方法获取对应类型资源的XmlResourceParser对象。
这些方法都统一调用了loadXmlResourceParser(id, type)方法
<pre>
XmlResourceParser loadXmlResourceParser(int id, String type) {
......
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);
......
}
</pre>
创建了TypedValue对象,用于存放对应的资源信息,并通过getValue方法向TypedValue填充数据。
<pre>
/*
*返回一个指定资源id的相关资源信息
- @param id 给定的资源id,由aapt tool生成。是由package、type和resource三部分加密组成。0表示无效id。
- @param outValue 放置资源数据的对象。
- @param resoveRefs 如果为true,这个资源将会继续寻找它所引用的资源,直到找到最终的真实资源数据。如果为false,TypedValue会使用这个引用本身填充。
public void getValue(@AnyRes int id, TypedValue outValue, boolean resolveRefs) throws NotFoundException {
boolean found = mAssets.getResourceValue(id, 0, outValue, resolveRefs);
if (found) {
return;
}
throw new NotFoundException("Resource ID" + Integer.toHexSring(id));
}
</pre>
调用mAssets的getResourceValue方法,mAssets就是AssetManager,返回值found表明是否找到了对应资源。
<pre>
final boolean getResourceValue(int ident, int density, TypedValue outValue, boolean resolveRefs) {
int block = loadResourceValue(ident, (short) density, outValue, resolvesRefs);
if (block >= 0) {
if (outValue.type != TypedValue.TYPE_STRING) {
return true;
}
outValue.string = mStringBlocks[block].get(outValue.data);
return true;
}
return false;
}
</pre>
文件位置:frameworks/base/core/java/android/content/res/AssetManager
返回值block大于等于0说明找到了对应的资源,并指明了在资源列表中的位置。
通过jni调用了本地方法loadResourceValue
<pre>
private native final int loadResourceValue(int ident, short density, TypedValue outValue, boolean resolve);
</pre>
通过本地C++方法获取到指定ident的资源信息并填充到outValue中。
到此,TypedValue对象已经填充了对应的资源数据,然后继续前面的流程,调用loadXmlResourceParser(value.string.toString(), id, value.assetCookie, type)方法
<pre>
XmlResourceParser loadXmlResourceParser(String file, int id, int assetCookie, String type) {
......
//先看看缓存中是否有需要的bolck对象
final int num = mCachedXmlBlockIds.length;
for (int i=0; i<num; i++) {
if (mCachedXmlBlockIds[i] == id) {
return mCachedXmlBlocks[i].newparser();
}
}
//如果缓存中没有,就创建一个新的block并放入缓存中的下一个位置
XmlBlock block = mAssets.openXmlBlockAsset(assetCookie, file);
if (block != null) {
int pos = mLastCachedXmlBlockIndex+1;
if (pos >= num) pos = 0;
mLastCachedXmlBlockIndex = pos;
XmlBlock oldBlock = mCacheXmlBlocks[pos];
if (oldBlock != null) {
oldBlock.close();
}
mCachedXmlBlockIds[pos] = id;
mCachedXmlBlocks[pos] = block;
return block.newParser();
}
......
}
</pre>
mCachedXmlBlockIds是一个长度4的int型数组,mCachedXmlBlocks是长度4的XmlBlock数组,它们分别用于缓存最近生成的四个xml布局的id和XmlBlock对象。mLastCachedXmlBlockIndex表示最后一个缓存位置,当大于4时重置为0。明白了这些,上面的代码逻辑就十分好理解了:循环查询mCachedXmlBlockIds是否缓存有请求的id,如果有,直接返回mCacheXmlBlocks[i].newParser生成的XmlResourceParser对象,如果没有则使用AssetManager.openXmlBlockAsset生成一个指定file的XmlBlock对象,并放入缓存中,然后返回newParser生成的XmlResourceParser对象。
xmlBolck是对编译后的xml文件的一个包装。这里的AssetManager.openXmlBlockAsset最终也是调用了本地C++方法获取了编译后的资源存放位置信息。
至此,Resource类通过getLayout方法查找并返回了指定布局id的XmlResourceParser对象。之后就通过inflate(parser, root, attachToRoot)方法完成组装工作了
组装流程:
<pre>
public void inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
......
View result = root;
try {
......
final String name = parser.getName();
......
if (TAG_MERGE.equals(name)) {
......
rInflate(parser, root, inflaterContext, attrs, false);
} else {
//#1-1
final View temp = createViewFromTag(root, name, inflaterContext, attars);
ViewGroup.LayoutParams params = null;
if (root != null) {
......
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
temp.setLayoutParams(params);
}
}
......
rInflateChildren(parser, temp, attrs, true);
......
//#1-2
if (root != null && attachToRoot) {
root.addView(temp, params);
}
if (root != null || !attachToRoot) {
result = temp;
}
}
} catch.......
......
return result;
}
</pre>
代码设置result作为返回对象并初始化为root,在#1-1处通过createViewFromTag方法创建View对象temp,在#1-2处判断根root是否为空,不为空说明根布局不为空,就可将temp添加进root;如果root为空,就给result设置为temp,最后将result返回。再来看createViewFromTag方法:
<pre>
private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
return createViewFromTag(parent, name, context, attrs, false);
}
View createViewFromTag(View parent, String name, Context context, AttributeSet attars, boolean ignoreThemeAttr) {
......
View view;
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attars);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attars);
} else {
view = null;
}
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attars);
}
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attars);
} else {
view = createView(name, null, attars);
}
} finally {
mConstructorArgs[0] = lastContext;
}
return view;
......
}
</pre>
首先,会依次判断mFactory2,mFactory和mPrivateFactory这些UI创建工厂是否为空,若不为空,就调用onCreateView方法创建view。如果都为空,就进行第二次判断,判断节点的name是否包含'.',如果包含就是系统view,会调用本类的onCreateView方法;如果不包含就是自定义view,会调用本类的createView方法。
其中onCreateView也就是创建自定义view时其实是先调用子类的onCreateView的,原因是LayoutInflater的实现类是PhoneLayoutInflater,这个创建过程后面再说,先来看PhoneLayoutInflater的onCreateView:
<pre>
private static final String[] sClassPrefixList = {
"android.widget.",
"android.webkit.",
"android.app."
};
@Override
protected View onCreateView(String name, AttributeSet attars) {
for(String prefix : sClassPrefixList) {
try {
View view = createView(name, prefix, attars);
if (view != null) {
return view;
}
} catch (ClassNotFoundException e) {
}
}
return super.onCreateView(name, attars);
}
</pre>
经过轮询"android.widget""android.webkit""android.app",通过父类的createView方法创建view,如果其中一个系统前缀能够创建出view,就直接返回,如果没有成功的,就调用父类LayoutInflate的onCreateView,在父类里就会直接调用createView(name, "android.view.", attars)。这样一来,最终就都到了createView方法:
<pre>
public final View createView(String name, String prefix, AttributeSet attars) {
......
final View view = constructor.newInstance(args);
......
return view;
......
}
</pre>
方法通过newInstance实例化出相应节点view。
最后,再来追溯上面提到的子类PhoneLayoutInflater的实现过程。
LayoutInflater是通过from方法创建实例的:
<pre>
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;
}
</pre>
通过context的getSystemService获取,而context的实现类其实是ContextImpl:
<pre>
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
</pre>
然后调用SystemServiceRegistry的getSystemService:
<pre>
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
</pre>
通过SYSTEM_SERVICE_FETCHERS获取对应的ServiceFetcher对象,而SYSTEM_SERVICE_FETCHERS是通过registerService方法赋值的:
<pre>
private static <T> void registerService(String serviceName, Class<T> serviceClass, ServiceFetcher<T> serviceFetcher) {
SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}
</pre>
registerService就是设置各种各样Manager的地方,设置registerService是在本类的静态块中进行的
<pre>
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class, new CachedServiceFetcher<LayoutInflater>({
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
</pre>
在这里就给出了最终将会获取的LayoutInflater子类:PhoneLayoutInflater。