系列文章索引
Android系统启动流程
LayoutInflater源码详解
更新
录播回放已上传,请戳链接食用:【Android/源码/面试】LayoutInflater源码详解
前言
这篇文章会从源码的角度分析,LayoutInflater
将xml文件实例化为一个view
对象的流程
我们会发现,其中有两个部分是耗时的主要来源
- XmlResourseParser对xml的遍历
- 反射创建View对象导致的耗时
这两点,又跟Xml的复杂程度成正相关,Xml越复杂,则递归调用所消耗的时间就越长,就产生了我们所说的,卡顿问题
整体流程概览
彩蛋:BlinkLayout
BlinkLayout是LayoutInflater中的一个内部类,它本身是是FrameLayout
的子类,如果当前标签为TAG_1995
,则创建一个隔0.5秒闪烁一次的BlinkLayout来承载它的布局内容
源码注释也很有意思,写了Let's party like it's 1995!
, 据说是为了庆祝1995
年的复活节
LayoutInflater
public final View tryCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context,
@NonNull AttributeSet attrs) {
if (name.equals(TAG_1995)) {
// Let's party like it's 1995!
return new BlinkLayout(context, attrs);
}
...
return view;
}
具体使用也很简单
<blink
android:layout_below="@id/iv_qr_code"
android:layout_centerHorizontal="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Android研习社"
android:textColor="#157686"
android:textSize="55sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</blink>
效果如下,这种效果也适合来做EditText中光标的闪烁效果
扫描上方二维码关注「Android研习社」公众号,获取更多学习资料!
ps: 想深入学习的都关注了,还不赶快关注一波?
LayoutInflater的创建
概览
LayoutInflater是一个抽象类,它的创建,并不是交由App层处理的,而是调用了from()
的静态函数,经由系统服务LAYOUT_INFLATER_SERVICE
,最终创建了一个LayoutInflater
的子类对象–PhoneLayoutInflater
重要函数解析
LayoutInflater.from(cxt)
这个函数比较简单,就是根据传递过来的Context对象,调用getSystemService()
来获取对应的系统服务,并赋值给LayoutInflater
public static LayoutInflater from(Context context) {
LayoutInflater LayoutInflater = //LayoutInflate是一个系统服务,最终返回的是`PhoneLahyoutInflater`
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}
而Context
本身是一个抽象类,它真正的实例化对象,是ContextImpl
, 在这个类的getSystemService()
函数中,真正执行获取系统服务的类,是SystemServiceRegistry
,其中又封装了一个ServiceFetcher
来获取真正的系统服务,所有的系统服务,都是存储在一个map集合–SYSTEM_SERVICE_FETCHERS
当中,这里其实是一个get
方法,是从这个map集合中取出对应的系统服务
LayoutInflater
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
SystemServiceRegistry
/** * 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
的put函数,这个动作是交由registerService()
来完成的
/** * 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);
}
SystemServiceRegistry
这个类中有一个静态代码块,是用来完成所有服务的注册的,这里我们只关心LAYOUT_INFLATER_SERVICE
对应的服务是如何注册的
static{
...
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher<LayoutInflater>() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
...
}
正如我们之前所说,这里最终是创建了一个PhoneLayoutInflater
并返回的,到这里LayoutInflater的创建流程就分析完了
思考
为什么要交由系统服务来做,而不是直接创建一个PhoneLayoutInflater
的实例对象?
LayoutInflater布局的实例化
整体流程
实例化的调用流程我们都很熟悉了,调用layoutInflater
的inflater()
函数,传入一个xml的resId
参数就可以了
重要函数解析
inflate
这个函数就是我们把Xml布局文件实例化为一个View对象的入口了
LayoutInflater
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) + ")");
}
View view = tryInflatePrecompiled(resource, res, root, attachToRoot); //这段代码其实是必然返回null的,因为当前版本写死了预编译的Enable为false
if (view != null) {
return view;
}
XmlResourceParser parser = res.getLayout(resource); //获取XmlBlock.Parser对象
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
此处又调用了inflate(parser, root, attachToRoot)
这个函数,来对Xml布局进行解析
这里看到对一些熟悉的标签,比如include
,merge
,的处理,具体细节请看下面的源码及注释
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {//XmlPullParser是一个接口
//此函数是真正执行将xml解析为视图view的过程,此处的parser为根据xml布局获取到的parser对象
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
final Context inflaterContext = mContext;
final inflateAttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root; //需要返回的view对象
try {
advanceToRootNode(parser); //对START_TAG和END_TAG进行判断和处理
final String name = parser.getName(); //获取当前标签
if (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
}
if (TAG_MERGE.equals(name)) { //如果使用merge标签
if (root == null || !attachToRoot) { //使用merge标签必须有父布局,且依赖于父布局加载,否则就会抛出异常
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);//递归(Recursive)生成布局视图
} else { //如果不使用merge标签,创建tmp 作为临时的根节点,并最终赋值给result返回
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, inflaterContext, attrs); ////根据标签名创建一个view
ViewGroup.LayoutParams params = null;
if (root != null) { //如果rootView不为空
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs); //根据rootView生成layoutparams
if (!attachToRoot) { //如果attachToRoot为false
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params); //设置一个临时的params
}
}
if (DEBUG) {
System.out.println("-----> start inflating children");
}
// Inflate all children under temp against its context.
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不为空,且attachToRoot为true
root.addView(temp, params); //把temp添加到到rootview中
}
// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) { // 如果root为空且attachToRoot为false
result = temp; //将temp,也就是根结点的View赋值给result
}
}
}
...
return result; //返回结果
}
}
rInflate
从上面的代码中我们也可以看到,不管是merge标签,还是非merge标签,最终都会调用到rInflate()
这个函数,这个是用于递归向下遍历xml布局,最终调用createViewFromTag()
函数来反射创建View对象
具体细节请看下面的源码及注释
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)) { //如果需REQUEST_FOCUS标签
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); //对 include标签进行解析
} 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); //根据Tag创建view,反射创建View
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflateChildren(parser, view, attrs, true); //递归调用rInflate函数
viewGroup.addView(view, params);
}
}
if (pendingRequestFocus) {
parent.restoreDefaultFocus();
}
if (finishInflate) {
parent.onFinishInflate();
}
}
createViewFromTag()
终于到了我们的重头戏,也是真正根据解析到的Tag标签去反射创建View的函数
LayoutInflater
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
// Apply a theme wrapper, if allowed and one is specified.
if (!ignoreThemeAttr) {
final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
final int themeResId = ta.getResourceId(0, 0);
if (themeResId != 0) {
context = new ContextThemeWrapper(context, themeResId);
}
ta.recycle();
}
try {
View view = tryCreateView(parent, name, context, attrs); //尝试使用Factory来闯将View对象
if (view == null) { //如果tryCreateView返回null
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
//sample:com.aiwinn.base.widget.CameraSurfaceView
if (-1 == name.indexOf('.')) { //如果当前Tag含有“.”
view = onCreateView(context, parent, name, attrs);
} else {
view = createView(context, name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
}
...
}
在这个函数中会首先调用tryCreateView()
来获取View对象,如果为null,则进一步调用createView()
函数来创建View对象
public final View createView(@NonNull Context viewContext, @NonNull String name,
@Nullable String prefix, @Nullable AttributeSet attrs)
throws ClassNotFoundException, InflateException {
//根据Tag反射创建view
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;
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
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);//把prefix和name进行拼接,获取到对应的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 {
...
}
Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = viewContext;
Object[] args = mConstructorArgs;
args[1] = attrs;
try {
final View view = constructor.newInstance(args); //根据获取到的构造器创建一个View的实例化对象
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;
}catch{
...
}
}
}
这里的代码其实在耗时上算是比较重量级了,因为是使用反射来创建的,一般的说法是,反射比直接创建对象要慢3倍,iReader
的x2c
框架就是基于这一点去做的优化
加餐
Resources的创建和获取
这里先获取到Resources
对象–mResources
,这个对象的创建是由createResources()
来完成的,这里最终是交由ResourcesManager
这个类来获取对应的resources
ContextImpl
private static Resources createResources(IBinder activityToken, LoadedApk pi, String splitName,
int displayId, Configuration overrideConfig, CompatibilityInfo compatInfo) {
final String[] splitResDirs;
final ClassLoader classLoader;
try {
splitResDirs = pi.getSplitPaths(splitName);
classLoader = pi.getSplitClassLoader(splitName);
} catch (NameNotFoundException e) {
throw new RuntimeException(e);
}
return ResourcesManager.getInstance().getResources(activityToken,
pi.getResDir(),
splitResDirs,
pi.getOverlayDirs(),
pi.getApplicationInfo().sharedLibraryFiles,
displayId,
overrideConfig,
compatInfo,
classLoader);
}
ResourcesManager
public @Nullable Resources getResources(@Nullable IBinder activityToken,
@Nullable String resDir,
@Nullable String[] splitResDirs,
@Nullable String[] overlayDirs,
@Nullable String[] libDirs,
int displayId,
@Nullable Configuration overrideConfig,
@NonNull CompatibilityInfo compatInfo,
@Nullable ClassLoader classLoader) {
try {
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources");
final ResourcesKey key = new ResourcesKey(
resDir,
splitResDirs,
overlayDirs,
libDirs,
displayId,
overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
compatInfo);
classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
return getOrCreateResources(activityToken, key, classLoader);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
ContextImpl
@Override
public Resources getResources() {
return mResources;
}
由这里我们也可以推断,LayoutInflater
交由服务来创建来创建,是因为其需要获取系统服务才能获取的某些资源
XmlBlock
inflate()
函数里还涉及到一个重要的类, XmlResourceParser
,这个类是负责对xml的标签进行遍历解析的,它的真正的实现类是XmlBlock
的内部类XmlBlock.Parser
,而真正完成xml的遍历操作的函数都是由XmlBlock
来实现的,为了提升效率,该函数都是通过JNI调用native的函数来做的,对应的native层是android_util_XmlBlock.cpp
XmlBlock.java
@FastNative
/*package*/ static final native int nativeNext(long state);
@FastNative
private static final native int nativeGetNamespace(long state);
@FastNative
/*package*/ static final native int nativeGetName(long state);
@FastNative
private static final native int nativeGetText(long state);
@FastNative
private static final native int nativeGetLineNumber(long state);
...
“android_util_XmlBlock.cpp`
static jint android_content_XmlBlock_nativeNext(JNIEnv* env, jobject clazz, jlong token) {
ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token);
if (st == NULL) {
return ResXMLParser::END_DOCUMENT;
}
do {
ResXMLParser::event_code_t code = st->next();
switch (code) {
case ResXMLParser::START_TAG:
return 2;
case ResXMLParser::END_TAG:
return 3;
case ResXMLParser::TEXT:
return 4;
case ResXMLParser::START_DOCUMENT:
return 0;
case ResXMLParser::END_DOCUMENT:
return 1;
case ResXMLParser::BAD_DOCUMENT:
goto bad;
default:
break;
}
} while (true);
bad:
jniThrowException(env, "org/xmlpull/v1/XmlPullParserException",
"Corrupt XML binary file");
return ResXMLParser::BAD_DOCUMENT;
}
tryInflatePrecompiled
这个函数是Android10的源码里面新增的一个函数,是用来根据xml预编译生成的dex,通过反射来生成对应的View,从而减少XmlPullParser解析Xml的时间 — 放到编译期来进行– 的一个优化 ,而反射获取对应的View时可以直接获取到预编译的View对象,而不需要递归调用rInflate
这里基本上就是真正彻底解决了复杂布局导致的卡顿问题
View tryInflatePrecompiled(@LayoutRes int resource, Resources res, @Nullable ViewGroup root, boolean attachToRoot) {
if (!mUseCompiledView) {
return null;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate (precompiled)");
// Try to inflate using a precompiled layout.
String pkg = res.getResourcePackageName(resource);
String layout = res.getResourceEntryName(resource);
//依然是通过反射的方式,根据已经创建的mPrecompiledClassLoader来反射生成view对象
try {
Class clazz = Class.forName("" + pkg + ".CompiledView", false, mPrecompiledClassLoader); //获取到预编译生成的view对象的Class类
Method inflater = clazz.getMethod(layout, Context.class, int.class);
View view = (View) inflater.invoke(null, mContext, resource);
if (view != null && root != null) {
// We were able to use the precompiled inflater, but now we need to do some work to
// attach the view to the root correctly.
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;
} catch (Throwable e) {
if (DEBUG) {
Log.e(TAG, "Failed to use precompiled view", e);
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
return null;
}
写在最后
下一篇文章,我们会提出一些优化方案,来解决(或者说)减缓复杂布局产生的卡顿问题,敬请期待!
参考文章:
1. https://www.reddit.com/r/androiddev/comments/3sekn8/lets_party_like_its_1995_from_the_layoutinflater/
2. https://www.cnblogs.com/liyilin-jack/p/10282385.html
3. https://blog.csdn.net/axi295309066/article/details/60128009
4. https://github.com/RTFSC-Android/RTFSC/blob/master/BlinkLayout.md
5. https://juejin.cn/post/6844903785219751944
郑重声明
本文版权归Android研习社
所有,未经允许禁止转载,侵权必究!
今天的文章「Android10源码分析」为什么复杂布局会产生卡顿?– LayoutInflater详解分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/14535.html