LayoutInflater源码分析

转载请注明出处:http://blog.csdn.net/fishle123/article/details/51039293

在Android里面,经常使用LayoutInflater来加载布局。这里结合源码分析一下LayoutInflater是如何加载布局的。

LayoutInflater一个常见的用法如下:

private View inflateView(Context context, int resID) {
    LayoutInflater inflater = LayoutInflater.from(context);
    //或者也可以这样获取LayoutInlfater
    //LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    return inflater.inflate(resID, null);
}

这样简单的几行代码就可以就把使用xml定义的布局转化为对应View。

下面就从LayoutInflater的inflate方法开始分析LayoutInflater的工作过程。

/**
 * Obtains the LayoutInflater from the given context.
 */
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;
}

从这里可以看到,LayoutInlfater最终是通过getSystemService来获取的。如果大家对Context有一定的了解就会知道,Context的实现类是ContextImpl,这里直接去看ContextImpl的源码:

//https://github.com/android/platform_frameworks_base/blob/master/core/java/android/app/ContextImpl.java
@Override
public Object getSystemService(String name) {
    return SystemServiceRegistry.getSystemService(this, name);
}

继续看SystemServiceRegistry的getSystemService的源码:

/**https://github.com/android/platform_frameworks_base/blob/master/core/java/android/app/SystemServiceRegistry.java

 * Gets a system service from a given context.
 */
public static Object getSystemService(ContextImpl ctx, String name) {
    ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
    return fetcher != null ? fetcher.getService(ctx) : null;
}

SYSTEM_SERVICE_FETCHERS定义如下:

// Service registry information.
// This information is never changed once static initialization has completed.
private static final HashMap<Class<?>, String> SYSTEM_SERVICE_NAMES =
        new HashMap<Class<?>, String>();
private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
        new HashMap<String, ServiceFetcher<?>>();

可以看到,SYSTEM_SERVICE_FETCHERS其实是一个HashMap,它保存了系统提供的各种ServiceFetcher,在ServiceFetcher里面保存有对应的Service对象。在SystemServiceRegistry中会在静态语句块中调用registerService来注册系统提供的Service,比如LayoutInflater也是在这里注册的(见第21--26行):

static {
   ......

    registerService(Context.ACTIVITY_SERVICE, ActivityManager.class,
            new CachedServiceFetcher<ActivityManager>() {
                @Override
                public ActivityManager createService(ContextImpl ctx) {
                    return new ActivityManager(ctx.getOuterContext(), ctx.mMainThread.getHandler());
                }});

    registerService(Context.ALARM_SERVICE, AlarmManager.class,
            new CachedServiceFetcher<AlarmManager>() {
                @Override
                public AlarmManager createService(ContextImpl ctx) {
                    IBinder b = ServiceManager.getService(Context.ALARM_SERVICE);
                    IAlarmManager service = IAlarmManager.Stub.asInterface(b);
                    return new AlarmManager(service, ctx);
                }});
    ......
    //注册LayoutInlfater
    registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
            new CachedServiceFetcher<LayoutInflater>() {
                @Override
                public LayoutInflater createService(ContextImpl ctx) {
                    return new PhoneLayoutInflater(ctx.getOuterContext());
                }});

   ......

}
/**
 * Statically registers a system service with the context.
 * This method must be called during static initialization only.
 */
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);
}

到这里,我们已经弄清楚LayoutInflater是如何创建和获取的了,下面来看看inflate方法是如何加载布局的。先看inflate的源码:

/**
 * Inflate a new view hierarchy from the specified xml resource. Throws
 * {@link InflateException} if there is an error.
 * 
 * @param resource ID for an XML layout resource to load (e.g.,
 *        <code>R.layout.main_page</code>)
 * @param root Optional view to be the parent of the generated hierarchy.
 * @return The root View of the inflated hierarchy. If root was supplied,
 *         this is the root View; otherwise it is the root of the inflated
 *         XML file.
 */
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
    return inflate(resource, root, root != null);
}

inflate(int resource, ViewGroup root)最终会调用inflate(int resource, ViewGroup root, boolean attachToRoot)

,而后者会调用inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot),继续看源码:

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

        final Context inflaterContext = mContext;
        final AttributeSet attrs = Xml.asAttributeSet(parser);
        Context lastContext = (Context) mConstructorArgs[0];
        mConstructorArgs[0] = inflaterContext;
        View result = root;

        try {
            // 查找布局的根节点.
            int type;
            while ((type = parser.next()) != XmlPullParser.START_TAG &&
                    type != XmlPullParser.END_DOCUMENT) {
                // Empty
            }

            if (type != XmlPullParser.START_TAG) {
                throw new InflateException(parser.getPositionDescription()
                        + ": No start tag found!");
            }

            final String name = parser.getName();
            
           ......

            if (TAG_MERGE.equals(name)) {
                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 {
                // Temp is the root view that was found in the xml
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                ViewGroup.LayoutParams params = null;

                if (root != null) {
                    if (DEBUG) {
                        System.out.println("Creating params from root: " +
                                root);
                    }
                    // Create layout params that match root, if supplied
                    params = root.generateLayoutParams(attrs);
                    if (!attachToRoot) {
                        // Set the layout params for temp if we are not
                        // attaching. (If we are, we use addView, below)
                        temp.setLayoutParams(params);
                    }
                }

               .....
                // 加载子布局
                rInflateChildren(parser, temp, attrs, true);

                if (DEBUG) {
                    System.out.println("-----> done inflating children");
                }

                // We are supposed to attach all the views we found (int temp)
                // to root. Do that now.
                if (root != null && attachToRoot) {
                    root.addView(temp, params);//将布局添加到父节点中
                }

                // Decide whether to return the root that was passed in or the
                // top view found in xml.
                if (root == null || !attachToRoot) {
                    result = temp;
                }
            }

        } catch (XmlPullParserException e) {
            InflateException ex = new InflateException(e.getMessage());
            ex.initCause(e);
            throw ex;
        } catch (Exception e) {
            InflateException ex = new InflateException(
                    parser.getPositionDescription()
                            + ": " + e.getMessage());
            ex.initCause(e);
            throw ex;
        } finally {
            // Don't retain static reference on context.
            mConstructorArgs[0] = lastContext;
            mConstructorArgs[1] = null;
        }

        Trace.traceEnd(Trace.TRACE_TAG_VIEW);

        return result;
    }

其实inflate的逻辑还是比较简单的,从inflate的参数可以看出,它使用的Pull方式来分析xml布局文件。遇到一个View节点后,会调用createViewFromTag来创建对应的View实例。如果该View节点有父节点(root),还会为该View生成对应布局参数LayoutParams,即我们为该节点使用layout_xxx属性可以生效。但是我们在Activity里面调用setContentView的时候,在外层会有一个FrameLayout,因此这时候root是不等于null的,因此我们在根节点中使用layout_xxx属性时都是可以生效的。这里重点看一下第37行和第57行,在37行先创建了当前布局的根节点,然后在57行调用 rInflateChildren(parser, temp, attrs, true)来加载子布局。

下面先来看一下createViewFromTag是怎样创建View的,在createViewFromTag内部又会调用createView,这里直接看createView的源码:

public final View createView(String name, String prefix, AttributeSet attrs)
        throws ClassNotFoundException, InflateException {
    Constructor<? extends View> constructor = sConstructorMap.get(name);
    Class<? extends View> clazz = null;

    try {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
        //如果constructor为null,就先加载相应的class
        if (constructor == null) {
            // Class not found in the cache, see if it's real, and try to add it
            clazz = mContext.getClassLoader().loadClass(
                    prefix != null ? (prefix + name) : name).asSubclass(View.class);
            
            if (mFilter != null && clazz != null) {
                boolean allowed = mFilter.onLoadClass(clazz);
                if (!allowed) {
                    failNotAllowed(name, prefix, attrs);
                }
            }
            constructor = clazz.getConstructor(mConstructorSignature);
            constructor.setAccessible(true);
            //将构造函数constructor保存起来,后续遇到相同的View的时候可以直接拿来用
            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 = mContext.getClassLoader().loadClass(
                            prefix != null ? (prefix + name) : name).asSubclass(View.class);
                    
                    boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                    mFilterMap.put(name, allowed);
                    if (!allowed) {
                        failNotAllowed(name, prefix, attrs);
                    }
                } else if (allowedState.equals(Boolean.FALSE)) {
                    failNotAllowed(name, prefix, attrs);
                }
            }
        }

        Object[] args = mConstructorArgs;
        args[1] = attrs;
        //创建相应的实例
        final View view = constructor.newInstance(args);
        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;

    } catch (NoSuchMethodException e) {
       ......
    } catch (ClassCastException e) {
       ......    
    } catch (ClassNotFoundException e) {
       .......
} catch (Exception e) {
        ......
} finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

在第3行会先去sConstructorMap查找对应View的构造函数是否已经缓存,如果有就直接拿来用,当然第一次遇到该View的时候是不存在的,因此9--23行会加载对应的class,然后找到构造函数,找到之后会保存到sConstructorMap,这样下次再遇到相同的View就不用再去重新加载class了。接着47行会调用newInstance来创建对应的View的实例。这里可以看到从xml文件中解析到View的名字后,会使用反射的方式来创建对应的实例。

最后,再来看一下 rInflateChildren(parser, temp, attrs, true)的源码:

final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
        boolean finishInflate) throws XmlPullParserException, IOException {
    rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}

void rInflate(XmlPullParser parser, View parent, Context context,
        AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

    final int depth = parser.getDepth();
    int type;

    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)) {
            parseRequestFocus(parser, parent);
        } else if (TAG_TAG.equals(name)) {
            parseViewTag(parser, parent, attrs);
        } else if (TAG_INCLUDE.equals(name)) {
            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 {
            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);
        }
    }

    if (finishInflate) {
        parent.onFinishInflate();
    }
}

直接看12--39行,在while循环中会逐个节点的分析xml布局,然后调用createViewFromTag创建对应的View。接着在36行递归调用rInflateChildren来分析子布局。因为这是使用的是递归的方式,所以我们的布局不能嵌套太多层级了。

我们可以看到inflate加载布局的过程其实可以分为两步:第一步,先解析整个xml布局的根节点,并创建对应的实例(通过反射来创建);第二步,通过递归的方式来分析子布局并创建对应的实例。

到此为止,我们就弄清楚LayoutInflater是如何加载布局的了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值