你的位置:首页 > 操作系统

[操作系统]Android GUI之Activity、Window、View


  相信大家在接触Android之初就已经知道了Activity中的setContentView方法的作用了,很明显此方法是用于为Activity填充相应的布局的。那么,Activity是如何将填充的布局绘制出来的呢?实际上Activity将View的绘制与显示交给了Window对象来处理,下面我们通过源码来进行跟踪分析。

  Activity的源码如下,只给出我们关注的部分:

public class Activity extends ContextThemeWrapper    implements LayoutInflater.Factory2,    Window.Callback, KeyEvent.Callback,    OnCreateContextMenuListener, ComponentCallbacks2,    Window.OnWindowDismissedCallback {   ……  ……  private Window mWindow;  private WindowManager mWindowManager;  …… /**   * 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;  }  ……  /**   * 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(int layoutResID) {    getWindow().setContentView(layoutResID);    initWindowDecorActionBar();  }  /**   * Set the activity content to an explicit view. This view is placed   * directly into the activity's view hierarchy. It can itself be a complex   * view hierarchy. When calling this method, the layout parameters of the   * specified view are ignored. Both the width and the height of the view are   * set by default to {@link ViewGroup.LayoutParams#MATCH_PARENT}. To use   * your own layout parameters, invoke   * {@link #setContentView(android.view.View,android.view.ViewGroup.LayoutParams)}   * instead.   *   * @param view The desired content to display.   *   * @see #setContentView(int)   * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)   */  public void setContentView(View view) {    getWindow().setContentView(view);    initWindowDecorActionBar();  }  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, IVoiceInteractor voiceInteractor) {    attachBaseContext(context);    mFragments.attachActivity(this, mContainer, null);    mWindow = PolicyManager.makeNewWindow(this);       ……  }……}

PolicyManager的部分源码:

public final class PolicyManager {  ……private static final IPolicy sPolicy;  static {    // Pull in the actual implementation of the policy at run-time    ……      sPolicy = (IPolicy)policyClass.newInstance();   ……  }  // Cannot instantiate this class  private PolicyManager() {}  // The static methods to spawn new policy-specific objects  public static Window makeNewWindow(Context context) {    return sPolicy.makeNewWindow(context);  }  ……}

Policy的部分源码:

public class Policy implements IPolicy {  ……  public Window makeNewWindow(Context context) {    return new PhoneWindow(context);}……}

  从给出的源码我们可以看到,Activity内部含有一个Window类型的对象mWindow,当我们调用setContentView方法时,实际上是委托给了Window对象进行处理。Window本身是一个抽象类,它描述了android窗口的基本属性和行为特征。在activity的attach方法中通过mWindow = PolicyManager.makeNewWindow(this)创建了Window对象。通过追踪代码可知, PolicyManager.makeNewWindow(this)实际上是调用Policy中的makeNewWindow方法,在此方法中创建了一个PhoneWindow对象。而PhoneWindow正是Window的子类。他们的关系图如下:

继续追踪源码,PhoneWindow对Window的抽象方法setContentView(int layoutResId)进行了实现,具体源码如下:

@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);    }    final Callback cb = getCallback();    if (cb != null && !isDestroyed()) {      cb.onContentChanged();    }  }

  在这个方法中我们可以看到首先对mContentParent进行了判断,如果为空的话则调用installDecor方法,通过hasFeature判断window是否具备某些特征,如果窗口不含有FEATURE_CONTENT_TRANSITIONS特征,则清空mContentParent中的所有子元素,为后面加载布局文件到mContentParent中做好准备。通过后面的判断,我们也可以看出无论走那个分支,其实都是对mContentParent布局内容做了更新。由此我们可以推断出mContentParent其实就是我们自己的布局的存放容器,它在PhoneWindow中定义如下:

  // This is the view in which the window contents are placed. It is either  // mDecor itself, or a child of mDecor where the contents go.  private ViewGroup mContentParent;

  那么mContentParent是在哪里被创建的呢,很显然是在方法installDecor中,方法installDecor的关键代码如下:

  private void installDecor() {    if (mDecor == null) {      mDecor = generateDecor();      ……    }    if (mContentParent == null) {mContentParent = generateLayout(mDecor);      ……    }}

  在这个方法中,我们可以看到,首先对mDecor进行判断,如果为空在调用generateDecor方法生成mDecor对象,那么mDecor对象是什么呢?通过查看代码,可以知道mDecor的类型为DecorView,此类型是定义在PhoneWindow中的一个内部类,它继承了FrameLayout。紧接着判断mContentParent是否为空,为空则调用generateLayout并通过传入参数mDecor生成了mContentParent对象。在这个方法中通过应用的主题、窗口特征等来确定使用的布局资源并将使用的布局添加mDecor中,而这些布局中都会含有一个id为content的ViewGroup(FrameLayout),此ViewGroup正是mContentParent,方法关键代码如下:

protected ViewGroup generateLayout(DecorView decor) {    ……    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);    ……    return contentParent;  }

  由此我们可以确定,view的显示处理顺序为Activity->PhoneWindow->DecorView->ViewGroup(mContentView)->自定义的View(布局)。

  Activity中显示视图的层次结构,具体如下:

 

  疑问咨询或技术交流,请加入官方QQ群:JRedu技术交流 (452379712)

 

作者:杰瑞教育
出处:http://www.cnblogs.com/jerehedu/ 
本文版权归烟台杰瑞教育科技有限公司和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。