在日常的开发过程如果需要加载一个布局,这时候通常会用到加载布局的系统服务类——LayoutInflater。比如在RecyclerView的onCreateViewHolder方法中,或者ListView的getView方法中,亦或是我们经常使用的的Activity的setContentView()加载布局方法,其实它底层用的还是LayoutInflater服务。由此可见只要涉及到布局加载就离不开LayoutInflater,那么弄清LayoutInflater的内在逻辑,对于我们学习如何加载布局就显得至关重要。
学习源码一定不要像无头苍蝇似的乱撞,否则就深入源码的苦海无法自拔。带着问题找答案永远要比盲目更容易柳暗花明,所以在开始学习LayoutInflater之前思考几件事情。
- 如何获取获取LayoutInflater服务;
- 和获取与之对应的就是LayoutInflater服务是什么时候注册的;
- LayoutInflater服务是如何加载布局的;
带着这三个问题开始这篇文章,下面以RecyclerView中的onCreateViewHolder方法为例:
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
//加载布局的时候需要注意要把parent传进去
View v = inflater.inflate(R.layout.layout_item, parent, false);
//返回MyViewHolder的对象
return new MyViewHolder(v);
}
通常情况通过LayoutInflater.from(context)来获取LayoutInflater服务,在调用inflate( int resource,ViewGroup root, boolean attachToRoot)方法。
第一个参数:添加的布局子布局;
第二个参数:想要添加到的父布局 ——root为null时第一个参数中最外层的布局大小无效,有值的时候最外层的布局大小有效,后面会解释为什么。
第三个参数:是否直接添加到第二个参数布局上面——true代表layout文件填充的View会被直接添加进root,而传入false则代表创建的View会以其他方式被添加进root
先看LayoutInflater.from(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;
}
可以看出form函数内部调用的是Context的getSystemService方法,而大家都知道Context又是个抽象类,getSystemService也是够抽象方法。所以现在要找的是:这里使用的Context对象的实现到底是谁?其实在Application、Activity、Service中都会存在一个Context对象,显然这里用不到Application和Service中的Context。View通常都显示在Activity中,所以使用的是Activity中的Context。
Activity的入口是ActivityThread的main方法,main方法中会创建一个ActivityThead对象,在UI线程中启动消息循环,创建新的Activity、Context对象。然后把这个Context传递给Activity。
public static void main(String[] args) {
//代码...
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
//代码...
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
}
private void attach(boolean system, long startSeq) {
sCurrentActivityThread = this;
mSystemThread = system;
if (!system) {
ViewRootImpl.addFirstDrawHandler(new Runnable() {
@Override
public void run() {
ensureJitEnabled();
}
});
android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
UserHandle.myUserId());
RuntimeInit.setApplicationObject(mAppThread.asBinder());
final IActivityManager mgr = ActivityManager.getService();
try {
mgr.attachApplication(mAppThread, startSeq);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
//代码...
} else {
//代码...
}
}
在main方法中ActivityThread会调用attach函数,参数为false表明非系统应用。会通过binder机制与ActivityManagerServer通信(关于Binder通信不是本篇主要内容,可以查看老罗的系列博客),并最终回调ActivityThread的handleLaunchActivity方法:
@Override
public Activity handleLaunchActivity(ActivityClientRecord r,
PendingTransactionActions pendingActions, Intent customIntent) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
//代码...
final Activity a = performLaunchActivity(r, customIntent);
//代码...
return a;
}
/** Core implementation of activity launch. */
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
ActivityInfo aInfo = r.activityInfo;
if (r.packageInfo == null) {
r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
Context.CONTEXT_INCLUDE_CODE);
}
//代码...
//获取context对象
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if (r.state != null) {
r.state.setClassLoader(cl);
}
} catch (Exception e) {
if (!mInstrumentation.onException(activity, e)) {
throw new RuntimeException(
"Unable to instantiate activity " + component
+ ": " + e.toString(), e);
}
}
try {
//代码...
if (activity != null) {
//代码...
//将appContext等对象attach到activity中
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback);
if (customIntent != null) {
activity.mIntent = customIntent;
}
r.lastNonConfigurationInstances = null;
checkAndBlockForNetworkAccess();
activity.mStartedActivity = false;
int theme = r.activityInfo.getThemeResource();
if (theme != 0) {
activity.setTheme(theme);
}
activity.mCalled = false;
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
//调用activity的onCreate方法
mInstrumentation.callActivityOnCreate(activity, r.state);
//代码...
return activity;
}
从上面可知在Activity中的Context实现对象是ContexImpl,继续进入到ContexImpl类中,找到需要的getSystemService方法的实现:
class ContextImpl extends Context {
//代码...
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
//代码...
}
final class SystemServiceRegistry {
//Service容器
private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
new HashMap<String, ServiceFetcher<?>>();
/**
* Gets a system service from a given context.
*根据key 获取对应的服务
*/
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
/**
* Base interface for classes that fetch services.
* These objects must only be created during static initialization.
*/
static abstract interface ServiceFetcher<T> {
T getService(ContextImpl ctx);
}
}
可以看到调用的是SystemServiceRegistry的getSystemService方法,并通过对应的服务名从HashMap中获取对应的ServiceFetcher,然后在通过ServiceFetcher的getService方法来获取具体的服务对象。到这里终于解答第一个问题,原来我们的服务是通过key-value的方式从一个HashMap中获取。但是还有一个很关键的问题,这些服务是怎么缓存到SYSTEM_SERVICE_FETCHERS这个Map中的——也就是这些服务什么时候注册的。
想要知道如何注册,同样也能在SystemServiceRegistry这个类寻找到答案。通过查看在该类其中有一大段静态代码块,在这段静态代码块中注册各种各样的系统服务。这里只贴出LayoutInflater服务有关的代码:
final class SystemServiceRegistry {
static {
//代码...
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher<LayoutInflater>() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
//PhoneLayoutInflater才是我们真正需要的布局加载服务
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
//代码...
}
static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {
private final int mCacheIndex;
CachedServiceFetcher() {
// Note this class must be instantiated only by the static initializer of the
// outer class (SystemServiceRegistry), which already does the synchronization,
// so bare access to sServiceCacheSize is okay here.
mCacheIndex = sServiceCacheSize++;
}
@Override
@SuppressWarnings("unchecked")
public final T getService(ContextImpl ctx) {
final Object[] cache = ctx.mServiceCache;
final int[] gates = ctx.mServiceInitializationStateArray;
for (;;) {
boolean doInitialize = false;
synchronized (cache) {
// Return it if we already have a cached instance.
//如果在缓存有直接返回需要的服务
T service = (T) cache[mCacheIndex];
if (service != null || gates[mCacheIndex] == ContextImpl.STATE_NOT_FOUND) {
return service;
}
// If we get here, there's no cached instance.
// Grr... if gate is STATE_READY, then this means we initialized the service
// once but someone cleared it.
// We start over from STATE_UNINITIALIZED.
if (gates[mCacheIndex] == ContextImpl.STATE_READY) {
gates[mCacheIndex] = ContextImpl.STATE_UNINITIALIZED;
}
// It's possible for multiple threads to get here at the same time, so
// use the "gate" to make sure only the first thread will call createService().
// At this point, the gate must be either UNINITIALIZED or INITIALIZING.
if (gates[mCacheIndex] == ContextImpl.STATE_UNINITIALIZED) {
doInitialize = true;
gates[mCacheIndex] = ContextImpl.STATE_INITIALIZING;
}
}
if (doInitialize) {
// Only the first thread gets here.
T service = null;
@ServiceInitializationState int newState = ContextImpl.STATE_NOT_FOUND;
try {
// This thread is the first one to get here. Instantiate the service
// *without* the cache lock held.
//初始化服务
service = createService(ctx);
newState = ContextImpl.STATE_READY;
} catch (ServiceNotFoundException e) {
onServiceNotFound(e);
} finally {
synchronized (cache) {
//把创建的服务放入缓存中
cache[mCacheIndex] = service;
gates[mCacheIndex] = newState;
cache.notifyAll();
}
}
return service;
}
// The other threads will wait for the first thread to call notifyAll(),
// and go back to the top and retry.
synchronized (cache) {
while (gates[mCacheIndex] < ContextImpl.STATE_READY) {
try {
cache.wait();
} catch (InterruptedException e) {
Log.w(TAG, "getService() interrupted");
Thread.currentThread().interrupt();
return null;
}
}
}
}
}
//需要重写的方法
public abstract T createService(ContextImpl ctx) throws ServiceNotFoundException;
}
}
我们知道静态代码块只会在类第一次加载的时候执行(只执行一次),也就是说在我们第一次调用SystemServiceRegistry.getSystemService方法时,会预先加载这一段代码块,也就完成服务的注册。在代码块中能清楚的发现PhoneLayoutInflater才是真正需要的服务,也就说虚拟机第一次加载SystemServiceRegistry类时会注册各种ServiceFetcher,其中就包含PhoneLayoutInflater,这些服务通过健值对的形式存储在一个HashMap中,用户使用时根据key来获取对应的ServiceFetcher,然后通过ServiceFetcher的getService方法获取具体的对象。当第一次获取时,会调用ServiceFetcher的createService方法创建对象,然后把这个对象缓存在一个列表中,等下次在获取时直接从缓存中获取避免重复创建对象。
分析到这我们已经弄清楚前两个问题——如何获取获取LayoutInflater服务和什么时候注册LayoutInflater服务。
接下来就剩LayoutInflater如何加载布局这个最关键的问题,接下来查看LayoutInflater的inflate方法:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
//root不为空则会把resource布局解析到view,并添加到root中
return inflate(resource, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}
//获得XML解析器
final 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 {
// Look for the root node.
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
final String name = parser.getName();
//解析merage标签
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 {
//这里通过xml的tag来解析layout的根视图,name就是要解析的视图名称(如:ViewGroup、LinearLayout等)
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
//1:如果父布局为空生成布局参数
if (root != null) {
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
//attachToRoot为false则给temp设置布局参数
if (!attachToRoot) {
temp.setLayoutParams(params);
}
}
//解析temp视图下所有子视图
rInflateChildren(parser, temp, attrs, true);
//如果root不为空且attachToRoot为true,将temp添加到父视图
if (root != null && attachToRoot) {
root.addView(temp, params);
}
//如果root为空或者attachToRoot为false直接返回temp结果
if (root == null || !attachToRoot) {
result = temp;
}
}
}
//代码...
return result;
}
}
上述inflate方法中,主要有下面几步:
- 根据resource获的xml解析器
- 解析xml中的根标签
- 如果根标签是merge,那么调用rInflate进行解析,rInflate会将merge标签下所有的子view直接添加到标签中。
- 如果是普通标签,则调用createViewFromTag对该标签进行解析;
- 调用rInflateChildren解析temp根元素下的所有子 view,并将这些子View添加到temp下;
- 返回解析的根视图。
分析到这可以解答文章最开始的一个问题——在使用LayoutInflater的inflate(int resource, ViewGroup root,boolean attachToRoot)方法时,如果第二参数root为空,则第一个参数中最外层的布局大小无效。
if (root != null) {
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
//attachToRoot为false则给temp设置布局参数
if (!attachToRoot) {
temp.setLayoutParams(params);
}
}
在这段代码中,如果root不为空才会调用 root.generateLayoutParams(attrs)获取布局参数,如果root为空显然就不会有布局参数自然外层布局大小也无效
回到主题继续分析View的解析,接下来先从简单的单个标签布局开始分析,即createViewFromTag方法:
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 attrs,
boolean ignoreThemeAttr) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
try {
//用户通过设置LayoutInflater的Factory2 、mPrivateFactory、mFactory自行解析
View view;
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
//通过内置onCreateView解析
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
//系统内置的View
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
//自定义View
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
}
//代码...
}
在createViewFromTag方法中有一段代码需要解释一下,如果标签名name中不包含'.'时,也就是-1 == name.indexOf('.'),这表明是系统内置的View,解析方式onCreateView(parent, name, attrs)。例如在xml文件中声明一个内置的TextView方式如下:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
如果标签名中包含'.'时,表明为自定义view,解析方式为 createView(name, null, attrs),例如在xml文件中声明一个自定义view方式如下:
<com.sun.wds.content.MyTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
这李我们已经知道这段逻辑判读的含义,那两者之间有有什么不同呢?
我们先从onCreateView(parent, name, attrs)方法开始,也就是解析系统内置View,注意最开始调用的是三个参数的方法:
protected View onCreateView(View parent, String name, AttributeSet attrs)
throws ClassNotFoundException {
return onCreateView(name, attrs);
}
protected View onCreateView(String name, AttributeSet attrs)
throws ClassNotFoundException {
return createView(name, "android.view.", attrs);
}
public class PhoneLayoutInflater extends LayoutInflater {
//内置View类型的前缀
private static final String[] sClassPrefixList = {
"android.widget.",
"android.webkit.",
"android.app."
};
@Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
//在View名字的前面添加前缀构造View的完整路径
for (String prefix : sClassPrefixList) {
try {
View view = createView(name, prefix, attrs);
if (view != null) {
return view;
}
} catch (ClassNotFoundException e) {
// In this case we want to let the base class take a crack
// at it.
}
}
return super.onCreateView(name, attrs);
}
}
从上文可知PhoneLayoutInflater是LayoutInflater真正的实现类,其覆写了onCreateView(String name, AttributeSet attrs)方法。当解析内置View的情况下,调用是PhoneLayoutInflater的onCreateView方法,该方法会给View加上前缀构造View的完整路径,然后传递给createView(name, prefix, attrs)方法。也就是说不管是内置View还是自定义View最终都会调用createView(name, prefix, attrs)方法解析,只不过是自定义View调用createView方法是prefix为空,内置View不为空。
当只写View的名称没有路径,LayoutLayout解析时就认为是系统内置View,在onCreateView方法中自动添加路径,最后传递给createView方法进行解析。
如果是自定义View,那么在xml文中定义必须是完整路径,此时调用createView(name, prefix, attrs)解析,只不过prefix前缀为空。
解释了这么,最重要还是要看createView方法是如何解析的,代码如下:
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
//缓存中获取构造函数
Constructor<? extends View> constructor = sConstructorMap.get(name);
if (constructor != null && !verifyClassLoader(constructor)) {
constructor = null;
sConstructorMap.remove(name);
}
Class<? extends View> clazz = null;
try {
if (constructor == null) {
/**如果缓存不存在,从Class对象获取构造函数,并添加至缓存**/
//prefix != null ? (prefix + name) : name
//如果prefix不为空则添加完整路径,加载此类
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);
}
}
//从Class对象获取构造函数
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
//添加缓存
sConstructorMap.put(name, constructor);
} else {
//代码...
}
Object lastContext = mConstructorArgs[0];
if (mConstructorArgs[0] == null) {
// Fill in the context if not already within inflation.
mConstructorArgs[0] = mContext;
}
Object[] args = mConstructorArgs;
args[1] = attrs;
//通过反射构造View args [context,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]));
}
mConstructorArgs[0] = lastContext;
return view;
} catch (NoSuchMethodException e) {
//代码...
}
}
逻辑很简单,先从缓存中获取View的构造函数,如果没有就拼接一个View的完整路径,并将该类加载到虚拟机中,然后获取该类的构造函数并缓存起来,再通过构造函数来创建改View的对象,最后返回该View。
到这里我们只是分析了单个标签通过createViewFromTag的解析方式,通常情况下一个布局下会有很多子View,子View下又有子View,组成一个树状结构。这个时候就要回到inflate 方法中步骤5,调用rInflateChildren解析temp视图下所有子视图。
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;
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)) {
pendingRequestFocus = true;
consumeChildElements(parser);
} else if (TAG_TAG.equals(name)) {
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)) {//merge标签
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
rInflateChildren(parser, view, attrs, true);
//把解析到View添加父容器中
viewGroup.addView(view, params);
}
}
}
rInflate方法通过深度优先遍历视图,每解析完一个View就会递归调用rInflateChildren解析,直到这个深度下的最后一个view,然后再把解析得到的每一个View添加到这些它们的父容器中,这样整个树状视图就解析完成了。