LayoutInflater 是如何解析各种标签的
本文是 LayoutInflater 详解(一)的续篇,讲解 xml 中 include 和 fragment 标签的解析
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 {
// ...
}
在上一篇的时候,本打算把 LayoutInflater 所有东西都讲完,但是写着写着,发现文章的篇幅已经很大了,想写的东西还有好多,所以决定分开来写
接着看代码,从上面的代码看到,这里一共涉及到了四个 tag ,分别是 requestFocus 、tag 、include 以及 merge
TAG_REQUEST_FOCUS
private void parseRequestFocus(XmlPullParser parser, View view)
throws XmlPullParserException, IOException {
// 请求获取焦点
view.requestFocus();
// 这个函数啥都没做
consumeChildElements(parser);
}
TAG_TAG
private void parseViewTag(XmlPullParser parser, View view, AttributeSet attrs)
throws XmlPullParserException, IOException {
final Context context = view.getContext();
final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ViewTag);
// 获取 tag 的 id
final int key = ta.getResourceId(R.styleable.ViewTag_id, 0);
// 获取 tag 的 value
final CharSequence value = ta.getText(R.styleable.ViewTag_value);
// 把 tag 设置给 view
view.setTag(key, value);
ta.recycle();
consumeChildElements(parser);
}
TAG_INCLUDE
private void parseInclude(XmlPullParser parser, Context context, View parent,
AttributeSet attrs) throws XmlPullParserException, IOException {
int type;
if (parent instanceof ViewGroup) { // 父 view 必须是 ViewGroup
// start --- 判断是否有 theme 有过有则实例化一个带主题的 context
final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
final int themeResId = ta.getResourceId(0, 0);
final boolean hasThemeOverride = themeResId != 0;
if (hasThemeOverride) {
context = new ContextThemeWrapper(context, themeResId);
}
ta.recycle();
// end --- 主题
// 获取 include 中 layout 的资源 id
int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);
if (layout == 0) {
final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
if (value == null || value.length() <= 0) {
throw new InflateException("You must specify a layout in the"
+ " include tag: <include layout=\"@layout/layoutID\" />");
}
// 如果 上面的 id 是 0 则尝试获取 "?attr/name" 的id
// value.substring(1) 是去掉问号
layout = context.getResources().getIdentifier(value.substring(1), null, null);
}
if (mTempValue == null) {
mTempValue = new TypedValue();
}
if (layout != 0 && context.getTheme().resolveAttribute(layout, mTempValue, true)) {
layout = mTempValue.resourceId;
}
if (layout == 0) {
final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
throw new InflateException("You must specify a valid layout "
+ "reference. The layout ID " + value + " is not valid.");
} else {
// 注意从这里向下看
final XmlResourceParser childParser = context.getResources().getLayout(layout);
try {
final AttributeSet childAttrs = Xml.asAttributeSet(childParser);
while ((type = childParser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(childParser.getPositionDescription() +
": No start tag found!");
}
final String childName = childParser.getName();
if (TAG_MERGE.equals(childName)) {
rInflate(childParser, parent, context, childAttrs, false);
} else {
final View view = createViewFromTag(parent, childName,
context, childAttrs, hasThemeOverride);
final ViewGroup group = (ViewGroup) parent;
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.Include);
// 获取 include 标签中设置的 id
final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
// 获取 include 标签中设置的 visiblity
final int visibility = a.getInt(R.styleable.Include_visibility, -1);
a.recycle();
ViewGroup.LayoutParams params = null;
try {
params = group.generateLayoutParams(attrs);
} catch (RuntimeException e) {
}
if (params == null) {
params = group.generateLayoutParams(childAttrs);
}
view.setLayoutParams(params);
rInflateChildren(childParser, view, childAttrs, true);
// 从这里向上,代码很熟悉,与之前讲的 inflate 函数的代码几乎一致
if (id != View.NO_ID) {
view.setId(id);
}
switch (visibility) {
case 0:
view.setVisibility(View.VISIBLE);
break;
case 1:
view.setVisibility(View.INVISIBLE);
break;
case 2:
view.setVisibility(View.GONE);
break;
}
group.addView(view);
}
} finally {
childParser.close();
}
}
} else {
throw new InflateException("<include /> can only be used inside of a ViewGroup");
}
LayoutInflater.consumeChildElements(parser);
}
到这里,所有的标签都讲完了,但是有朋友可能会说:不对啊,merge 标签貌似还没有讲,我想说,骚年,注意看代码,当 tag 是 merge 的时候,实际调用的函数是 rInflate() 我们在前一篇文章已经讲过了的,那 fragment 标签呢?咳咳,这个确实还没有讲,好,下面开始讲
我在第一次看 LayoutInflater 的代码的时候是懵逼的,我擦,fragment 是怎么解析的?搜索关键字搜不到啊,其他几个 tag 都能搜到,直到我看到了 Factory 回调,这个问题豁然开朗,我们在上一篇文章中说过
final void attach(Context context, ...) {
// ...
mWindow.getLayoutInflater().setPrivateFactory(this);
// ...
}
Activity 在 attach() 中为 LayoutInflater 设置了一个 PrivateFactory ,我们发现 Activity 果然实现了 LayoutInflater.Factory2 接口
public class Activity extends ContextThemeWrapper implements LayoutInflater.Factory2, ... {
public View onCreateView(String name, Context context, AttributeSet attrs) {
return null;
}
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
if (!"fragment".equals(name)) {
return onCreateView(name, context, attrs);
}
return mFragments.onCreateView(parent, name, context, attrs);
}
}
这里调用了 FragmentController 的 onCreateView() 函数
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
return mHost.mFragmentManager.onCreateView(parent, name, context, attrs);
}
这里的 mFragmentManager 是 FragmentManagerImpl 类的一个实例,它也实现了 LayoutInflater.Factory2 接口
final class FragmentManagerImpl extends FragmentManager implements LayoutInflater.Factory2 {
// ...
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
if (!"fragment".equals(name)) {
return null;
}
// 获取 fragment 标签中 class 的值,即类名
String fname = attrs.getAttributeValue(null, "class");
TypedArray a =
context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Fragment);
if (fname == null) {
// 获取 name 的值
fname = a.getString(com.android.internal.R.styleable.Fragment_name);
}
// 获取 id 的值
int id = a.getResourceId(com.android.internal.R.styleable.Fragment_id, View.NO_ID);
// 获取 tag 的值
String tag = a.getString(com.android.internal.R.styleable.Fragment_tag);
a.recycle();
// 获取容器的 id
int containerId = parent != null ? parent.getId() : 0;
if (containerId == View.NO_ID && id == View.NO_ID && tag == null) {
throw new IllegalArgumentException(attrs.getPositionDescription()
+ ": Must specify unique android:id, android:tag, or have a parent with"
+ " an id for " + fname);
}
Fragment fragment = id != View.NO_ID ? findFragmentById(id) : null;
if (fragment == null && tag != null) {
fragment = findFragmentByTag(tag);
}
if (fragment == null && containerId != View.NO_ID) {
fragment = findFragmentById(containerId);
}
// 上面的 findFragmentByXXX() 不用看,因为必然是空
if (fragment == null) {
// 反射获取新的实例
fragment = Fragment.instantiate(context, fname);
fragment.mFromLayout = true;
fragment.mFragmentId = id != 0 ? id : containerId;
fragment.mContainerId = containerId;
fragment.mTag = tag;
fragment.mInLayout = true;
fragment.mFragmentManager = this;
// onInflate 里面生成一个 view
fragment.onInflate(mHost.getContext(), attrs, fragment.mSavedFragmentState);
addFragment(fragment, true);
} else if (fragment.mInLayout) {
throw new IllegalArgumentException(attrs.getPositionDescription()
+ ": Duplicate id 0x" + Integer.toHexString(id)
+ ", tag " + tag + ", or parent id 0x" + Integer.toHexString(containerId)
+ " with another fragment for " + fname);
} else {
fragment.mInLayout = true;
if (!fragment.mRetaining) {
fragment.onInflate(mHost.getContext(), attrs, fragment.mSavedFragmentState);
}
}
if (mCurState < Fragment.CREATED && fragment.mFromLayout) {
moveToState(fragment, Fragment.CREATED, 0, 0, false);
} else {
moveToState(fragment);
}
if (fragment.mView == null) {
throw new IllegalStateException("Fragment " + fname
+ " did not create a view.");
}
if (id != 0) {
fragment.mView.setId(id);
}
if (fragment.mView.getTag() == null) {
fragment.mView.setTag(tag);
}
// 把 fragment 里的 view 返回
return fragment.mView;
}
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
return null;
}
}
fragment 对象已经被创建出来了,但是我们并没有看到 view 是在哪里被 inflate 出来的,所以我们接着看 addFragment() 函数,它调用了 moveToState() 函数,
void moveToState(Fragment f, int newState, int transit, int transitionStyle, boolean keepActive) {
// ...
if (f.mFromLayout) {
f.mView = f.performCreateView(f.getLayoutInflater(
f.mSavedFragmentState), null, f.mSavedFragmentState);
if (f.mView != null) {
// ...
}
}
// ...
}
View performCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (mChildFragmentManager != null) {
mChildFragmentManager.noteStateNotSaved();
}
return onCreateView(inflater, container, savedInstanceState);
}
最后调用了 onCreateView() ,这正是我们写 Fragment 时,绝大多数情况要重写的函数,就是给它一个 View ,到这里,关于 LayoutInflater 已经都讲完了
BTW ,在看代码过程中发现了一个有趣的类 AsyncLayoutInflater 它的代码也不是很多
public final class AsyncLayoutInflater {
private static final String TAG = "AsyncLayoutInflater";
LayoutInflater mInflater;
Handler mHandler;
InflateThread mInflateThread;
public AsyncLayoutInflater(@NonNull Context context) {
mInflater = new BasicInflater(context);
mHandler = new Handler(mHandlerCallback);
mInflateThread = InflateThread.getInstance();
}
@UiThread
public void inflate(@LayoutRes int resid, @Nullable ViewGroup parent,
@NonNull OnInflateFinishedListener callback) {
if (callback == null) {
throw new NullPointerException("callback argument may not be null!");
}
// 获取一个 InflateRequest
InflateRequest request = mInflateThread.obtainRequest();
request.inflater = this;
request.resid = resid;
request.parent = parent;
request.callback = callback;
// 把 InflateRequest 加入队列
mInflateThread.enqueue(request);
}
// 子线程 inflate 完成后的 ui 回调
private Callback mHandlerCallback = new Callback() {
@Override
public boolean handleMessage(Message msg) {
InflateRequest request = (InflateRequest) msg.obj;
if (request.view == null) { // 如果子线程返回 null
// 回退到主线程进行 inflate ,注意第三个参数是 false
request.view = mInflater.inflate(
request.resid, request.parent, false);
}
// 执行 onInflateFinished() 回调
request.callback.onInflateFinished(
request.view, request.resid, request.parent);
mInflateThread.releaseRequest(request);
return true;
}
};
public interface OnInflateFinishedListener {
void onInflateFinished(View view, int resid, ViewGroup parent);
}
private static class InflateRequest {
AsyncLayoutInflater inflater;
ViewGroup parent;
int resid;
View view;
OnInflateFinishedListener callback;
InflateRequest() {
}
}
// 和 PhoneLayoutInflater 一样的写法
private static class BasicInflater extends LayoutInflater {
private static final String[] sClassPrefixList = {
"android.widget.",
"android.webkit.",
"android.app."
};
BasicInflater(Context context) {
super(context);
}
@Override
public LayoutInflater cloneInContext(Context newContext) {
return new BasicInflater(newContext);
}
@Override
protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
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);
}
}
private static class InflateThread extends Thread {
private static final InflateThread sInstance;
static { // 初始化的时候就开启线程
sInstance = new InflateThread();
sInstance.start();
}
public static InflateThread getInstance() {
return sInstance;
}
// 等待 inflate 的队列
private ArrayBlockingQueue<InflateRequest> mQueue = new ArrayBlockingQueue<>(10);
// 带同步锁的对象池
private SynchronizedPool<InflateRequest> mRequestPool = new SynchronizedPool<>(10);
@Override
public void run() {
while (true) { // 死循环
InflateRequest request;
try {
// 从队列中取一个 request ,mQueue 为空,会阻塞
request = mQueue.take();
} catch (InterruptedException ex) {
// Odd, just continue
Log.w(TAG, ex);
continue;
}
try { // inflate 布局,注意第三个参数是 false
request.view = request.inflater.mInflater.inflate(
request.resid, request.parent, false);
} catch (RuntimeException ex) {
// Probably a Looper failure, retry on the UI thread
Log.w(TAG, "Failed to inflate resource in the background! Retrying on the UI"
+ " thread", ex);
}
Message.obtain(request.inflater.mHandler, 0, request)
.sendToTarget();
}
}
public InflateRequest obtainRequest() {
InflateRequest obj = mRequestPool.acquire();
if (obj == null) {
obj = new InflateRequest();
}
return obj;
}
public void releaseRequest(InflateRequest obj) {
obj.callback = null;
obj.inflater = null;
obj.parent = null;
obj.resid = 0;
obj.view = null;
mRequestPool.release(obj);
}
public void enqueue(InflateRequest request) {
try {
// 向 mQueue 中添加一个 request ,会唤醒 thread 继续向下执行
mQueue.put(request);
} catch (InterruptedException e) {
throw new RuntimeException(
"Failed to enqueue async inflate request", e);
}
}
}
}
它的用法也很简单
new AsyncLayoutInflater(this).inflate(R.layout.activity_main, null,
new AsyncLayoutInflater.OnInflateFinishedListener() {
@Override
public void onInflateFinished(View view, int resid, ViewGroup parent) {
// 这里可能需要手动将 view 添加到 parent 中
}});
但是,它限制比较多,比如
1、 parent 的 generateLayoutParams() 函数必须是线程安全的。
2、 所有正在构建的 views 不能创建任何 Handlers 或者调用 Looper.myLooper 函数。
3、 不支持设置 LayoutInflater.Factory 也不支持 LayoutInflater.Factory2
4、 由于第三点,所以它不能解析 fragment 标签以及自定义标签
真讲完了~~~
网友评论