LayoutInflater原理分析


两种资源

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

预编译的布局

目前的版本(Android 11)并没有开放这个功能.

基本的处理流程如下:

  1. 使用view_compiler将xml布局直接编译成CompiledView.java文件;

    viewcompiler my_layout.xml --package com.example.myapp --out CompiledView.java

    源码地址: view_compiler

    整个app的预编译layout资源最后会打包到compiled_view.dex文件中;

  2. 在需要渲染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对象;
  2. 使用XmlResourceParser解析XML Layout结构;
  3. 先获取到根节点(即第一个START TAG),然后进行递归遍历,先搞定内部嵌套的的结构,再搞定外部的结构;
  4. 从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;
  2. 进行Filter拦截处理,判断是否允许对应的View被加载成类;
  3. 使用Constructor通过反射创建View对象;
  4. 如果创建的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;
  2. 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去处理各种属性.


文章作者: 姜康
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 姜康 !
评论
  目录