视图对象(View)的创建过程分析

最后更新于:2022-04-02 05:03:07

原文出处——>[Android应用程序窗口(Activity)的视图对象(View)的创建过程分析](http://blog.csdn.net/luoshengyang/article/details/8245546) 从前文可知道,每一个Activity组件都有一个关联的Window对象,用来描述一个应用程序窗口。每一个应用程序窗口内部又包含有一个View对象,用来描述应用程序窗口的视图。应用程序窗口视图是真正用来实现UI内容和布局的,也就是说,每一个Activity组件的UI内容和布局都是通过与其所关联的一个Window对象的内部的一个View对象来实现的。在本文中,我们就详细分析应用程序窗口视图的创建过程。 在前面Android应用程序窗口(Activity)实现框架简要介绍和学习计划一文中提到,应用程序窗口内部所包含的视图对象的实际类型为DecorView。DecorView类继承了View类,是作为容器(ViewGroup)来使用的,它的实现如图1所示: :-: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/f04dde05ea6e56c6f8d59f239a28ed6d_680x428.jpg) 图1 DecorView类的实现 这个图的具体描述可以参考Android应用程序窗口(Activity)实现框架简要介绍和学习计划一文中的图5,这里不再详述。 从前面Android应用程序窗口(Activity)实现框架简要介绍和学习计划一文还可以知道,每一个应用程序窗口的视图对象都有一个关联的ViewRoot对象,这些关联关系是由窗口管理器来维护的,如图2所示: :-: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/5ccb934e8cd134a8b2ab2a3126da9e05_818x575.jpg) 图2 应用程序窗口视图与ViewRoot的关系图 这个图的具体描述可以参考Android应用程序窗口(Activity)实现框架简要介绍和学习计划一文中的图6,这里不再详述。 简单来说,ViewRoot相当于是MVC模型中的Controller,它有以下职责: 1. 负责为应用程序窗口视图创建Surface。 2. 配合WindowManagerService来管理系统的应用程序窗口。 3. 负责管理、布局和渲染应用程序窗口视图的UI。 那么,应用程序窗口的视图对象及其所关联的ViewRoot对象是什么时候开始创建的呢? 从前面Android应用程序窗口(Activity)的窗口对象(Window)的创建过程分析一文可以知道,Activity组件在启动的时候,系统会为它创建窗口对象(Window),同时,系统也会为这个窗口对象创建视图对象。另一方面,当Activity组件被激活的时候,系统如果发现与它的应用程序窗口视图对象所关联的ViewRoot对象还没有创建,那么就会先创建这个ViewRoot对象,以便接下来可以将它的UI渲染出来。 从前面Android应用程序启动过程源代码分析一文可以知道,Activity组件在启动的过程中,会调用ActivityThread类的成员函数handleLaunchActivity,用来创建以及首次激活Activity组件,因此,接下来我们就从这个函数开始,具体分析应用程序窗口的视图对象及其所关联的ViewRoot对象的创建过程,如图3所示: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/5d9ffc061569cec9feeb80155f74054a_836x1105.jpg) 图3 应用程序窗口视图的创建过程 这个过程一共可以分为13个步骤,接下来我们就详细分析每一个步骤。 **Step 1. ActivityThread.handleLaunchActivity** ~~~ public final class ActivityThread { ...... private final void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) { ...... Activity a = performLaunchActivity(r, customIntent); if (a != null) { ...... handleResumeActivity(r.token, false, r.isForward); ...... } ...... } ...... } ~~~ 这个函数定义在文件frameworks/base/core/java/android/app/ActivityThread.java文件中。 函数首先调用ActivityThread类的成员函数performLaunchActivity来创建要启动的Activity组件。在创建Activity组件的过程中,还会为该Activity组件创建窗口对象和视图对象。Activity组件创建完成之后,就可以将它激活起来了,这是通过调用ActivityThread类的成员函数handleResumeActivity来执行的。 接下来,我们首先分析ActivityThread类的成员函数performLaunchActivity的实现,以便可以了解应用程序窗口视图对象的创建过程,接着再回过头来继续分析ActivityThread类的成员函数handleResumeActivity的实现,以便可以了解与应用程序窗口视图对象所关联的ViewRoot对象的创建过程。 **Step 2. ActivityThread.performLaunchActivity** 这个函数定义在文件frameworks/base/core/java/android/app/ActivityThread.java文件中。 这一步可以参考Android应用程序窗口(Activity)的运行上下文环境(Context)的创建过程分析一文的Step 1,它主要就是创建一个Activity组件实例,并且调用这个Activity组件实例的成员函数onCreate来让其执行一些自定义的初始化工作。 **Step 3. Activity.onCreate** 这个函数定义在文件frameworks/base/core/java/android/app/Activity.java中。 这一步可以参考Android应用程序窗口(Activity)的运行上下文环境(Context)的创建过程分析一文的Step 10。我们在实现一个Activity组件的时候,也就是在实现一个Activity子类的时候,一般都会重写成员函数onCreate,以便可以执行一些自定义的初始化工作,其中就包含初始化UI的工作。例如,在前面在Ubuntu上为Android系统内置Java应用程序测试Application Frameworks层的硬件服务一文中,我们实现了一个名称为Hello的Activity组件,用来测试硬件服务,它的成员函数onCreate的样子长得大概如下所示: ~~~ public class Hello extends Activity implements OnClickListener { ...... /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); ...... } ...... } ~~~ 其中,调用从父类Activity继承下来的成员函数setContentView就是用来创建应用程序窗口视图对象的。 接下来,我们就继续分析Activity类的成员函数setContentView的实现。 **Step 4. Activity.setContentView** ~~~ public class Activity extends ContextThemeWrapper implements LayoutInflater.Factory, Window.Callback, KeyEvent.Callback, OnCreateContextMenuListener, ComponentCallbacks { ...... private Window mWindow; ...... public Window getWindow() { return mWindow; } ...... public void setContentView(int layoutResID) { getWindow().setContentView(layoutResID); } ...... } ~~~ 这个函数定义在文件frameworks/base/core/java/android/app/Activity.java中。 Activity类的成员函数setContentView首先调用另外一个成员函数getWindow来获得成员变量mWindow所描述的一个窗口对象,接着再调用这个窗口对象的成员函数setContentView来执行创建应用程序窗口视图对象的工作。 从前面Android应用程序窗口(Activity)的窗口对象(Window)的创建过程分析一文可以知道,Activity类的成员变量mWindow指向的是一个PhoneWindow对象,因此,接下来我们就继续分析PhoneWindow类的成员函数setContentView的实现。 **Step 5. PhoneWindow.setContentView** ~~~ public class PhoneWindow extends Window implements MenuBuilder.Callback { ...... // 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; ...... @Override public void setContentView(int layoutResID) { if (mContentParent == null) { installDecor(); } else { mContentParent.removeAllViews(); } mLayoutInflater.inflate(layoutResID, mContentParent); final Callback cb = getCallback(); if (cb != null) { cb.onContentChanged(); } } ...... } ~~~ 这个函数定义在文件frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java中。 PhoneWindow类的成员变量mContentParent用来描述一个类型为DecorView的视图对象,或者这个类型为DecorView的视图对象的一个子视图对象,用作UI容器。当它的值等于null的时候,就说明正在处理的应用程序窗口的视图对象还没有创建。在这种情况下,就会调用成员函数installDecor来创建应用程序窗口视图对象。否则的话,就说明是要重新设置应用程序窗口的视图。在重新设置之前,首先调用成员变量mContentParent所描述的一个ViewGroup对象来移除原来的UI内空。 由于我们是在Activity组件启动的过程中创建应用程序窗口视图的,因此,我们就假设此时PhoneWindow类的成员变量mContentParent的值等于null。接下来,函数就会调用成员函数installDecor来创建应用程序窗口视图对象,接着再通过调用PhoneWindow类的成员变量mLayoutInflater所描述的一个LayoutInflater对象的成员函数inflate来将参数layoutResID所描述的一个UI布局设置到前面所创建的应用程序窗口视图中去,最后还会调用一个Callback接口的成员函数onContentChanged来通知对应的Activity组件,它的视图内容发生改变了。从前面Android应用程序窗口(Activity)的窗口对象(Window)的创建过程分析一文可以知道,Activity组件自己实现了这个Callback接口,并且将这个Callback接口设置到了与它所关联的应用程序窗口对象的内部去,因此,前面实际调用的是Activity类的成员函数onContentChanged来发出一个视图内容变化通知。 接下来,我们就继续分析PhoneWindow类的成员函数installDecor的实现,以便可以继续了解应用程序窗口视图对象的创建过程。 **Step 6. PhoneWindow.installDecor** ~~~ public class PhoneWindow extends Window implements MenuBuilder.Callback { ...... // This is the top-level view of the window, containing the window decor. private DecorView mDecor; ...... // 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; ...... private TextView mTitleView; ...... private CharSequence mTitle = null; ...... private void installDecor() { if (mDecor == null) { mDecor = generateDecor(); ...... } if (mContentParent == null) { mContentParent = generateLayout(mDecor); mTitleView = (TextView)findViewById(com.android.internal.R.id.title); if (mTitleView != null) { 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); } } } } ...... } ~~~ 这个函数定义在文件frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java中。 由于我们是在Activity组件启动的过程中创建应用程序窗口视图的,因此,我们同时假设此时PhoneWindow类的成员变量mDecor的值等于null。这时候PhoneWindow类的成员函数installDecor就会调用另外一个成员函数generateDecor来创建一个DecorView对象,并且保存在PhoneWindow类的成员变量mDecor中。 PhoneWindow类的成员函数installDecor接着再调用另外一个成员函数generateLayout来根据当前应用程序窗口的Feature来加载对应的窗口布局文件。这些布局文件保存在frameworks/base/core/res/res/layout目录下,它们必须包含有一个id值为“content”的布局控件。这个布局控件必须要从ViewGroup类继承下来,用来作为窗口的UI容器。PhoneWindow类的成员函数generateLayout执行完成之后,就会这个id值为“content”的ViewGroup控件来给PhoneWindow类的成员函数installDecor,后者再将其保存在成员变量mContentParent中。 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来设置。 这一步执行完成之后,应用程序窗口视图就创建完成了,回到前面的Step 1中,即ActivityThread类的成员函数handleLaunchActivity中,接下来就会调用ActivityThread类的另外一个成员函数handleResumeActivity来激活正在启动的Activity组件。由于在是第一次激活该Activity组件,因此,在激活之前,还会为该Activity组件创建一个ViewRoot对象,并且与前面所创建的应用程序窗口视图关联起来,以便后面可以通过该ViewRoot对象来控制应用程序窗口视图的UI展现。 接下来,我们就继续分析ActivityThread类的成员函数handleResumeActivity的实现。 **Step 7. ActivityThread.handleResumeActivity** ~~~ public final class ActivityThread { ...... final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) { ...... ActivityClientRecord r = performResumeActivity(token, clearHide); if (r != null) { final Activity a = r.activity; ...... // If the window hasn't yet been added to the window manager, // and this guy didn't finish itself or start another activity, // then go ahead and add the window. boolean willBeVisible = !a.mStartedActivity; if (!willBeVisible) { try { willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible( a.getActivityToken()); } catch (RemoteException e) { } } if (r.window == null && !a.mFinished && willBeVisible) { r.window = r.activity.getWindow(); View decor = r.window.getDecorView(); decor.setVisibility(View.INVISIBLE); ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; ...... if (a.mVisibleFromClient) { a.mWindowAdded = true; wm.addView(decor, l); } } ...... } ...... } ...... } ~~~ 这个函数定义在文件frameworks/base/core/java/android/app/ActivityThread.java中。 ActivityThread类的成员函数handleResumeActivity首先调用另外一个成员函数performResumeActivity来通知Activity组件,它要被激活了,即会导致Activity组件的成员函数onResume被调用。ActivityThread类的成员函数performResumeActivity的返回值是一个ActivityClientRecord对象r,这个ActivityClientRecord对象的成员变量activity描述的就是正在激活的Activity组件a。 ActivityThread类的成员函数handleResumeActivity接下来判断正在激活的Activity组件接下来是否是可见的。如果是可见的,那么变量willBeVisible的值就会等于true。Activity类的成员变量mStartedActivity用来描述一个Activity组件是否正在启动一个新的Activity组件,并且等待这个新的Activity组件的执行结果。如果是的话,那么这个Activity组件的成员变量mStartedActivity的值就会等于true,表示在新的Activity组件的执行结果返回来之前,当前Activity组件要保持不可见的状态。因此,当Activity组件a的成员变量mStartedActivity的值等于true的时候,它接下来就是不可见的,否则的话,就是可见的。 虽然说在Activity组件a的成员变量mStartedActivity的值等于true的情况下,它接下来的状态要保持不可见的,但是有可能它所启动的Activity组件的UI不是全屏的。在这种情况下,Activity组件a的UI仍然是有部分可见的,这时候也要将变量willBeVisible的值设置为true。因此,如果前面得到变量willBeVisible的值等于false,那么ActivityThread类的成员函数handleResumeActivity接下来就会通过Binder进程间通信机制来调用ActivityManagerService服务的成员函数willActivityBeVisible来检查位于Activity组件a上面的其它Activity组件(包含了Activity组件a正在等待其执行结果的Activity组件)是否是全屏的。如果不是,那么ActivityManagerService服务的成员函数willActivityBeVisible的返回值就会等于true,表示接下来需要显示Activity组件a。 前面得到的ActivityClientRecord对象r的成员变量window用来描述当前正在激活的Activity组件a所关联的应用程序窗口对象。当它的值等于null的时候,就表示当前正在激活的Activity组件a所关联的应用程序窗口对象还没有关联一个ViewRoot对象。进一步地,如果这个正在激活的Activity组件a还活着,并且接下来是可见的,即ActivityClientRecord对象r的成员变量mFinished的值等于false,并且前面得到的变量willBeVisible的值等于true,那么这时候就说明需要为与Activity组件a所关联的一个应用程序窗口视图对象关联的一个ViewRoot对象。 将一个Activity组件的应用程序窗口视图对象与一个ViewRoot对象关联是通过该Activity组件所使用的窗口管理器来执行的。从前面Android应用程序窗口(Activity)的窗口对象(Window)的创建过程分析一文可以知道,一个Activity组件所使用的本地窗口管理器保存它的成员变量mWindowManager中,这可以通过Activity类的成员函数getWindowManager来获得。在接下来的Step 10中,我们再分析Activity类的成员函数getWindowManager的实现。 由于我们现在要给Activity组件a的应用程序窗口视图对象关联一个ViewRoot对象,因此,我们就需要首先获得这个应用程序窗口视图对象。从前面的Step 6可以知道,一个Activity组件的应用程序窗口视图对象保存在与其所关联的一个应用程序窗口对象的内部,因此,我们又要首先获得这个应用程序窗口对象。与一个Activity组件所关联的应用程序窗口对象可以通过调用该Activity组件的成员函数getWindow来获得。一旦获得了这个应用程序窗口对象(类型为PhoneWindow)之后,我们就可以调用它的成员函数getDecorView来获得它内部的视图对象。在接下来的Step 8和Step 9中,我们再分别分析Activity类的成员函数Activity类的成员函数getWindow和PhoneWindow类的成员函数getDecorView的实现。 在关联应用程序窗口视图对象和ViewRoot对象的时候,还需要第三个参数,即应用程序窗口的布局参数,这是一个类型为WindowManager.LayoutParams的对象,可以通过调用应用程序窗口的成员函数getAttributes来获得。一切准备就绪之后,还要判断最后一个条件是否成立,即当前正在激活的Activity组件a在本地进程中是否是可见的,即它的成员变量mVisibleFromClient的值是否等于true。如果是可见的,那么最后就可以调用前面所获得的一个本地窗口管理器wm(类型为LocalWindowManager)的成员函数addView来执行关联应用程序窗口视图对象和ViewRoot对象的操作。 接下来,我们就分别分析Activity类的成员函数getWindow、PhoneWindow类的成员函数getDecorView、ctivity类的成员函数getWindowManager以及LocalWindowManager类的成员函数addView的实现。 **Step 8. Activity.getWindow** ~~~ public class Activity extends ContextThemeWrapper implements LayoutInflater.Factory, Window.Callback, KeyEvent.Callback, OnCreateContextMenuListener, ComponentCallbacks { ...... private Window mWindow; ...... public Window getWindow() { return mWindow; } ...... } ~~~ 这个函数定义在文件frameworks/base/core/java/android/app/Activity.java中。 从前面Android应用程序窗口(Activity)的窗口对象(Window)的创建过程分析一文可以知道,Activity类的成员变量mWindow指向的是一个类型为PhoneWindow的窗口对象,因此,Activity类的成员函数getWindow返回给调用者的是一个PhoneWindow对象。 这一步执完成之后,返回到前面的Step 7中,即ActivityThread类的成员函数handleResumeActivity中,接下来就会继续调用前面所获得的一个PhoneWindow对象的成员函数getDecorView来获得当前正在激活的Activity组件所关联的一个应用程序窗口视图对象。 **Step 9. PhoneWindow.getDecorView** ~~~ public class PhoneWindow extends Window implements MenuBuilder.Callback { ...... private DecorView mDecor; ...... @Override public final View getDecorView() { if (mDecor == null) { installDecor(); } return mDecor; } ...... } ~~~ 这个函数定义在文件frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java中。 PhoneWindow类的成员函数getDecorView首先判断成员变量mDecor的值是否等于null。如果是的话,那么就说明当前正在处理的应用程序窗口还没有创建视图对象。这时候就会调用另外一个成员函数installDecor来创建这个视图对象。从前面的调用过程可以知道,当前正在处理的应用程序窗口已经创建过视图对象,因此,这里的成员变量mDecor的值不等于null,PhoneWindow类的成员函数getDecorView直接将它返回给调用者。 这一步执完成之后,返回到前面的Step 7中,即ActivityThread类的成员函数handleResumeActivity中,接下来就会继续调用当前正在激活的Activity组件的成员函数getWindowManager来获得一个本地窗口管理器。 **Step 10. Activity.getWindowManager** ~~~ public class Activity extends ContextThemeWrapper implements LayoutInflater.Factory, Window.Callback, KeyEvent.Callback, OnCreateContextMenuListener, ComponentCallbacks { ...... private WindowManager mWindowManager; ...... public WindowManager getWindowManager() { return mWindowManager; } ...... } ~~~ 这个函数定义在文件frameworks/base/core/java/android/app/Activity.java中。 从前面Android应用程序窗口(Activity)的运行上下文环境(Context)的创建过程分析一文可以知道,Activity类的成员变量mWindowManager指向的一是类型为LocalWindowManager的本地窗口管理器,Activity类的成员函数getWindowManager直接将它返回给调用者。 这一步执完成之后,返回到前面的Step 7中,即ActivityThread类的成员函数handleResumeActivity中,接下来就会继续调用前面所获得的一个LocalWindowManager对象的成员函数addView来为当前正在激活的Activity组件的应用程序窗口视图对象关联一个ViewRoot对象。 **Step 11. LocalWindowManager.addView** ~~~ public abstract class Window { ...... private class LocalWindowManager implements WindowManager { ...... public final void addView(View view, ViewGroup.LayoutParams params) { ...... mWindowManager.addView(view, params); } ...... private final WindowManager mWindowManager; ...... } ...... } ~~~ 这个函数定义在文件frameworks/base/core/java/android/view/Window.java中。 从前面Android应用程序窗口(Activity)的窗口对象(Window)的创建过程分析一文可以知道,LocalWindowManager类的成员变量mWindowManager指向的是一个WindowManagerImpl对象,因此,LocalWindowManager类的成员函数addView接下来调用WindowManagerImpl类的成员函数addView来给参数view所描述的一个应用程序窗口视图对象关联一个ViewRoot对象。 **Step 12. WindowManagerImpl.addView** ~~~ public class WindowManagerImpl implements WindowManager { ...... public void addView(View view, ViewGroup.LayoutParams params) { addView(view, params, false); } ...... private void addView(View view, ViewGroup.LayoutParams params, boolean nest) { ...... final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params; ViewRoot root; View panelParentView = null; synchronized (this) { // Here's an odd/questionable case: if someone tries to add a // view multiple times, then we simply bump up a nesting count // and they need to remove the view the corresponding number of // times to have it actually removed from the window manager. // This is useful specifically for the notification manager, // which can continually add/remove the same view as a // notification gets updated. int index = findViewLocked(view, false); if (index >= 0) { if (!nest) { throw new IllegalStateException("View " + view + " has already been added to the window manager."); } root = mRoots[index]; root.mAddNesting++; // Update layout parameters. view.setLayoutParams(wparams); root.setLayoutParams(wparams, true); return; } // If this is a panel window, then find the window it is being // attached to for future reference. if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW && wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { final int count = mViews != null ? mViews.length : 0; for (int i=0; i ';