文档章节

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

先进青年
 先进青年
发布于 2016/12/12 23:34
字数 2808
阅读 9
收藏 0

跟踪Activity的源码就会发现: 
Activity.attch() -> PolicyManager -> Policy -> PhoneWindow -> mLayoutInflater.inflate()&mContentParent.addView() 
这只是一个简单的跟踪过程描述。通过跟踪源代码,就可以很清晰的看出他们三者的关系。  
Activity像一个工匠(控制单元),Window像窗户(承载模型),View像窗花(显示视图) 
LayoutInflater像剪刀,Xml配置像窗花图纸。 
1)一个Activity构造的时候会初始化一个Window,准确的说是PhoneWindow。 
2)这个PhoneWindow有一个“ViewRoot”,引号是说其实这个“ViewRoot”是一个View或者说ViewGroup,是最初始的根视图。 
3)“ViewRoot”通过addView方法来一个个的添加View。比如TextView,Button等 

4)这些View的事件监听,是由WindowManagerService来接受消息,并且回调Activity函数。比如onClickListener,onKeyDown等

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通信。

本文转载自:http://blog.csdn.net/chujidiy/article/details/7820451

先进青年
粉丝 0
博文 28
码字总数 9089
作品 0
武汉
私信 提问
Android自定义View基础:ViewRoot、DecorView & Window的简介

前言 自定义View原理是Android开发者必须了解的基础,在了解自定义View之前,你需要有一定的知识储备。 今天,本文将全面解析关于自定义View中基础:ViewRoot、DecorView & Window,希望你们...

Carson_Ho
05/30
0
0
Activity、Window、View三者关系

目录介绍 01.Window,View,子Window 02.什么是Activity 03.什么是Window 04.什么是DecorView 05.什么是View 06.关系结构图 07.Window创建过程 08.创建机制分析 8.1 Activity实例的创建 8.2 ...

潇湘剑雨
05/29
11
0
Android杂谈--Activity、Window、View的关系

一、首先说说View和ViewGroup吧   Android系统中的所有UI类都是建立在View和ViewGroup这两个类的基础上的。所有View的子类成为”Widget”,所有ViewGroup的子类成为”Layout”。View和Vie...

垂盆草
2012/11/10
212
1
Android窗口机制(三)Window和WindowManager的创建与Activity

Android窗口机制系列 Android窗口机制(一)初识Android的窗口结构 Android窗口机制(二)Window,PhoneWindow,DecorView,setContentView源码理解 Android窗口机制(三)Window和WindowMan...

亭子happy
02/26
68
0
Android窗口机制(一)初识Android的窗口结构

Android窗口机制系列 Android窗口机制(一)初识Android的窗口结构 Android窗口机制(二)Window,PhoneWindow,DecorView,setContentView源码理解 Android窗口机制(三)Window和WindowMan...

亭子happy
02/26
45
0

没有更多内容

加载失败,请刷新页面

加载更多

cesium调用天地图服务

本文转载于:专业的前端网站➧cesium调用天地图服务 全球矢量地图服务 var viewer = new Cesium.Viewer("cesiumContainer", { animation: false, //是否显示动画控件 baseLayerPi...

前端老手
23分钟前
4
0
Docker常用命令

场景一:镜像下载、运行及删除 COMMAND DESC 查看 docker images 列出所有镜像(images) docker ps 列出正在运行的容器(containers) docker ps -a 列出所有的容器 docker pull centos 下载cen...

_Change_
23分钟前
4
0
Spark ML使用DataFrame进行K-Means

1.前言 前一篇文章使用了RDD的方式,进行了K-Means聚类. 从Spark 2.0开始,程序包中基于RDD的API spark.mllib已进入维护模式.现在,用于Spark的主要机器学习API是软件包中基于DataFrame的API...

一位不知名的帅气网友
26分钟前
4
0
当遇到美女面试官之如何理解Redis的Expire Key(过期键)

  在面试中遇到美女面试官时,我们以为面试会比较容易过,也能好好表现自己技术的时候了。然而却出现以下这一幕,当美女面试官听说你使用过Redis时,那么问题来了。 👩面试官:Q1,你知道...

ccww_
30分钟前
5
0
干货来袭!游戏背景音乐的角色创建和主界面

角色创建/选择 在一些大型的游戏中,例如多人在线的游戏玩家必须创建一个游戏的虚拟人物进行扮演游戏。初次玩这款游戏的人都会进行创建,选择职业起名字性别选择编辑人设样式等等的操作,通常...

奇亿音乐
33分钟前
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部