Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android應用程序窗口View的創建過程

Android應用程序窗口View的創建過程

編輯:關於Android編程

View類是android中非常重要的一個類.view是應用程序界面的直觀體現,我們看到的應用程序界面就可以看作是View(視圖)組成的.

那麼我們應用程序的界面是怎麼創建的呢,也就是應用程序的View是什麼時候創建的?

在android中與界面直接相關的就是Activity了.

我們平時在Activity的onCreate()函數中,通過調用它的setContentView()函數,將我們應用程序的界面資源設置進去.然後運行程序就可以看到我們布局文件裡描述的界面了.

從我們調用setContentView()函數將界面資源設置進去,到運行完成界面完全顯示出來,其中經過了很多過程.

這裡我主要是通過源碼來分析一下其中最終界面到底是什麼樣的View? 然後分析一下View的measure,layout,draw過程.

因為我們設置界面是setContentView()中設置的,所以就從該函數開始來分析.

我們知道Activity的onCreate()函數最先被調用.第五十步

 private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
       ....
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } catch (Exception e) {
      ....
        }

        try {
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);
....
            if (activity != null) {
                Context appContext = createBaseContextForActivity(r, activity);
                CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
                Configuration config = new Configuration(mCompatConfiguration);
              ...
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config);

                if (customIntent != null) {
                    activity.mIntent = customIntent;
                }
                r.lastNonConfigurationInstances = null;
                activity.mStartedActivity = false;
                int theme = r.activityInfo.getThemeResource();
                if (theme != 0) {
                    activity.setTheme(theme);
                }

                activity.mCalled = false;
                mInstrumentation.callActivityOnCreate(activity, r.state);
             .....
    }

這裡首先創建Activity 的實例,然後mInstrumentation.callActivityOnCreate(activity, r.state)該函數最終就會調用Activity的onCreate()函數.

好了,看setContentView()函數

第一步:setContentView()

在rameworks/base/core/java/android/app/Activity.java中


public void setContentView(int layoutResID) {
        getWindow().setContentView(layoutResID);
        initActionBar();
    }
    public void setContentView(View view) {
        getWindow().setContentView(view);
        initActionBar();
    }
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        getWindow().setContentView(view, params);
        initActionBar();
    }

Activity中setContentView()函數有三個重載函數,一般用第一個比較多,這裡就按第一個繼續往下分析,其實它們最終實現都一樣.首先看getWindow()函數

第二步:getWindow()

在frameworks/base/core/java/android/app/Activity.java中

public Window getWindow() {
        return mWindow;
    }
Activity的成員變量mWindow是Window類型,它是什麼時候被賦值的呢?
這裡還是到分析點擊android桌面app圖標啟動應用程序的過程一文中第五十步看看、private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ....
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } catch (Exception e) {
       ...
        }

        try {
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);
...
            if (activity != null) {
                Context appContext = createBaseContextForActivity(r, activity);
                CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
                Configuration config = new Configuration(mCompatConfiguration);
            ...
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config);

           ....
    }
這裡創建Activity的實例後,就通過activity.attach()函數給activity內部變量賦值,所以進attach()函數裡面看看

第三步:attach()在rameworks/base/core/java/android/app/Activity.java中

 

 

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) {
       ...
        mWindow = PolicyManager.makeNewWindow(this);//
      ....
    }
mWindow = PolicyManager.makeNewWindow(this);這裡就是給activity中mWindow賦值.那繼續看PolicyManager.makeNewWindow(this)這個函數

 

第四步:makeNewWindow()

在frameworks/base/core/java/com/android/internal/policyPolicyManager.java中

 

 // The static methods to spawn new policy-specific objects
    public static Window makeNewWindow(Context context) {
        return sPolicy.makeNewWindow(context);
    }


 


public final class PolicyManager {
    private static final String POLICY_IMPL_CLASS_NAME =
        "com.android.internal.policy.impl.Policy";
    private static final IPolicy sPolicy;
    static {
        // Pull in the actual implementation of the policy at run-time
        try {
            Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
            sPolicy = (IPolicy)policyClass.newInstance();
        } catch (ClassNotFoundException ex) {
         ....
    }
這裡繼續調用sPolicy.makeNewWindow(context);由上面代碼可以知道這裡的sPolicy其實是Policy類型

第五步:makeNewWindow()

在frameworks/base/policy/src/com/android/internal/policy/impl/Policy.java中

 

public class Policy implements IPolicy {
   .
    public Window makeNewWindow(Context context) {
        return new PhoneWindow(context);
    }
這裡直接new PhoneWindow(context)返回,可知PhoneWindow類是Window類的子類,進入PhoneWindow類看看

第六步:PhoneWindow()

在frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java中

 

public class PhoneWindow extends Window implements MenuBuilder.Callback {
.
    public PhoneWindow(Context context) {
        super(context);
        mLayoutInflater = LayoutInflater.from(context);
    }
來看一下PhoneWindow類的構造函數,首先是調用了其父類(Window)的構造函數,然後創建了一個LayoutInflater對象mLayoutInflater,這個對象根據它的名字大概可以知道它是渲染布局資源的

 

去Window類的構造函數看看

 

第七步:Window()

在frameworks/base/core/java/android/view/Window.java中

 

public Window(Context context) {
        mContext = context;
    }
回到第二步中,這是我們知道getWindow()返回其實是一個PhoneWindow對象,即Activity的成員變量mWindow是PhoneWindow類型.

 

然後回到第一步中,那麼接著其實是調用PhoneWindow.setContentView()了

 

第八步:setContentView()

在frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java中

 

 public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();
        } else {
            mContentParent.removeAllViews();
        }
        mLayoutInflater.inflate(layoutResID, mContentParent);
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }

PhoneWindow的成員變量mContentParent是ViewGroup類型,第一次進來為null,所以調用installDecor()函數,那我們首先看看該函數

 

 

第九步:installDecor()

在frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java中

 

private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor();
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
          ...
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);

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

            mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
            if (mTitleView != null) {
                mTitleView.setLayoutDirection(mDecor.getLayoutDirection());
                if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
                    View titleContainer = findViewById(com.android.internal.R.id.title_container);
                    if (titleContainer != null) {
                        titleContainer.setVisibility(View.GONE);
                    } else {
                        mTitleView.setVisibility(View.GONE);
                    }
                    if (mContentParent instanceof FrameLayout) {
                        ((FrameLayout)mContentParent).setForeground(null);
                    }
                } else {
                    mTitleView.setText(mTitle);
                }
            } else {
                mActionBar = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);
                if (mActionBar != null) {
                    mActionBar.setWindowCallback(getCallback());
                    if (mActionBar.getTitle() == null) {
                        mActionBar.setWindowTitle(mTitle);
                    }
                    final int localFeatures = getLocalFeatures();
                    if ((localFeatures & (1 << FEATURE_PROGRESS)) != 0) {
                        mActionBar.initProgress();
                    }
                    if ((localFeatures & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
                        mActionBar.initIndeterminateProgress();
                    }

                    boolean splitActionBar = false;
                    final boolean splitWhenNarrow =
                            (mUiOptions & ActivityInfo.UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW) != 0;
                    if (splitWhenNarrow) {
                        splitActionBar = getContext().getResources().getBoolean(
                                com.android.internal.R.bool.split_action_bar_is_narrow);
                    } else {
                        splitActionBar = getWindowStyle().getBoolean(
                                com.android.internal.R.styleable.Window_windowSplitActionBar, false);
                    }
                    final ActionBarContainer splitView = (ActionBarContainer) findViewById(
                            com.android.internal.R.id.split_action_bar);
                    if (splitView != null) {
                        mActionBar.setSplitView(splitView);
                        mActionBar.setSplitActionBar(splitActionBar);
                        mActionBar.setSplitWhenNarrow(splitWhenNarrow);

                        final ActionBarContextView cab = (ActionBarContextView) findViewById(
                                com.android.internal.R.id.action_context_bar);
                        cab.setSplitView(splitView);
                        cab.setSplitActionBar(splitActionBar);
                        cab.setSplitWhenNarrow(splitWhenNarrow);
                    } else if (splitActionBar) {
                        Log.e(TAG, "Requested split action bar with " +
                                "incompatible window decor! Ignoring request.");
                    }
                  .
                }
            }
        }
    }

PhoneWindow的成員變量mDecor是DecorView類型,看一下DecorView的類圖結構

 

\

由上圖可知,DecorView也是View,其實這個DecorView也是應用程序窗口根View.

第一次進來mDecor為null,所以會執行下面:

 

mDecor = generateDecor();
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);//焦點先交給mDecor的子view處理,如果子View沒有處理自己再處理
         
首先來看一下generateDecor()這個函數

 

 

第十步:generateDecor()

在frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java中

 

protected DecorView generateDecor() {
        return new DecorView(getContext(), -1);
    }
這裡就直接新建了DecorView實例

 

回到第九步中,mContentParent == null成立,所以執行:

 

mContentParent = generateLayout(mDecor);
進入generateLayout(mDecor)函數看看,傳進去的參數就是第十步創建的DecorView對象

 

 

第十一步:generateLayout()

在frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java中

 

protected ViewGroup generateLayout(DecorView decor) {
     ....
        mDecor.startChanging();

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

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
     ...
        return contentParent;
    }
這個函數內容還是挺多的,不過不難,首先收集在清單配置文件中,給該activity配置的theme屬性值,然後根據這些屬性值去加載系統的布局文件,設置這些theme屬性值也可以在代碼中設置,不過要setContentView()之前,不過就不起作用了.

 

 

else {
            // Embedded, so no decoration is needed.
            layoutResource = com.android.internal.R.layout.screen_simple;
            // System.out.println("Simple!");
        }
這裡假設需要加載的布局文件id是com.android.internal.R.layout.screen_simple,系統的布局文件在frameworks/base/core/res/res/layout/目錄下,

 

\

進去screen_simple.xml看一下

 





    
    <framelayout android:foreground="?android:attr/windowContentOverlay" android:foregroundgravity="fill_horizontal|top" android:foregroundinsidepadding="false" android:id="@android:id/content" android:layout_height="match_parent" android:layout_width="match_parent">
</framelayout>
有一個id為content的控件,這個很關鍵.

 

在確定好加載哪個系統布局文件後,接下來:

 

View in = mLayoutInflater.inflate(layoutResource, null);
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mLayoutInflater在第六步創建的,這裡將一個布局文件渲染成一個View,這個view的具體類型就是布局文件的根節點的對象類型,像上面的screen_simple.xml它的根節點就是LinearLayout.

 

接著將這個渲染成的LinearLayout添加到decor中,因為DecorView是ViewGroup類型,能添加子view.

接著往下看:

 

ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

這裡ID_ANDROID_CONTENT為com.android.internal.R.id.content,我們進到findViewById()函數進去看看

 

 

第十二步:findViewById()

在frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java中

 

public View findViewById(int id) {
        return getDecorView().findViewById(id);
    }
  public final View getDecorView() {
        if (mDecor == null) {
            installDecor();
        }
        return mDecor;
    }
這個函數就去查找decorView中id為com.android.internal.R.id.content的子view,在上面的布局文件中就是一個FrameLayout了,所以說系統布局文件要有一個id為content的控件.

 

好了,回到第十一步,找到了這控件後就返回到第九步中,將它賦值給了mContentParent.

現在整理一下思路,mDecor賦值了,mContentParent也賦值了,它們的關系是:

\

回到第九步,繼續往下分析:

 

mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
            if (mTitleView != null) {
                mTitleView.setLayoutDirection(mDecor.getLayoutDirection());
                if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
                    View titleContainer = findViewById(com.android.internal.R.id.title_container);
                    if (titleContainer != null) {
                        titleContainer.setVisibility(View.GONE);
                    } else {
                        mTitleView.setVisibility(View.GONE);
                    }
                    if (mContentParent instanceof FrameLayout) {
                        ((FrameLayout)mContentParent).setForeground(null);
                    }
                } else {
                    mTitleView.setText(mTitle);
                }
            } else {
                mActionBar = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);
                if (mActionBar != null) {
                    mActionBar.setWindowCallback(getCallback());
                    if (mActionBar.getTitle() == null) {
                        mActionBar.setWindowTitle(mTitle);
                    }
                    final int localFeatures = getLocalFeatures();
                    if ((localFeatures & (1 << FEATURE_PROGRESS)) != 0) {
                        mActionBar.initProgress();
                    }
                    if ((localFeatures & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
                        mActionBar.initIndeterminateProgress();
                    }

                    boolean splitActionBar = false;
                    final boolean splitWhenNarrow =
                            (mUiOptions & ActivityInfo.UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW) != 0;
                    if (splitWhenNarrow) {
                        splitActionBar = getContext().getResources().getBoolean(
                                com.android.internal.R.bool.split_action_bar_is_narrow);
                    } else {
                        splitActionBar = getWindowStyle().getBoolean(
                                com.android.internal.R.styleable.Window_windowSplitActionBar, false);
                    }
                    final ActionBarContainer splitView = (ActionBarContainer) findViewById(
                            com.android.internal.R.id.split_action_bar);
                    if (splitView != null) {
                        mActionBar.setSplitView(splitView);
                        mActionBar.setSplitActionBar(splitActionBar);
                        mActionBar.setSplitWhenNarrow(splitWhenNarrow);

                        final ActionBarContextView cab = (ActionBarContextView) findViewById(
                                com.android.internal.R.id.action_context_bar);
                        cab.setSplitView(splitView);
                        cab.setSplitActionBar(splitActionBar);
                        cab.setSplitWhenNarrow(splitWhenNarrow);
                    } else if (splitActionBar) {
                        Log.e(TAG, "Requested split action bar with " +
                                "incompatible window decor! Ignoring request.");
                    }

                

PhoneWindow類的成員函數installDecor()還會檢查前面加載的窗口布局文件是否包含有一個id值為“title”的TextView控件。如果包含有的話,就會將它保存在PhoneWindow類的成員變量mTitleView中,用來描述當前應用程序窗口的標題欄。但是,如果當前應用程序窗口是沒有標題欄的,即它的Feature位FEATURE_NO_TITLE的值等於1,那麼PhoneWindow類的成員函數installDecor()就需要將前面得到的標題欄隱藏起來。注意,PhoneWindow類的成員變量mTitleView所描述的標題欄有可能是包含在一個id值為“title_container”的容器裡面的,在這種情況下,就需要隱藏該標題欄容器。另一方面,如果當前應用程序窗口是設置有標題欄的,那麼PhoneWindow類的成員函數installDecor就會設置它的標題欄文字。應用程序窗口的標題欄文字保存在PhoneWindow類的成員變量mTitle中,我們可以調用PhoneWindow類的成員函數setTitle來設置.如果沒有id值為“title”的TextView控件,就去檢查前面加載的窗口布局文件是否包含有一個id值為“action_bar”的ActionBarView控件.下面就不分析了.

 

回到第八步setContentView()函數裡,

往下接著到

 

 mLayoutInflater.inflate(layoutResID, mContentParent);
這裡的layoutResID,是在activity的onCreate()方法裡面,通過setContentView()設置的應用程序的窗口布局資源id.

 

這裡mLayoutInflater.inflate()方法,將應用程序的窗口布局資源渲染成一個view,然後添加到mContentParent這個ViewGroup中.

所以應用程序窗口的界面的View結構如下:

\
 

平時我們寫應用,只需要寫上圖中setContentView()的布局就可以,其他android已經實現好了.

好了,應用程序窗口的布局結構就分析完了.我們知道一個應用程序的窗口的顯示區域,其實就是DecorView及其包含的子view.

設置好應用程序的布局文件後,就要將DecorView包含內容渲染顯示到屏幕上了.

至於如何渲染不打算分析了.

在顯示出來之前,DecorView還要經過measure(測量),layout(布局),draw(繪制)三個過程.後面打算分析源碼,對這三個過程加一分析下.

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