Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android setContentView 加載布局源碼解析

Android setContentView 加載布局源碼解析

編輯:關於Android編程

1,背景

作為Android 四大組件之一的Activity 在應用開發中在常見不過。
而回調Activity 生命周期的onCreat()以及加載布局的setContentView()我們更是耳聞熟詳。
但是我們卻很少真正去關注Activity的布局到底是怎樣被加載,又如何去顯示的。

2,源碼分析

2-1,典型使用

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home);
   }

這段代碼我們再熟悉不過。我們來看下Activity中setContentView()源碼實現

   /**
     * Set the activity content from a layout resource.  The resource will be
     * inflated, adding all top-level views to the activity.
     *
     * @param layoutResID Resource ID to be inflated.
     *
     * @see #setContentView(android.view.View)
     * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
     */
    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

其中getWindow() 返回局部變量mWindow;

  /**
     * Retrieve the current {@link android.view.Window} for the activity.
     * This can be used to directly access parts of the Window API that
     * are not available through Activity/Screen.
     *
     * @return Window The current window, or null if the activity is not
     *         visual.
     */
    public Window getWindow() {
        return mWindow;
    }

我們查找可知。mWindow 是在Activity的attach()方法中被實例化的。其實例化對象為PhoneWindow,PhoneWindow為抽象Window類的實現類。關於attach()方法,我們稍後會在activity的啟動中講到。在這我們只需要知道mWindow再此實例化就好。

2-2,這裡先簡要說下有關類的相關職責:

public abstract class Window {
public class PhoneWindow extends Window ...
private final class DecorView extends FrameLayout...

1,Window是abstract修飾。提供有關Window的通用方法

2,PhoneWindow是Window的實現,提供具體的操作邏輯

3,DecorView是PhoneWindow 中的內部類,繼承了FrameLayout,是所有應用窗口的根View 。

看下attach()方法


    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
        if (info.uiOptions != 0) {
            mWindow.setUiOptions(info.uiOptions);
        }
        mUiThread = Thread.currentThread();

        mMainThread = aThread;
        mInstrumentation = instr;
        mToken = token;
        mIdent = ident;
        mApplication = application;
        mIntent = intent;
        mReferrer = referrer;
        mComponent = intent.getComponent();
        mActivityInfo = info;
        mTitle = title;
 mWindow = new PhoneWindow(this);

我們可以發現,得知mWindow其實是PhoneWindow對象。
也就是說,我們的 getWindow().setContentView(layoutResID);其實就是調用PhoneWindow 的setContentView(layoutResID);方法。
有關PhoneWindow 這是個隱藏class. 需要在android sdk source中查看。路徑為
sdk/source/android-x/com/android/internal/policy/impl/PhoneWindow.java。

2-3,PhoneWindow.setContentView(layoutResID)實現

看下PhoneWindow 的setContentView(layoutResID)的實現


    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        // 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) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            view.setLayoutParams(params);
            final Scene newScene = new Scene(mContentParent, view);
            transitionTo(newScene);
        } else {
            mContentParent.addView(view, params);
        }
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }

繼續看下hasFeature(FEATURE_CONTENT_TRANSITIONS)

   public boolean hasFeature(int feature) {
        return (getFeatures() & (1 << feature)) != 0;
    }

getFeatures()

 protected final int getFeatures()
    {
        return mFeatures;
    }

而mFeatures默認是false,
首次執行mContentParent為null,執行installDecor()方法。否則移除mContentParent所有內部view,然後通過LayoutInflater.inflate將我們傳入的layout放置到mContentParent中。

分析下installDecor()干了什麼。

private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor();
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);

            // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
            mDecor.makeOptionalFitsSystemWindows();

            final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
                    R.id.decor_content_parent);

            if (decorContentParent != null) {
                mDecorContentParent = decorContentParent;
                mDecorContentParent.setWindowCallback(getCallback());
                if (mDecorContentParent.getTitle() == null) {

                        ....

這個方法有點長,同樣初次執行,mDecor為null,執行 mDecor = generateDecor(); 所以基本確定generateDecor() 就是實例化mDecor對象的。

  protected DecorView generateDecor() {
        return new DecorView(getContext(), -1);
    }

可以看見generateDecor方法僅僅是new一個DecorView的實例。
就往下分析。同樣初次執行mContentParent(mContentParent是一個ViewGroup)為null,執行generateLayout(mDecor);

 protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.

        TypedArray a = getWindowStyle();

        if (false) {
            System.out.println("From style:");
            String s = "Attrs:";
            for (int i = 0; i < R.styleable.Window.length; i++) {
                s = s + " " + Integer.toHexString(R.styleable.Window[i]) + "="
                        + a.getString(i);
            }
            System.out.println(s);
        }
    //style 樣式...
        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);
            .
            .
            .
              int layoutResource;
        int features = getLocalFeatures();
        // System.out.println("Features: 0x" + Integer.toHexString(features));
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            layoutResource = R.layout.screen_swipe_dismiss;
        } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleIconsDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = 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 = R.layout.screen_progress;
            .
            .
            .

        mDecor.startChanging();

        View in = mLayoutInflater.inflate(layoutResource, null);
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        mContentRoot = (ViewGroup) in;

        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);
            }
        }

        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            registerSwipeCallbacks();
        }
        ..
        .
        .

        mDecor.finishChanging();
        return contentParent;

額。好吧,又是個巨長的方法,我們挑重要的分析。
可以發現,在
mDecor.startChanging(); 之前基本都是要一系列的樣式
雖然不明白到底在干什麼。但是我們仔細分析一下,以及結合之前分析的DecorView 的作用裝飾布局的作用,結合下代碼

  View in = mLayoutInflater.inflate(layoutResource, null);

我們在看下layoutResource的取值范圍

 int layoutResource;
 ..
  if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            layoutResource = R.layout.screen_swipe_dismiss;
        } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleIconsDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_title_icons;
            }
   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(
                        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 {

基本可以得出結論:
上面方法主要作用就是根據窗口的風格修飾類型為該窗口選擇不同的窗口根布局文件。

 decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));

mDecor做為根視圖將該裝飾窗口根布局添加進去。
繼續分析

 ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

通過mDecor.findViewById傳入R.id.content(相信這個id大家或多或少都聽說過),返回mDecor(布局)中的id為content的View,一般為FrameLayout。

(在這你應該可以猜到。我們的setContentView 的布局是加入這個id 為content 的FrameLayout 中的)

然後返回這個contentParent

  mDecor.finishChanging();
  return contentParent;

2-4,顯示xml文件顯示到屏幕

在以上的分析中,我們大都分析了一下Window,PhoneWidow以及DecroView 的工作關系 以及各自的作用,DecroView 裝飾窗口風格的工作過程。
那麼我們的xml 文件是怎樣被添加到窗口布局上去的呢?
重點來了,之前的分析的代碼最終是返回了mDecor(布局)中的id為content的View,一般為FrameLayout。
我們在回顧一下,也就是PhoneWindow.setContentView()

  @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) {
            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);
        }

沒錯,就是 mLayoutInflater.inflate(layoutResID, mContentParent)。把我們的xml布局 inflate 到mContentParent 中。

3,總結

首先初始化mDecor,然後根據設置的屬性去得到不同的mDecor 裝飾窗口風格。通過infalter.inflater放入到我們的mDecor中
在這些布局中,一般會包含ActionBar,Title,和一個id為content的FrameLayout。
最後,我們在Activity中設置的布局,會通過infalter.inflater壓入到我們的id為content的FrameLayout中去。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved