美文网首页Android知识Android开发android技术专栏
LayoutInflater 详解(二)标签解析

LayoutInflater 详解(二)标签解析

作者: 为何是Hex的昵称 | 来源:发表于2017-03-13 13:58 被阅读455次

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

    相关文章

      网友评论

        本文标题:LayoutInflater 详解(二)标签解析

        本文链接:https://www.haomeiwen.com/subject/ewrogttx.html