LayoutInflater 详解(二)标签解析

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 标签以及自定义标签
真讲完了~~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容