Android之解密setContentView

解密setContentView

我們先來看一下Android中View視圖在Activity中的整個層級關係:
在這裏插入圖片描述

包含關係:Activity中有個成員變量Window,Window是個抽象類,它的實現類是PhoneWindow,PhoneWindow有一個成員變量DecorView.

Phonewindow對象創建的開始

簡要說一下整個調用流程:

入口:ActivityThread#handleLaunchActivity() ->ActivityThread#performLaunchActivity()

開始創建PhoneWindow : performLaunchActivity() -> Activity.attach()

後續的DecorView的創建主要在SetContentView中,後面會分析到.

// Activity類
final void attach(...) {
        attachBaseContext(context);
        mFragments.attachHost(null /*parent*/);
        // 創建PhoneWindow
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        ... 
}

setContentView登場

學過Android的都知道,setContentView通常會在Acvitity中的Oncreate方法中調用,那麼SetContentView到底做了什麼呢?

DecorView的創建

DecorView創建來龍去脈 Activity#setConTentView -> PhoneWindow#setConTentView -> PhoneWindow#install -> PhoneWindow#generateDecor

代碼調用鏈如下:

// Activity類
public void setContentView(@LayoutRes int layoutResID) {
        // 獲取PhoneWindow對象,調用PhoneWindow中的SetContentView方法
        getWindow().setContentView(layoutResID);
        // 初始化ActionBar
        initWindowDecorActionBar();
    }
    
// PhoneWindow類
@Override
    public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            // 創建DecorView對象實例
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }
       if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            // 核心代碼,後面重點講這裏面幹了什麼
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
      ...
    }
private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            // 真正創建decorView的地方
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }
        ...
}
protected DecorView generateDecor(int featureId) {
        Context context;
        if (mUseDecorContext) {
            Context applicationContext = getContext().getApplicationContext();
            if (applicationContext == null) {
                context = getContext();
            } else {
                context = new DecorContext(applicationContext, getContext());
                if (mTheme != -1) {
                    context.setTheme(mTheme);
                }
            }
        } else {
            context = getContext();
        }
        // 返回創建的DecorView對象,至此DecorView創建完畢
        return new DecorView(context, featureId, this, getAttributes());
    }
    

LayoutInflater.inflate(layoutResID, mContentParent)做了什麼

參數:

  • 第一個參數layoutResId:資源文件id(對應xxx.xml)

  • 第二個參數mContentParent: viewGroup對象,表示創建的view位於viewGroup下

邏輯處理過程如下:

// LayoutInflater類
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
    // 第三個參數表示創建的view是否依附於viewGroup下
    return inflate(resource, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                    + Integer.toHexString(resource) + ")");
        }
        // 解析xml,返回XmlResourceParser對象,通過該對象可以讀取xml中的view定義
        final XmlResourceParser parser = res.getLayout(resource);
        try {
            // 通過XmlResourceParser創建出一個view層級(一棵view數)
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            ...
            ...
            // 獲取xml中所有的屬性
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            mConstructorArgs[0] = inflaterContext;
            View result = root;
            // Temp is the root view that was found in the xml
            // 創建,取得當前xml的根view
            final View temp = createViewFromTag(root, name, inflaterContext, attrs);
            ...
            ...
            ...
            // Inflate all children under temp against its context.
            // 創建根view下的子view
            rInflateChildren(parser, temp, attrs, true);
            // We are supposed to attach all the views we found (int temp)
            // to root. Do that now.
            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.
            if (root == null || !attachToRoot) {
                result = temp;
            }
            return result;
        }
    }

final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
            boolean finishInflate) throws XmlPullParserException, IOException {
        rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
    }

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) {
            if (type != XmlPullParser.START_TAG) {
                continue;
            }
            final String name = parser.getName();
            if (TAG_REQUEST_FOCUS.equals(name)) {
                pendingRequestFocus = true;
                consumeChildElements(parser);
            } 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 {
                // 此處遞歸創建子view,最終會得到一棵view樹
                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);
            }
        }

        if (pendingRequestFocus) {
            parent.restoreDefaultFocus();
        }

        if (finishInflate) {
            parent.onFinishInflate();
        }
    }

總結一下 LayoutInflater.inflate一共做了什麼:

解析xml得到XmlPullParser對象 -> 通過XmlPullParser對象創建出viewRoot -> 把創建處理的view添加到父view中 -> 遞歸創建子view並把創建處理的view添加到父view中,最後得到一棵viewTree

以上就是SetContentView的所有過程
更多Android分享關注公衆號:Android開發
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章