type
status
date
slug
summary
tags
category
icon
password

两种资源

  • 预编译的layout资源(dex文件)
  • xml layout文件

预编译的布局

目前的版本(Android 11)并没有开放这个功能.
基本的处理流程如下:
  1. 使用view_compiler将xml布局直接编译成CompiledView.java文件;
    1. viewcompiler my_layout.xml --package com.example.myapp --out CompiledView.java
      源码地址: view_compiler
      整个app的预编译layout资源最后会打包到compiled_view.dex文件中;
  1. 在需要渲染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(); } }
看了上面这部分的代码,逻辑算是比较清晰的了:
  1. 获取Resources对象,通过Resources对象获取到与resourceId绑定的XmlResourceParser对象;
  1. 使用XmlResourceParser解析XML Layout结构;
  1. 先获取到根节点(即第一个START TAG),然后进行递归遍历,先搞定内部嵌套的的结构,再搞定外部的结构;
  1. 从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对象;
主要步骤如下:
  1. 找到或者创建对应的Constructor;
  1. 进行Filter拦截处理,判断是否允许对应的View被加载成类;
  1. 使用Constructor通过反射创建View对象;
  1. 如果创建的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()); }
到这里可以看到流程如下:
  1. C++层通过assetmanager根据resId + DynamicRefTable获取到对应资源文件的buffer和大小,并将数据设置到ResXmlTree中,并返回其地址,作为Java层的XmlBlock;
  1. 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去处理各种属性.
Kotlin中的协程Java中的ThreadPoolExecutor
姜康
姜康
一个软件工程师
公告
type
status
date
slug
summary
tags
category
icon
password
🎉博客网站重新制作了🎉
👏欢迎更新体验👏