两种资源
- 预编译的layout资源(dex文件)
- xml layout文件
预编译的布局
目前的版本(Android 11)并没有开放这个功能.
基本的处理流程如下:
使用view_compiler将xml布局直接编译成CompiledView.java文件;
viewcompiler my_layout.xml --package com.example.myapp --out CompiledView.java
源码地址: view_compiler
整个app的预编译layout资源最后会打包到compiled_view.dex文件中;
在需要渲染xml布局的地方使用
CompiledView.infalte
替代
View tryInflatePrecompiled(@LayoutRes int resource, Resources res, @Nullable ViewGroup root,
boolean attachToRoot) {
if (!mUseCompiledView) {
return null;
}
String pkg = res.getResourcePackageName(resource);
String layout = res.getResourceEntryName(resource);
try {
// 通过反射调用CompiledView中的inflater方法
Class clazz = Class.forName("" + pkg + ".CompiledView", false, mPrecompiledClassLoader);
Method inflater = clazz.getMethod(layout, Context.class, int.class);
View view = (View) inflater.invoke(null, mContext, resource);
// 拿到View之后根据参数判断是否要添加到root
if (view != null && root != null) {
XmlResourceParser parser = res.getLayout(resource);
try {
AttributeSet attrs = Xml.asAttributeSet(parser);
advanceToRootNode(parser);
ViewGroup.LayoutParams params = root.generateLayoutParams(attrs);
if (attachToRoot) {
root.addView(view, params);
} else {
view.setLayoutParams(params);
}
} finally {
parser.close();
}
}
return view;
}
return null;
}
xml布局
// 一般都是使用这个方法进行布局加载的
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
// 加载预编译的layout
View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
if (view != null) {
return view;
}
// 加载普通的xml layout
XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
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 {
// 移到第一个tag的开始
advanceToRootNode(parser);
// root layout 的名字
final String name = parser.getName();
if (TAG_MERGE.equals(name)) {
// 处理<merge>,<merge>必须用在根结点
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 {
// tmp是xml是根据xml中发现的root节点创建的View
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
temp.setLayoutParams(params);
}
}
// 加载子View,实际上也是调用的rInflate()方法
rInflateChildren(parser, temp, attrs, true);
// 返回传入的root
if (root != null && attachToRoot) {
root.addView(temp, params);
}
// 返回xml布局的root view
if (root == null || !attachToRoot) {
result = temp;
}
}
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
}
return result;
}
}
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();
if (TAG_REQUEST_FOCUS.equals(name)) {
// 处理<requestFocus>标签,作用是为其父View提供初始焦点,任何View都可以包含这个标签
// 但是每个xml文件只允许有一个<requestFocus>标签
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)) {
throw new InflateException("<merge /> must be the root element");
} else {
// 递归调用,依次加载View,并添加到父View
// 过程是一层层的加,先把里层的层级关系搞定,再搞外层的
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflateChildren(parser, view, attrs, true);
viewGroup.addView(view, params);
}
}
// 处理<requestFocus>,为parent获取焦点
if (pendingRequestFocus) {
parent.restoreDefaultFocus();
}
// 调用parent的方法
if (finishInflate) {
parent.onFinishInflate();
}
}
看了上面这部分的代码,逻辑算是比较清晰的了:
- 获取Resources对象,通过Resources对象获取到与resourceId绑定的XmlResourceParser对象;
- 使用XmlResourceParser解析XML Layout结构;
- 先获取到根节点(即第一个START TAG),然后进行递归遍历,先搞定内部嵌套的的结构,再搞定外部的结构;
- 从XML Tag创建View的过程中间可以通过Factory/Factory2等进行代理/拦截,如果不做处理,则会使用反射进行创建;
从XML TAG 到 View
public final View createView(@NonNull Context viewContext, @NonNull String name,
@Nullable String prefix, @Nullable AttributeSet attrs)
throws ClassNotFoundException, InflateException {
Objects.requireNonNull(viewContext);
Objects.requireNonNull(name);
Constructor<? extends View> constructor = sConstructorMap.get(name);
if (constructor != null && !verifyClassLoader(constructor)) {
constructor = null;
sConstructorMap.remove(name);
}
Class<? extends View> clazz = null;
// 缓存 + Filter拦截机制
try {
if (constructor == null) {
// Class not found in the cache, see if it's real, and try to add it
clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
mContext.getClassLoader()).asSubclass(View.class);
if (mFilter != null && clazz != null) {
boolean allowed = mFilter.onLoadClass(clazz);
if (!allowed) {
failNotAllowed(name, prefix, viewContext, attrs);
}
}
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
sConstructorMap.put(name, constructor);
} else {
// If we have a filter, apply it to cached constructor
if (mFilter != null) {
// Have we seen this name before?
Boolean allowedState = mFilterMap.get(name);
if (allowedState == null) {
// New class -- remember whether it is allowed
clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
mContext.getClassLoader()).asSubclass(View.class);
boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
mFilterMap.put(name, allowed);
if (!allowed) {
failNotAllowed(name, prefix, viewContext, attrs);
}
} else if (allowedState.equals(Boolean.FALSE)) {
failNotAllowed(name, prefix, viewContext, attrs);
}
}
}
Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = viewContext;
Object[] args = mConstructorArgs;
args[1] = attrs;
try {
// 通过反射创建View对象
final View view = constructor.newInstance(args);
// 如果是ViewStub需要特殊处理下
if (view instanceof ViewStub) {
// Use the same context when inflating ViewStub later.
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
return view;
} finally {
mConstructorArgs[0] = lastContext;
}
}
}
如果不设置各种Factory,则最终会调用LayoutInflater的createView()
方法,从XML tag通过反射创建View对象;
主要步骤如下:
- 找到或者创建对应的Constructor;
- 进行Filter拦截处理,判断是否允许对应的View被加载成类;
- 使用Constructor通过反射创建View对象;
- 如果创建的View对象是ViewStub,则将当前的LayoutInflater设置给它;
其实分析到这里还有几个关键问题没有提及到:
- XmlResourceParser是如何创建的?作为一个XmlPullParser的实现,又是在哪设置的输入,将xml文件输入到API中的?
- Android Layout文件中View的各种属性是如何与对应View进行绑定的,或者说是暂时存储在哪的?
XmlResourceParser的创建
在Resources中有:
public XmlResourceParser getLayout(@LayoutRes int id) throws NotFoundException {
return loadXmlResourceParser(id, "layout");
}
XmlResourceParser loadXmlResourceParser(@AnyRes int id, @NonNull String type)
throws NotFoundException {
final TypedValue value = obtainTempTypedValue();
try {
// 将结果输出到value中存储
final ResourcesImpl impl = mResourcesImpl;
impl.getValue(id, value, true);
if (value.type == TypedValue.TYPE_STRING) {
return loadXmlResourceParser(value.string.toString(), id,
value.assetCookie, type);
}
throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+ " type #0x" + Integer.toHexString(value.type) + " is not valid");
} finally {
releaseTempTypedValue(value);
}
}
XmlResourceParser loadXmlResourceParser(String file, int id, int assetCookie,
String type) throws NotFoundException {
return mResourcesImpl.loadXmlResourceParser(file, id, assetCookie, type);
}
在ResourcesImpl中有:
XmlResourceParser loadXmlResourceParser(@NonNull String file, @AnyRes int id, int assetCookie,
@NonNull String type)
throws NotFoundException {
if (id != 0) {
try {
synchronized (mCachedXmlBlocks) {
final int[] cachedXmlBlockCookies = mCachedXmlBlockCookies;
final String[] cachedXmlBlockFiles = mCachedXmlBlockFiles;
final XmlBlock[] cachedXmlBlocks = mCachedXmlBlocks;
// First see if this block is in our cache.
final int num = cachedXmlBlockFiles.length;
for (int i = 0; i < num; i++) {
if (cachedXmlBlockCookies[i] == assetCookie && cachedXmlBlockFiles[i] != null
&& cachedXmlBlockFiles[i].equals(file)) {
return cachedXmlBlocks[i].newParser(id);
}
}
// 获取XmlBlock对象,XmlBlock就是已经编译的XML文件的一个表示类
final XmlBlock block = mAssets.openXmlBlockAsset(assetCookie, file);
if (block != null) {
final int pos = (mLastCachedXmlBlockIndex + 1) % num;
mLastCachedXmlBlockIndex = pos;
final XmlBlock oldBlock = cachedXmlBlocks[pos];
if (oldBlock != null) {
oldBlock.close();
}
cachedXmlBlockCookies[pos] = assetCookie;
cachedXmlBlockFiles[pos] = file;
cachedXmlBlocks[pos] = block;
//创建XmlResoucesParser
return block.newParser(id);
}
}
} catch (Exception e) {
final NotFoundException rnf = new NotFoundException("File " + file
+ " from xml type " + type + " resource ID #0x" + Integer.toHexString(id));
rnf.initCause(e);
throw rnf;
}
}
throw new NotFoundException("File " + file + " from xml type " + type + " resource ID #0x"
+ Integer.toHexString(id));
}
这里再看看XMLBlock的创建:
@NonNull XmlBlock openXmlBlockAsset(int cookie, @NonNull String fileName) throws IOException {
Objects.requireNonNull(fileName, "fileName");
synchronized (this) {
ensureOpenLocked();
// xmlBlock是ResXmlTree对应的地址
final long xmlBlock = nativeOpenXmlAsset(mObject, cookie, fileName);
if (xmlBlock == 0) {
throw new FileNotFoundException("Asset XML file: " + fileName);
}
// 根据ResXmlTree对应的地址创建XmlBlock对象
final XmlBlock block = new XmlBlock(this, xmlBlock);
incRefsLocked(block.hashCode());
return block;
}
}
XMLBlock中有:
public XmlResourceParser newParser(@AnyRes int resId) {
synchronized (this) {
if (mNative != 0) {
return new Parser(nativeCreateParseState(mNative, resId), this);
}
return null;
}
}
frameworks/base/core/jni/android_util_AssetManager.cpp
中有:
static jint NativeGetResourceValue(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid,
jshort density, jobject typed_value,
jboolean resolve_references) {
ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
// density = 0
auto value = assetmanager->GetResource(static_cast<uint32_t>(resid), false /*may_be_bag*/,
static_cast<uint16_t>(density));
//true
if (resolve_references) {
auto result = assetmanager->ResolveReference(value.value());
if (!result.has_value()) {
ThrowIfIOError(env, result);
return ApkAssetsCookieToJavaCookie(kInvalidCookie);
}
}
return CopyValue(env, *value, typed_value);
}
static jlong NativeOpenXmlAsset(JNIEnv* env, jobject /*clazz*/, jlong ptr, jint jcookie,
jstring asset_path) {
ApkAssetsCookie cookie = JavaCookieToApkAssetsCookie(jcookie);
// 文件路径
ScopedUtfChars asset_path_utf8(env, asset_path);
if (asset_path_utf8.c_str() == nullptr) {
// This will throw NPE.
return 0;
}
// assetManager对象
ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
std::unique_ptr<Asset> asset;
// 使用AssetManager打开非Asset资源
if (cookie != kInvalidCookie) {
asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), cookie, Asset::ACCESS_RANDOM);
} else {
asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), Asset::ACCESS_RANDOM, &cookie);
}
if (!asset) {
jniThrowException(env, "java/io/FileNotFoundException", asset_path_utf8.c_str());
return 0;
}
// 获取文件buffer和大小
const incfs::map_ptr<void> buffer = asset->getIncFsBuffer(true /* aligned */);
const size_t length = asset->getLength();
if (!buffer.convert<uint8_t>().verify(length)) {
jniThrowException(env, kResourcesNotFound, kIOErrorMessage);
return 0;
}
// 创建ResXMLTree,并设置buffer
auto xml_tree = util::make_unique<ResXMLTree>(assetmanager->GetDynamicRefTableForCookie(cookie));
status_t err = xml_tree->setTo(buffer.unsafe_ptr(), length, true);
if (err != NO_ERROR) {
jniThrowException(env, "java/io/FileNotFoundException", "Corrupt XML binary file");
return 0;
}
// 返回ResXmlTrhee的地址
return reinterpret_cast<jlong>(xml_tree.release());
}
到这里可以看到流程如下:
- C++层通过assetmanager根据resId + DynamicRefTable获取到对应资源文件的buffer和大小,并将数据设置到ResXmlTree中,并返回其地址,作为Java层的XmlBlock;
- Java层中XmlResourcesParser的解析工作其实是通过XmlBlock中的内部类Parser去完成的,而XmlBlock层中有来自于C++层的ResXmlTree的地址,因此刚刚好可以用来读取布局数据;
XmlBlock中的解析
public int next() throws XmlPullParserException,IOException {
if (!mStarted) {
mStarted = true;
return START_DOCUMENT;
}
if (mParseState == 0) {
return END_DOCUMENT;
}
int ev = nativeNext(mParseState);
if (mDecNextDepth) {
mDepth--;
mDecNextDepth = false;
}
switch (ev) {
case START_TAG:
mDepth++;
break;
case END_TAG:
mDecNextDepth = true;
break;
}
mEventType = ev;
if (ev == END_DOCUMENT) {
// Automatically close the parse when we reach the end of
// a document, since the standard XmlPullParser interface
// doesn't have such an API so most clients will leave us
// dangling.
close();
}
return ev;
}
看下next()方法实现就差不多了,其他很多方法都在C++层,这里只需要知道XmlResourcesParser的实现类其实是XmlBlock中的Parser即可;
Layout文件中属性的绑定
其实前面在解析的时候有下面这一句:
final AttributeSet attrs = Xml.asAttributeSet(parser);
因为XmlResourcesParser本身就是实现的AttributeSet,因此可以进行转换;
然后这个AttributeSet会在加载渲染的过程中传递给View.主要有两个用途:
设置布局的layout_width/layout_height
params = root.generateLayoutParams(attrs);
public LayoutParams(Context c, AttributeSet attrs) { TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout); setBaseAttributes(a, R.styleable.ViewGroup_Layout_layout_width, R.styleable.ViewGroup_Layout_layout_height); a.recycle(); }
在反射创建View的时候作为参数传入
mConstructorArgs[0] = viewContext; Object[] args = mConstructorArgs; args[1] = attrs; // 相当于调用了View(Context,AttributeSet) final View view = constructor.newInstance(args);
可以看到反射一定会调用到
View(Context,AttributeSet)
;这个时候会传入Context参数和AttributeSet参数.便于View去处理各种属性.