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 標簽以及自定義標簽
真講完了~~~

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容