LayoutInflater实例化布局流程分析

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。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容