天天看點

Activity布局加載流程

一、布局加載流程

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //分析->布局檔案的加載流程,最終調用的是 PhoneWindow.setContentView(int layoutResID)
        setContentView(R.layout.activity_main);
    }
}
           

二、分析步驟

1.PhoneWindow.setContentView(int layoutResID)

@Override
public void setContentView(int layoutResID) {
    if (mContentParent == null) {
        // 步驟2
        installDecor();
    }
    ...
    // 通過 LayoutInflater 把 Activity 的布局檔案中的内容添加到系統布局中一個 id 為 content 的 FrameLayout(即 mContentParent )中
    mLayoutInflater.inflate(layoutResID, mContentParent);        
}
           

2.PhoneWindow.installDecor()

private void installDecor() {
    if (mDecor == null) {
        // 步驟3->建立 DecorView
        // DecorView 繼承 FrameLayout,是視窗的頂級視圖,是 Activity 的根布局,包含一個 TitleView 和 ContentView
        mDecor = generateDecor(-1);
    } else {
        mDecor.setWindow(this);
    }
    if (mContentParent == null) {
        // 步驟4->初始化 mContentParent
        mContentParent = generateLayout(mDecor);
    }
}
           

3.PhoneWindow.generateDecor(int featureId)

protected DecorView generateDecor(int featureId) {
    Context context;
    // 調用 Activity 的 setContentView() 方法時為 true
    if (mUseDecorContext) {
        Context applicationContext = getContext().getApplicationContext();
        if (applicationContext == null) {
            context = getContext();
        } else {
            context = new DecorContext(applicationContext, getContext().getResources());
            if (mTheme != -1) {
                context.setTheme(mTheme);
            }
        }
    } else { // 對應 Application 的 Context
        context = getContext();
    }
    // DecorView 繼承了 FrameLayout
    return new DecorView(context, featureId, this, getAttributes());
}
           

4.PhoneWindow.generateLayout(DecorView decor)

protected ViewGroup generateLayout(DecorView decor) {
    // Apply data from current theme.
    
    TypedArray a = getWindowStyle();
    
    if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
        requestFeature(FEATURE_NO_TITLE);
    } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
        // Don't allow an action bar if there is no title.
        requestFeature(FEATURE_ACTION_BAR);
    }
    
    // Inflate the window decor.
    //做一系列的判斷,去加載系統的 layout 資源檔案
    int layoutResource;
    int features = getLocalFeatures();
    
    //各種判斷加載系統布局
    if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
        // If no other features and not embedded, only need a title.
        // If the window is floating, we need a dialog layout
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                    R.attr.dialogTitleDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
            layoutResource = a.getResourceId(
                    R.styleable.Window_windowActionBarFullscreenDecorLayout,
                    R.layout.screen_action_bar);
        } else {
            layoutResource = R.layout.screen_title;
        }
        // System.out.println("Title!");
    } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
        layoutResource = R.layout.screen_simple_overlay_action_mode;
    } else {
        // Embedded, so no decoration is needed.
        layoutResource = R.layout.screen_simple;
        // System.out.println("Simple!");
    }
    
    mDecor.startChanging();
	// 步驟5->把系統布局加載到 DecorView 中
	mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
    
    /**
      * The ID that the main layout in the XML layout file should have.
      * public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
      *
      * 找一個 id 為 content 的 FrameLayout,Activity 對應的布局内容就是添加在這個 FrameLayout 中
      */
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    if (contentParent == null) {
        throw new RuntimeException("Window couldn't find content container view");
    }

    mDecor.finishChanging();

    // 傳回 contentParent
    return contentParent;
}
           

5.PhoneWindow.onResourcesLoaded(LayoutInflater inflater, int layoutResource)

// 将得到的布局檔案加載到 DecorView 中
    void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
        ...
        // 擷取 DecroView 的标題視圖 mDecorCaptionView 
        mDecorCaptionView = createDecorCaptionView(inflater);
        // 解析、執行個體化系統的布局(重點)
        final View root = inflater.inflate(layoutResource, null);
        // 标題視圖 mDecorCaptionView 不為空
        if (mDecorCaptionView != null) {
            if (mDecorCaptionView.getParent() == null) {
                addView(mDecorCaptionView,
                        new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            }
            // 把系統的布局加入到 DecorView 中(寬高都是 MATCH_PARENT)
            mDecorCaptionView.addView(root,
                    new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
        } else { // 标題視圖 mDecorCaptionView 為空

            // 把系統的布局加入到 DecorView 中(寬高都是 MATCH_PARENT)
            // Put it below the color views.
            addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mContentRoot = (ViewGroup) root;
    }
           

三、總結

1.Activity.setContentView() 實作了把布局檔案中對應的内容加載到 DecroView 中

2.contentParent 是一個 id 為 content 的 FrameLayout,Activity 對應的布局内容就是添加在這個 FrameLayout 中

四、注意

在 Activity 的 onCreate()、onResume() 中,不能直接擷取 View 的寬高,因為 View 需要 onMeasure() 之後才能擷取到真實寬高,onMeasure() 在 Activity 的 onResume() 之後執行的