美文网首页
LayoutInflater实例化布局流程分析

LayoutInflater实例化布局流程分析

作者: 土豆肉多 | 来源:发表于2017-01-10 18:37 被阅读0次

    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。

    相关文章

      网友评论

          本文标题:LayoutInflater实例化布局流程分析

          本文链接:https://www.haomeiwen.com/subject/wcyovttx.html