1. 程式人生 > >android setContentView原始碼分析

android setContentView原始碼分析

         Activity是android的四大元件之一,其重要性不言而喻,而且在我們的開發過程中打交道最多的也是它。在設定檢視的時候,我們一般都是通過setContentView來載入我們的佈局資源的,看起來很簡單的一行程式碼setContentView(),但是實際上裡面都做了哪些事情你真的知道嗎?

        

在開始講解setContentView的原始碼之前,你首先要弄懂上面的這張圖,弄不懂也沒關係,但是你需要記住它,這樣有利於後面的原始碼理解。

原始碼解析

@Override
public void setContentView(int layoutResID) {
        // mContentParent即DecorView
        // mContentParent是否為空,第一次進來的時候為空
        if (mContentParent == null) {
            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);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
}
private void installDecor() {
        mForceDecorInstall = false;
        // mDecor是否為空,第一次進來的時候為空
        if (mDecor == null) {
            // 初始化DecorView
            mDecor = generateDecor(-1);
            // 省略部分程式碼
        } else {
            mDecor.setWindow(this);
        }
        // mContentParent是否為空,第一次進來的時候為空
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
            // 省略部分程式碼
        }
}
// 初始化DecorView
protected DecorView generateDecor(int featureId) {
    // 省略部分程式碼
    // 建立DecorView
    return new DecorView(context, featureId, this, getAttributes());
}
protected ViewGroup generateLayout(DecorView decor) {
     TypedArray a = getWindowStyle();
 
    // 設定 當前Activity配置的主題theme
	// 視窗是否是浮動的
	mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
	int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
			& (~getForcedWindowFlags());
	if (mIsFloating) {
		setLayout(WRAP_CONTENT, WRAP_CONTENT);
		setFlags(0, flagsToUpdate);
	} else {
		setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
	}
	// 視窗是否有標題欄
	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);
	}
	// 視窗中ActionBar是否在佈局空間內
	if (a.getBoolean(R.styleable.Window_windowActionBarOverlay, false)) {
		requestFeature(FEATURE_ACTION_BAR_OVERLAY);
	}
 
	if (a.getBoolean(R.styleable.Window_windowActionModeOverlay, false)) {
		requestFeature(FEATURE_ACTION_MODE_OVERLAY);
	}
 
	if (a.getBoolean(R.styleable.Window_windowSwipeToDismiss, false)) {
		requestFeature(FEATURE_SWIPE_TO_DISMISS);
	}
	// 視窗是否全屏顯示
	if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
		setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
	}
 
	... ...
   
	//視窗狀態列顏色配置
	if (!mForcedStatusBarColor) {
		mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, 0xFF000000);
	}
	//視窗導航欄顏色配置
	if (!mForcedNavigationBarColor) {
		mNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0xFF000000);
	}
 
	if (mAlwaysReadCloseOnTouchAttr || getContext().getApplicationInfo().targetSdkVersion
			>= android.os.Build.VERSION_CODES.HONEYCOMB) {
		if (a.getBoolean(
				R.styleable.Window_windowCloseOnTouchOutside,false)) {
			setCloseOnTouchOutsideIfNotSet(true);
		}
	}
 
	WindowManager.LayoutParams params = getAttributes();
	//輸入法配置
	if (!hasSoftInputMode()) {
		params.softInputMode = a.getInt(
				R.styleable.Window_windowSoftInputMode,
				params.softInputMode);
	}
 
    if (a.getBoolean(R.styleable.Window_backgroundDimEnabled, mIsFloating)) {
            /* All dialogs should have the window dimmed */
            if ((getForcedWindowFlags()&WindowManager.LayoutParams.FLAG_DIM_BEHIND) == 0) {
                params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
            }
            if (!haveDimAmount()) {
                params.dimAmount = a.getFloat(
                        android.R.styleable.Window_backgroundDimAmount, 0.5f);
            }
        }
        //設定當前Activity的出現動畫效果
        if (params.windowAnimations == 0) {
            params.windowAnimations = a.getResourceId(
                    R.styleable.Window_windowAnimationStyle, 0);
        }
 
        // The rest are only done if this window is not embedded; otherwise,
        // the values are inherited from our container.
        if (getContainer() == null) {
            if (mBackgroundDrawable == null) {
                if (mBackgroundResource == 0) {
                    mBackgroundResource = a.getResourceId(
                            R.styleable.Window_windowBackground, 0);
                }
                if (mFrameResource == 0) {
                    mFrameResource = a.getResourceId(R.styleable.Window_windowFrame, 0);
                }
                mBackgroundFallbackResource = a.getResourceId(
                        R.styleable.Window_windowBackgroundFallback, 0);
                if (false) {
                    System.out.println("Background: "
                            + Integer.toHexString(mBackgroundResource) + " Frame: "
                            + Integer.toHexString(mFrameResource));
                }
            }
            mElevation = a.getDimension(R.styleable.Window_windowElevation, 0);
            mClipToOutline = a.getBoolean(R.styleable.Window_windowClipToOutline, false);
            mTextColor = a.getColor(R.styleable.Window_textColor, Color.TRANSPARENT);
        }
	}
 
	// 為視窗新增 decor根佈局
    // Inflate the window decor.
 
    int layoutResource;
    int features = getLocalFeatures();
    if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                    com.android.internal.R.attr.dialogTitleIconsDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else {
            layoutResource = com.android.internal.R.layout.screen_title_icons;
        }
        // XXX Remove this once action bar supports these features.
        removeFeature(FEATURE_ACTION_BAR);
        // System.out.println("Title Icons!");
    } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
        && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
        // Special case for a window with only a progress bar (and title).
        // XXX Need to have a no-title version of embedded windows.
        layoutResource = com.android.internal.R.layout.screen_progress;
        // System.out.println("Progress!");
    } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
        // Special case for a window with a custom title.
        // If the window is floating, we need a dialog layout
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                    com.android.internal.R.attr.dialogCustomTitleDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else {
            layoutResource = com.android.internal.R.layout.screen_custom_title;
        }
        // XXX Remove this once action bar supports these features.
        removeFeature(FEATURE_ACTION_BAR);
    } else 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(
                    com.android.internal.R.attr.dialogTitleDecorLayout, res, true);
            layoutResource = res.resourceId;
         } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
             if ((features & (1 << FEATURE_ACTION_BAR_OVERLAY)) != 0) {
                layoutResource = com.android.internal.R.layout.screen_action_bar_overlay;
             } else {
                layoutResource = com.android.internal.R.layout.screen_action_bar;
             }
         } else {
            layoutResource = com.android.internal.R.layout.screen_title;
         }
         // System.out.println("Title!");
         } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
             layoutResource = com.android.internal.R.layout.screen_simple_overlay_action_mode;
         } else {
             layoutResource = com.android.internal.R.layout.screen_simple;
         }
 
         mDecor.startChanging();
 
		 // 通過佈局填充器LayoutInflater將layoutResource填充為佈局,載入到decor上
         View in = mLayoutInflater.inflate(layoutResource, null);
         decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
 
		 // 找到ID為com.android.internal.R.id.content的ViewGroup佈局並進行返回
         ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
         if (contentParent == null) {
             throw new RuntimeException("Window couldn't find content container view");
         }
 
         if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
            ProgressBar progress = getCircularProgressBar(false);
            if (progress != null) {
                progress.setIndeterminate(true);
            }
        }
 
    // Remaining setup -- of background and title -- that only applies
    // to top-level windows.
	// 設定視窗的背景標題等
    if (getContainer() == null) {
            Drawable drawable = mBackgroundDrawable;
        if (mBackgroundResource != 0) {
            drawable = getContext().getResources().getDrawable(mBackgroundResource);
        }
        mDecor.setWindowBackground(drawable);
        drawable = null;
        if (mFrameResource != 0) {
            drawable = getContext().getResources().getDrawable(mFrameResource);
        }
        mDecor.setWindowFrame(drawable);
 
        if (mTitleColor == 0) {
            mTitleColor = mTextColor;
        }
 
        if (mTitle != null) {
            setTitle(mTitle);
        }
        setTitleColor(mTitleColor);
    }
 
    mDecor.finishChanging();
    return contentParent;
}

在這裡先總結一下:

1.我們知道在Activity中設定setContentView()首先會去執行到Activity的setContentView()方法

2.而Activity的setContentView會去執行到Window類裡面,而Window裡面的setContentView()方法是個抽象方法,最終會執行到Window的具體類PhoneWindow裡面的setContentView()方法裡面

3.在PhoneWinow裡面首先會去判斷mContentParent是否為空,那麼mContentParent是什麼呢?其實mContentParent是DecorView類的根佈局,而DecorView是PhoneWindow的一個內部類

4.當mContentParent為空的時候,會去例項化DecorView,同時在例項化DecorView的時候會去設定狀態列的一些屬性並且找到一個ID為com.android.internal.R.id.content的ViewGroup佈局並進行返回

5.最終執行到setContentView()方法的第16行mLayoutInflater.inflate(layoutResID, mContentParent);其中layoutResID為我們傳遞進去的佈局檔案,mContentParent為ID為com.android.internal.R.id.content的ViewGroup

那麼mLayoutInflater.inflate(layoutResID, mContentParent);最終會執行到哪裡呢?

檢視原始碼知道,最終會執行到LayoutInflater的inflate方法裡面

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) + ")");
        }
        // XmlResourceParser是專門用來解析XML檔案的
        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {
            // 省略部分程式碼
            
            // attrs是傳入的佈局layout在xml檔案裡面設定的屬性集合
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            
            // 將ViewGroup型別的引數root賦值給result
            View result = root;

            // temp是傳入的引數resource的根佈局View
           final View temp = createViewFromTag(root, name, inflaterContext, attrs);
 
 
           ViewGroup.LayoutParams params = null;
 
 
           // 例項化temp檢視內的所有子檢視
           rInflateChildren(parser, temp, attrs, true);
 
 
           if (root != null) {
              // 根據attrs屬性集,建立佈局引數params
              params = root.generateLayoutParams(attrs);
	          // 如果temp不需要新增到root中,那麼為temp設定佈局引數params
              if (!attachToRoot) {
                  temp.setLayoutParams(params);
                }
            }
 
 
            if (root != null && attachToRoot) {
                 // 將temp新增到root中,並使用佈局引數params
                 root.addView(temp, params);
            }
 
 
            if (root == null || !attachToRoot) {
                  // 將temp賦值給result(在此之前,result==root)
                  result = temp;
            }
            return result;
    }
}

到這裡setContentView就分析完了,現在我們回過頭來看看setContentView()方法到底做了什麼?

其實答案很簡單,setContentView()方法只是載入了我們設定的佈局檔案並對裡面的屬性進行了解析,其他的事情什麼都沒做,那麼我們設定的佈局是如何顯示在介面上的呢?

哈哈哈,相信大家都已經猜到了,最終的佈局繪製是通過Activity的生命週期來實現的,那麼下結我們就一起來分析一下View的繪製流程