Android 中Activity,Window和View之间的关系

来源:互联网 发布:linux tcp ip协议栈 编辑:程序博客网 时间:2024/04/29 08:57

本文是我从以下地址拷贝来的:  http://lpqsun-126-com.iteye.com/blog/1409989

保存下来,仔细阅读。


我想大多数人,对于这3个东西的概念能区分,但是具体区别在哪却很难说出来。

我这里根据我个人的理解来讲讲我个人对这3个概念的理解。当然这里设计到通用的事件窗口模型等通用GUI设计,我这里就不打算讲了,纯粹从概念上来进行区分。

Activity是Android应用程序的载体,允许用户在其上创建一个用户界面,并提供用户处理事件的API,如onKeyEvent, onTouchEvent等。 并维护应用程序的生命周期(由于android应用程序的运行环境和其他操作系统不同,android的应用程序是运行在框架之内,所以他的应用程序不能当当从进程的级别去考虑,而更多是从概念上去考虑。android应用程序是由多个活动堆积而成,而各个活动又有其独立的生命周期)。Activity本身是个庞大的载体,可以理解成是应用程序的载体,如果木有Activity,android应用将无法运行。也可以理解成android应用程序的入口。Acivity的实例对象由系统维护。系统服务ActivityManager负责维护Activity的实例对象,并根据运行状态维护其状态信息。

但在用户级别,程序员可能根愿意理解成为一个界面的载体。但仅仅是个载体,它本身并不负责任何绘制。Activity的内部实现,实际上是聚了一个Window对象。Window是一个抽象类,它的具体是在android_src_home/framework/policies/base/phone/com/android/internal/policy/impl目录下的PhoneWindow.java。

当我们调用Acitivity的 setContentView方法的时候实际上是调用的Window对象的setContentView方法,所以我们可以看出Activity中关于界面的绘制实际上全是交给Window对象来做的。绘制类图的话,可以看出Activity聚合了一个Window对象。

下面是PhoneWindow中的setContentView方法的实现:

Java代码  收藏代码
  1. @Override  
  2.   
  3.     public void setContentView(View view, ViewGroup.LayoutParams params) {  
  4.   
  5.         if (mContentParent == null) {  
  6.   
  7.             installDecor();  
  8.   
  9.         } else {  
  10.   
  11.             mContentParent.removeAllViews();  
  12.   
  13.         }  
  14.   
  15.         mContentParent.addView(view, params);  
  16.   
  17.         final Callback cb = getCallback();  
  18.   
  19.         if (cb != null) {  
  20.   
  21.             cb.onContentChanged();  
  22.   
  23.         }  
  24.   
  25.     }  

 Window内部首先判断mContentParent是否为空,然后调用installDecor方法(安装装饰器),我们看看这个方法如何实现的 

Java代码  收藏代码
  1. private void installDecor() {  
  2.   
  3.         if (mDecor == null) {  
  4.   
  5.             mDecor = generateDecor();  
  6.   
  7.             mDecor.setIsRootNamespace(true);  
  8.   
  9.         }  
  10.   
  11.         if (mContentParent == null) {  
  12.   
  13.             mContentParent = generateLayout(mDecor);  
  14.   
  15.             mTitleView = (TextView)findViewById(com.android.internal.R.id.title);  
  16.   
  17.             if (mTitleView != null) {  
  18.   
  19.                 if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {  
  20.   
  21.                     View titleContainer = findViewById(com.android.internal.R.id.title_container);  
  22.   
  23.                     if (titleContainer != null) {  
  24.   
  25.                         titleContainer.setVisibility(View.GONE);  
  26.   
  27.                     } else {  
  28.   
  29.                         mTitleView.setVisibility(View.GONE);  
  30.   
  31.                     }  
  32.   
  33.                     if (mContentParent instanceof FrameLayout) {  
  34.   
  35.                         ((FrameLayout)mContentParent).setForeground(null);  
  36.   
  37.                     }  
  38.   
  39.                 } else {  
  40.   
  41.                     mTitleView.setText(mTitle);  
  42.   
  43.                 }  
  44.   
  45.             }  
  46.   
  47.         }  
  48.   
  49.     }  
 

在该方法中,首先创建一个DecorView,DecorView是一个扩张FrameLayout的类,是所有窗口的根View。我们在Activity中调用的setConctentView就是放到DecorView中了。这是我们类图的聚合关系如下:

Activity--->Window--->DecorView

这是我们得出这3个类之间最直接的一个关系。

我们详细分析一下,类对象是如何被创建的。

先不考虑Activity的创建(因为 Acitivity的实例由ActivityManager维护,是在另一个进程设计到IPC的通信,后面会讲到),而考虑Window和View的创建。

Activity被创建后,系统会调用它的attach方法来将Activity添加到ActivityThread当中。我们找到Activity的attach方法如下:

Java代码  收藏代码
  1. final void attach(Context context, ActivityThread aThread,  
  2.   
  3.             Instrumentation instr, IBinder token, int ident,  
  4.   
  5.             Application application, Intent intent, ActivityInfo info,  
  6.   
  7.             CharSequence title, Activity parent, String id,  
  8.   
  9.             Object lastNonConfigurationInstance,  
  10.   
  11.             HashMap<String,Object> lastNonConfigurationChildInstances,  
  12.   
  13.             Configuration config) {  
  14.   
  15.         attachBaseContext(context);  
  16.   
  17.        mWindow= PolicyManager.makeNewWindow(this);  
  18.   
  19.         mWindow.setCallback(this);  
  20.   
  21.         if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {  
  22.   
  23.             mWindow.setSoftInputMode(info.softInputMode);  
  24.   
  25.         }  
  26.   
  27.         mUiThread = Thread.currentThread();  
  28.   
  29.         mMainThread = aThread;  
  30.   
  31.         mInstrumentation = instr;  
  32.   
  33.         mToken = token;  
  34.   
  35.         mIdent = ident;  
  36.   
  37.         mApplication = application;  
  38.   
  39.         mIntent = intent;  
  40.   
  41.         mComponent = intent.getComponent();  
  42.   
  43.         mActivityInfo = info;  
  44.   
  45.         mTitle = title;  
  46.   
  47.         mParent = parent;  
  48.   
  49.         mEmbeddedID = id;  
  50.   
  51.         mLastNonConfigurationInstance = lastNonConfigurationInstance;  
  52.   
  53.         mLastNonConfigurationChildInstances = lastNonConfigurationChildInstances;  
  54.   
  55.         mWindow.setWindowManager(null, mToken, mComponent.flattenToString());  
  56.   
  57.         if (mParent != null) {  
  58.   
  59.             mWindow.setContainer(mParent.getWindow());  
  60.   
  61.         }  
  62.   
  63.         mWindowManager = mWindow.getWindowManager();  
  64.   
  65.         mCurrentConfig = config;  
  66.   
  67.     }  
 

我们看红色的代码部分,就是创建Window对象的代码。感兴趣的同学可以跟踪去看看具体是如何创建的。其实很简单,其内部实现调用了Policy对象的makeNewWindow方法,其方法直接new了一个PhoneWindow对象如下:

public PhoneWindow makeNewWindow(Context context) {

        returnnew PhoneWindow(context);

 }

这时我们已经可以把流程串起来,Activity创建后系统会调用其attach方法,将其添加到ActivityThread当中,在attach方法中创建了一个window对象。

下面分析View的创建。我们知道Window聚合了DocerView,当用户调用setContentView的时候会把一颗View树仍给DocerView.View树是已经创建好的实例对象了,所以我们研究的是DocerView是个什么东西,它是如何被创建的。

我们回头看看Window实现里边的setContentView方法,我们看上面代码的红色部分setContentView-> installDecor-> generateDecor.

 

generateDecor直接new了一个DecorView对象:  

protected DecorView generateDecor() {

        returnnew DecorView(getContext(), -1);

 }

我们可以去看看DecorView的实现,它是PhoneWindow的一个内部类。实现很简单,它默认会包含一个灰色的标题栏,然后在标题栏下边会包含一个空白区域用来当用户调用setContentView的时候放置用户View,并传递事件,这里不做详细分析,感兴趣同学可以自己研究研究。

当DecorView创建好之后再回到Window中的setContentView方法中来,见上面代码蓝色部分,调用

  mContentParent.addView(view, params);

来将用户的View树添加到DecorView中。

到这时为止,我想我们已经很清晰的认识到它们3者之间的关系,并知道其创建流程。

现在总结一下:

Activity在onCreate之前调用attach方法,在attach方法中会创建window对象。window对象创建时并木有创建Decor对象对象。用户在Activity中调用setContentView,然后调用window的setContentView,这时会检查DecorView是否存在,如果不存在则创建DecorView对象,然后把用户自己的View 添加到DecorView中。

上篇讲解了3个对象之间的关系和创建的时机。这篇讲解窗口是如何被绘制出来的。

首先,我们看一个概念。就是View的draw方法的doc:

Manually render this view (and all of its children) to the given Canvas.

意思是说把View绘制在画布上。个人觉得这个概念很重要,View和Canvas 的关系,按常规的思维,肯定认为View聚合了Canvas对象,然后在View的onDraw 方法中,在View中绘制图形。实际上恰恰相反,Canvas 是由系统提供,view通过draw方法来把自身绘制在画布上。如果这样来理解的话,很多东西理解起来就很自然了。系统在window中提供一个Canvas对象,DocerView通过调用draw方法来将自己绘制到canvas上。draw方法实际上是一个递归方法,他会循环调用孩子View的draw方法来完成整棵树的绘制。所以实际上一个界面的绘制所用的Cavans是同一个对象。Canvas内部聚合了Matrix对象来实现坐标系的变换。

这里将的是题外话,只是想让大家理解一个东西。

下面回到系统如何来绘制一个窗口。

android 系统提供了WindowManager,WindowManager顾名思义窗口管理器。实际上它只是对WindowManager服务做了一个包装。其内部实现通过ISessionWindow和IWindow接口来和WindowManager服务来通信,这里设计到IPC的概念。IPC即进程间的通讯,ANDROID通过IBinder接口来实现,IBinder通过transact方法来实现进程间的交互,这是一个使用很不友好的接口,好在android还提供了aidl的更人性化的接口,它使得IPC通信就像调用普通的JAVA方法那样便捷。不了解aidl的同学可以自行研究研究(其实我自己也是半桶水,了解概念,而用的不熟悉)

我来看Window是如何被添加到WindowManager的.

Activity有一个public的方法setVisible用来控制Activity的窗口是否显示。

我们看其内部实现发现其调用了makeVisible方法,该方法就是让Window显示在屏幕当中的方法,实现如下:

Java代码  收藏代码
  1. void makeVisible() {  
  2.   
  3.        if (!mWindowAdded) {  
  4.   
  5.            ViewManager wm = getWindowManager();  
  6.   
  7.            wm.addView(mDecor, getWindow().getAttributes());  
  8.   
  9.            mWindowAdded = true;  
  10.   
  11.        }  
  12.   
  13.        mDecor.setVisibility(View.VISIBLE);  
  14.   
  15.    }  
 

这时我们立马就明白了,Activity通过Context来获取WindowManager对象,然后把Window对象的DocerView添加到了WindowManager 服务中,所以android的窗口的创建和显示并不是在同一个进程中,而是把窗口的绘制和管理交给了专门的WindowManager服务,这也是android framework给我提供的基础服务。

在上面绿色的代码当中,我们看看mDeocr是在哪被创建的。

Java代码  收藏代码
  1. public void onWindowAttributesChanged(WindowManager.LayoutParams params) {  
  2.   
  3.       // Update window manager if: we have a view, that view is  
  4.   
  5.        // attached to its parent (which will be a RootView), and  
  6.   
  7.        // this activity is not embedded.  
  8.   
  9.         if (mParent == null) {  
  10.   
  11.             View decor = mDecor;  
  12.   
  13.             if (decor != null && decor.getParent() != null) {  
  14.   
  15.                 getWindowManager().updateViewLayout(decor, params);  
  16.   
  17.             }  
  18.   
  19.         }  
  20.   
  21.     }  
 

搜索代码发现其创建的地方如上面红色代码。

这时我们已经知道,Activity创建好Window之后只要调用WindowManager 的addView方法来将Window的DocerView添加进去即可是使Window显示出来。还方法Window其实是Activity中的概念,在WindowManager中是直接和View打交道的。

下面我们开始研究WindowManager对象,打开其源代码,发现它是一个接口,且只是简单的扩展了ViewManager接口.并增加了一个方法

getDefaultDisplay():Display.  内部还有一个继承自ViewGroup.LayoutParam的内部类。

我们在framework/base/core/java/android/view/WindowManagerImpl.java 中找到其实现类。

我们找到其核心的实现方法addView 方法,如下:

Java代码  收藏代码
  1. private void addView(View view, ViewGroup.LayoutParams params, boolean nest)  
  2.   
  3.     {  
  4.   
  5.         if (Config.LOGV) Log.v("WindowManager""addView view=" + view);  
  6.   
  7.         if (!(params instanceof WindowManager.LayoutParams)) {  
  8.   
  9.             throw new IllegalArgumentException(  
  10.   
  11.                     "Params must be WindowManager.LayoutParams");  
  12.   
  13.         }  
  14.   
  15.         final WindowManager.LayoutParams wparams  
  16.   
  17.                 = (WindowManager.LayoutParams)params;  
  18.   
  19.           
  20.   
  21.         ViewRoot root;  
  22.   
  23.         View panelParentView = null;  
  24.   
  25.           
  26.   
  27.         synchronized (this) {  
  28.   
  29.             // Here's an odd/questionable case: if someone tries to add a  
  30.   
  31.             // view multiple times, then we simply bump up a nesting count  
  32.   
  33.             // and they need to remove the view the corresponding number of  
  34.   
  35.             // times to have it actually removed from the window manager.  
  36.   
  37.             // This is useful specifically for the notification manager,  
  38.   
  39.             // which can continually add/remove the same view as a  
  40.   
  41.             // notification gets updated.  
  42.   
  43.             int index = findViewLocked(view, false);  
  44.   
  45.             if (index >= 0) {  
  46.   
  47.                 if (!nest) {  
  48.   
  49.                     throw new IllegalStateException("View " + view  
  50.   
  51.                             + " has already been added to the window manager.");  
  52.   
  53.                 }  
  54.   
  55.                 root = mRoots[index];  
  56.   
  57.                 root.mAddNesting++;  
  58.   
  59.                 // Update layout parameters.  
  60.   
  61.                 view.setLayoutParams(wparams);  
  62.   
  63.                 root.setLayoutParams(wparams, true);  
  64.   
  65.                 return;  
  66.   
  67.             }  
  68.   
  69.               
  70.   
  71.             // If this is a panel window, then find the window it is being  
  72.   
  73.             // attached to for future reference.  
  74.   
  75.             if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&  
  76.   
  77.                     wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {  
  78.   
  79.                 final int count = mViews != null ? mViews.length : 0;  
  80.   
  81.                 for (int i=0; i<count; i++) {  
  82.   
  83.                     if (mRoots[i].mWindow.asBinder() == wparams.token) {  
  84.   
  85.                         panelParentView = mViews[i];  
  86.   
  87.                     }  
  88.   
  89.                 }  
  90.   
  91.             }  
  92.   
  93.               
  94.   
  95.            root =newViewRoot(view.getContext());  
  96.   
  97.             root.mAddNesting = 1;  
  98.   
  99.             view.setLayoutParams(wparams);  
  100.   
  101.               
  102.   
  103.             if (mViews == null) {  
  104.   
  105.                 index = 1;  
  106.   
  107.                 mViews = new View[1];  
  108.   
  109.                 mRoots = new ViewRoot[1];  
  110.   
  111.                 mParams = new WindowManager.LayoutParams[1];  
  112.   
  113.             } else {  
  114.   
  115.                 index = mViews.length + 1;  
  116.   
  117.                 Object[] old = mViews;  
  118.   
  119.                 mViews = new View[index];  
  120.   
  121.                 System.arraycopy(old, 0, mViews, 0, index-1);  
  122.   
  123.                 old = mRoots;  
  124.   
  125.                 mRoots = new ViewRoot[index];  
  126.   
  127.                 System.arraycopy(old, 0, mRoots, 0, index-1);  
  128.   
  129.                 old = mParams;  
  130.   
  131.                 mParams = new WindowManager.LayoutParams[index];  
  132.   
  133.                 System.arraycopy(old, 0, mParams, 0, index-1);  
  134.   
  135.             }  
  136.   
  137.             index--;  
  138.   
  139.             mViews[index] = view;  
  140.   
  141.             mRoots[index] = root;  
  142.   
  143.             mParams[index] = wparams;  
  144.   
  145.         }  
  146.   
  147.         // do this last because it fires off messages to start doing things  
  148.   
  149.        root.setView(view, wparams, panelParentView);  
  150.   
  151.     }  
 

我们看看我标记未红色的两行代码  root =newViewRoot(view.getContext());和 root.setView(view, wparams, panelParentView);

创建了一个ViewRoot对象,然后把view添加到ViewRoot中。

ViewRoot对象是handler的一个实现,其聚合了ISessionWindow和IWindow对象来和WindowManager服务进行IPC通信。