Android源码–深入LayoutInflater一探究竟(基于android-12.0.0_r34分析)
前文:
单例设计模式在Android开发实际应用场景解析–activity的管理
https://blog.csdn.net/weixin_46039528/article/details/132287718?spm=1001.2014.3001.5501
Android源码设计模式–单例模式分析,系统服务开机注册单例模式源码解析
https://blog.csdn.net/weixin_46039528/article/details/132309733?spm=1001.2014.3001.5501
LayoutInflater 是一个抽象类,找到它具体的实现类
@SystemService(Context.LAYOUT_INFLATER_SERVICE)
public abstract class LayoutInflater {......}
系统开机注册
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher<LayoutInflater>() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
跟进PhoneLayoutInflater,这里看到PhoneLayoutInflater是LayoutInflater的一个实现类。
/**
* @hide
*/
public class PhoneLayoutInflater extends LayoutInflater {
//TextView 的完整路径是 android.widget.TextView,一些view的前缀字符串
private static final String[] sClassPrefixList = {
"android.widget.",
"android.webkit.",
"android.app."
};
......
/** Override onCreateView to instantiate names that correspond to the
widgets known to the Widget factory. If we don't find a match,
call through to our super class.
*/
@Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
//在View名字的前面添加前缀来构造 View 的完整路径,android.widget.TextView
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.
}
}
......
}
核心就是onCreateView通过传进来的名字,name,prefix前缀构造出对应的View对象,比如TextView。
接下来我们康康AppCompatActivity的setContentView,它的核心就是通过LayoutInflater来加载我们的布局。AppCompatActivity 的 setContentView 方法和 Activity的 setContentView 方法是有区别的。因为我们都是继承AppCompatActivity开发较多,此处只对AppCompatActivity分析。
@Override
public void setContentView(@LayoutRes int layoutResID) {
initViewTreeOwners();
//getDelegate返回的是一个对象。
getDelegate().setContentView(layoutResID);
}
跟进getDelegate,看到返回的是AppCompatDelegate,这是一个抽象类。
/**
* @return The {@link AppCompatDelegate} being used by this Activity.
*/
@NonNull
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
我们来看 AppCompatDelegate 的 create(Activity activity, AppCompatCallback callback) 方法,
/**
* Create an {@link androidx.appcompat.app.AppCompatDelegate} to use with {@code dialog}.
*
* @param callback An optional callback for AppCompat specific events
*/
@NonNull
public static AppCompatDelegate create(@NonNull Dialog dialog,
@Nullable AppCompatCallback callback) {
//AppCompatDelegateImpl是它的一个实现类,就是它调用了setContentView
return new AppCompatDelegateImpl(dialog, callback);
}
跟进到AppCompatDelegateImpl中的,有三个重载方法
......
@Override
public void setContentView(int resId) {
//初始化DecorView
ensureSubDecor();
//设置setContentView 要的父布局
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
//加载之前移除所有的布局
contentParent.removeAllViews();
//完成布局的加载
LayoutInflater.from(mContext).inflate(resId, contentParent);
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
......
我们看到会加载一个系统定义好的布局contentParent,通过 LayoutInflater 的 inflate 函数将指定布局的视图添加到mContentParent 中。
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
......
// 获取xm1资源解析器
XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
// 参数1为xmL 解析器,参数2为要解析布局的父视图,参数3为是否将要解析的视图添加到父视图中
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;
......
//解析merge 标签
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
//不是merge 标签那么直接解析布局中的视图
//这里就是通过 xml的 tag 来解析 layout 根视图
//name 就是要解析的视图的类名,如 RelativeLayout
//createViewFromTag会将该元素的parent 及名字传递过来
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);
//如果attachToRoot 为 false,给 temp 设置布局参数
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}
......
// Inflate all children under temp against its context.
//解析 temp 下的所有子view
rInflateChildren(parser, temp, attrs, true);
......
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
//如果Root 不为空,attachToRoot 为true,将 temp 添加到父视图中
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.
//如果root 为空或者attachToRoot 为 false,结果就是 temp
if (root == null || !attachToRoot) {
result = temp;
}
}
......
return result;
}
}
大致流程:解xml中的根标签,最外层的xml布局元素,如果根标签是 merge,那么调用rInflate进行解析,rnflate会将 merge 标签下的所有子View直接添加到根标签中,如果标签是普通元素,调用createViewFromTag 对该元素进行解析,调用rInflate解析 temp 根元素下的所有子 View,并且将这些子 View 都添加到temp下。
解析单个元素的createViewFromTag
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
......
try {
//跟进这个方法
View view = tryCreateView(parent, name, context, attrs);
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
//安卓系统自带View控件的创建解析
if (-1 == name.indexOf('.')) {
view = onCreateView(context, parent, name, attrs);
} else {
//自定义控件创建解析
view = createView(context, name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
......
}
跟进tryCreateView
@UnsupportedAppUsage(trackingBug = 122360734)
@Nullable
public final View tryCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context,
@NonNull AttributeSet attrs) {
......
View view;
//通过onCreateView创建View
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
......
return view;
}
最终我们的view都是通过createView方法创建的。文章来源:https://www.toymoban.com/news/detail-674547.html
//通过反射机制构造 view对象
@Nullable
public final View createView(@NonNull Context viewContext, @NonNull String name,
@Nullable String prefix, @Nullable 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 {
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
//如果 prefix不为空,那么构造完整的 View 路径加载该类
clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
mContext.getClassLoader()).asSubclass(View.class);
......
//从class对象中获取构造函数
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
//将构造函数存入缓存中
sConstructorMap.put(name, constructor);
} else {
......
}
......
try {
//通过反射构造View
final View view = constructor.newInstance(args);
......
}
这只是单个View的xml解析,要把子View全部解析完,我们看rInflate。文章来源地址https://www.toymoban.com/news/detail-674547.html
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) {
......
} 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);
}
}
......
}
到了这里,关于Android布局填充器--深入LayoutInflater一探究竟的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!