Android 视图渲染过程的源码分析

来源:互联网 发布:淘宝大股东是日本谁 编辑:程序博客网 时间:2024/04/30 04:49

我相信大部分Android开发工程师都知道通过 setContentView(@LayoutRes int layoutResID)就可以把自己layout布局文件的视图结构显示出来。但是鲜有人去研究Android系统是怎么把这些视图给渲染显示出来的。由于工作的需要,我尝试过很多次去研究,去探索。但不可否认这个过程很痛苦,很头疼。看FrameWork层源码无疑是最头疼的,但是没办法,这是工作的重中之重,只好逼着自己去慢慢研究。看过了很多大神写过的文章,深有启发,所以我觉得自己应该把自己的收获分享给大家。下面我将从源码的角度上来分析,在这个过程中有些是借助各路大神的指点,至于是谁我也记不清了,在此谢过各位大神的文章指点。好了,废话少说,进入正题。这篇博文只是初步介绍,深入了解可以关注我的下一篇博文WMS管理应用程序窗口
一、Activity与Context的纠缠
想要弄懂Android UI视图是怎么显示出来的,我们要从最经常打交道的Activity开始入手。 Activity类是从ContextThemeWrapper类继承下来的,而ContextThemeWrapper类又是从ContextWrapper类继承下来的,最后ContextWrapper类又继承了Context类。这个Context类是我们的交通枢纽,重中之重。我们先来看看Activity组件的类关系图:
这里写图片描述
从这个类图可以看出入口点在Activity的attach()方法:

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, String referrer, IVoiceInteractor voiceInteractor) {    attachBaseContext(context);    mFragments.attachHost(null /*parent*/);    mWindow = new PhoneWindow(this);    mWindow.setCallback(this);    mWindow.setOnWindowDismissedCallback(this);    mWindow.getLayoutInflater().setPrivateFactory(this);    if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {        mWindow.setSoftInputMode(info.softInputMode);    }    if (info.uiOptions != 0) {        mWindow.setUiOptions(info.uiOptions);    }    mUiThread = Thread.currentThread();    mMainThread = aThread;    mInstrumentation = instr;    mToken = token;    mIdent = ident;    mApplication = application;    mIntent = intent;    mReferrer = referrer;    mComponent = intent.getComponent();    mActivityInfo = info;    mTitle = title;    mParent = parent;    mEmbeddedID = id;    mLastNonConfigurationInstances = lastNonConfigurationInstances;    if (voiceInteractor != null) {        if (lastNonConfigurationInstances != null) {            mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;        } else {            mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,                    Looper.myLooper());        }    }    mWindow.setWindowManager(            (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),            mToken, mComponent.flattenToString(),            (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);    if (mParent != null) {        mWindow.setContainer(mParent.getWindow());    }    mWindowManager = mWindow.getWindowManager();    mCurrentConfig = config;}

attach()方法里面又调用attachBaseContext(Context newBase),不管三七二十一,进去看看再说。

protected void attachBaseContext(Context newBase) {    super.attachBaseContext(newBase);    this.mBase = newBase;}

从上面的代码我们可以看到,Activity组件在启动的过程中,系统会为它创建一个ContextImpl对象,用来描述它的运行上下文环境。这个ContextImpl对象首先是通过调用Acitivity类的成员函数attach传递到Activity组件内部,接着再依次通过调用父类ContextThemeWrapper和ContextWrapper的成员函数attachBaseContext来分别保存在它们的成员变量mBase中。因此,ContextThemeWrapper和ContextWrapper类的成员变量mBase指向的实际上是一个ContextImpl对象。系统为一个正在启动的Activity组件创建了一个ContextImpl对象之后,还会调用这个ContextImpl对象的成员函数setOuterContext来将正在启动的Activity组件保存在其成员变量mOuterContext中。这样,一个Activity组件就可以通过其父类ContextThemeWrapper或者ContextWrapper的成员变量mBase来访问用来描述它的运行上下文环境的一个ContextImpl对象,同时,一个ContextImpl对象也可以通过它的成员变量mOuterContext来访问它的宿主Activity组件。

final void setOuterContext(Context context) {    mOuterContext = context;}

你会不会觉得上面说的跟视图渲染没半毛钱关系啊,别着急,心急是吃不了热豆腐的。你肯定发现Activity类还有另外一个类型为WindowManager的成员变量mWindowManager,它实际上指向的一个LocalWindowManager对象。LocalWindowManager类是用来管理应用程序窗口的,例如,用来维护应用程序窗口内部的视图(View)。LocalWindowManager类有一个类型为WindowManager的成员变量mWindowManager,它实际上指向的是一个WindowManagerImpl对象。系统通过调用WindowManagerImpl类的静态成员函数getDefault来获得一个WindowManagerImpl对象,然后保存在LocalWindowManager类的成员变量mWindowManager中。这样,LocalWindowManager类就可以通过WindowManagerImpl类来真正实现管理应用程序窗口的功能。看到这里你是不是一头雾水,这里肯定是看不出一个Activity组件的窗口是如何描述的,那我们继续呗。
二、window类的实现过程
为了弄清出这个问题,不知道你是否还记得Activity类的还有另外一个成员变量mWindow,那我们现在就去看看这个mWindow是什么。Activity类的成员变量mWindow的类型为Window,它用来描述一个应用程序窗口。这样,通过这个成员变量,每一个Activity组件就都会有一个对应的Window对象,即一个对应的应用程序窗口。先来看看Window类的实现图
这里写图片描述
window其实就是窗口,那个窗口用来干嘛,显示视图呗,我相信你肯定知道显示视图要经过Activity调用setContentView(),那就进去瞧瞧。

/** * 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(@LayoutRes int layoutResID) {    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "window.setContentView");    getWindow().setContentView(layoutResID);    Trace.traceEnd(Trace.TRACE_TAG_VIEW);    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "initWindowDecorActionBar");    initWindowDecorActionBar();    Trace.traceEnd(Trace.TRACE_TAG_VIEW);}/** * 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();}/** 1. Set the activity content to an explicit view.  This view is placed 2. directly into the activity's view hierarchy.  It can itself be a complex 3. view hierarchy. 4.  5. @param view The desired content to display. 6. @param params Layout parameters for the view. 7.  8. @see #setContentView(android.view.View) 9. @see #setContentView(int) */public void setContentView(View view, ViewGroup.LayoutParams params) {    getWindow().setContentView(view, params);    initWindowDecorActionBar();}

Window类有一个类型为Context的成员变量mContext。这个成员变量指向的是一个Activity对象。当系统为一个Activity组件创建一个对应的Window对象时,就会将这个Activity组件的Context接口保存在这个对应的Window对象的成员变量mContext中。这样,一个Window对象就可以通过它的成员变量mContext来访问它所描述的Activity组件的资源。Window类还有一个类型为Window.Callback的成员变量mCallback。这个成员变量和成员变量mContext一样,都是指向同一个Activity对象,因为Activity类是实现了Window.Callback接口的。当系统为一个Activity组件创建一个对应的Window对象时,就会将这个Activity组件所实现的Window.Callback接口通过Window类的成员函数setCallback保存在对应的Window对象的成员变量mCallback。这样,一个Window对象就可以通过它的成员变量mCallback来将一些事件交给与它所对应的Activity组件来处理,例如,将接收的键盘事件交给对应的Activity组件来处理。

public void setCallback(Callback callback) {    mCallback = callback;}

最后,Window类还有一个类型为WindowManager的成员变量mWindowManager。这个成员变量指向的是一个LocalWindowManager对象。前面提到,Activity组件的成员变量mWindowManager指向的也是一个LocalWindowManager对象。系统在启动一个Activity组件的过程中,会通过Window类的成员函数setWindowManager来将保存在它的成员变量mWindowManager中的一个LocalWindowManager对象也保存在对应的Window对象的成员变量mWindowManager。这样,一个Activity组件以及它所对应的Window对象就可以使用同一个LocalWindowManager对象来管理它们所描述的UI了。其实不知道你还记得attach()方法有这么一行:

 mWindow = new PhoneWindow(this); //在Activity.java中new 一个PhoneWindow

事实上,Activity类的成员变量mWindow指向的并不是一个Window对象,而是一个PhoneWindow对象。也就是说,一个Activity组件的UI是使用一个PhoneWindow对象来描述的。 PhoneWindow类继承了Window类,因此,它的对象可以保存Activity类的成员变量mWindow中。PhoneWindow类的实现如下图所示:
这里写图片描述
用上面这张图上可以看出 PhoneWindow类有两个重要的成员变量mDecor和mContentParent,它们的类型分别DecorView和ViewGroup。其中,成员变量mDecor是用描述自己的窗口视图,而成员变量mContentParent用来描述视图内容的父窗口。

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 ViewGroup mContentRoot;    protected DecorView generateDecor() {        return new DecorView(getContext(), -1);    }}

DecorView类继承了FrameLayout类,而FrameLayout类又继承了ViewGroup类,最后ViewGroup类又继承了View类。View类有一个成员函数draw,它是用来绘制应用程序窗口的UI的。DecorView类、FrameLayout类和ViewGroup类都重写了父类的成员函数draw,这样,它们就都可以定制自己的UI。

 private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {          public DecorView(Context context, int featureId) {            super(context);            mFeatureId = featureId;            mShowInterpolator = AnimationUtils.loadInterpolator(context,                    android.R.interpolator.linear_out_slow_in);            mHideInterpolator = AnimationUtils.loadInterpolator(context,                    android.R.interpolator.fast_out_linear_in);            mBarEnterExitDuration = context.getResources().getInteger(                    R.integer.dock_enter_exit_duration);        }        public void setBackgroundFallback(int resId) {            mBackgroundFallback.setDrawable(resId != 0 ? getContext().getDrawable(resId) : null);            setWillNotDraw(getBackground() == null && !mBackgroundFallback.hasFallback());        }        @Override        public void onDraw(Canvas c) {            super.onDraw(c);            mBackgroundFallback.draw(mContentRoot, c, mContentParent);        }     .     .     .}

DecorView类所描述的应用程序窗口视图是否需要重新绘制是由另外一个类ViewRoot来控制的。系统在启动一个Activity组件的过程中,会为这个Activity组件创建一个ViewRoot对象,同时还会将前面为这个Activity组件所创建的一个PhoneWindow对象的成员变量mDecor所描述的一个视图(DecorView)保存在这个ViewRoot对象的成员变量mView中。这样,这个ViewRoot对象就可以通过调用它的成员变量mView的所描述的一个DecorView的成员函数draw来绘制一个Acitivity组件的UI了。ViewRoot类的作用是非常大的,它除了用来控制一个Acitivity组件的UI绘制之外,还负责接收Acitivity组件的IO输入事件。下面我们来看看ViewRoot类的具体结构层次:
这里写图片描述
那么ViewRoot主要是负责什么的呢?ViewRoot主要有以下几个职责:

  1. 负责为应用程序窗口视图创建Surface。
  2. 配合WindowManagerService来管理系统的应用程序窗口
  3. 负责管理、布局和渲染应用程序窗口视图的UI

从上图可以看出来ViewRoot是从Handler类继承下来的。从Handler类继承下来的子类可以调用父类的成员函数sendMessage来向指定的线程的消息队列发送消息,以及在自己重写的成员函数handleMessage中处理该消息。 ViewRoot类在以下两种情况需要应用程序进程的主线程的消息队列发送消息。

  1. 当ViewRoot类从系统输入管理器InputManager接收到键盘、触摸屏等输入事件时,它就会把这些输入事件封装成一个消息,并且发送到应用程序进程的主线程的消息队列中去进一步处理,这样就可以保证键盘、触摸屏等输入事件可以在应用程序进程的主线程中进行处理
  2. 当ViewRoot类需要重新绘制与它所关联的一个Activity组件的UI时,它就会将这个绘制UI的操作封装成一个消息,并且发送到应用程序进程的主线程的消息队列中去进一步处理,这样同样可以保证绘制UI的操作可以在应用程序进程的主线程中执行。

    注意:每一个ViewRoot对象都有一个类型为View的成员变量mView,它指向了一个DecorView对象。这个DecorView对象是从哪里来的呢?我们知道每一个Activity组件都有一个对应的ViewRoot对象以及一个对应的PhoneWindow对象,这个DecorView对象就是来自于这个对应的PhoneWindow对象的成员变量mDecor。也就是说,与同一个Activity组件对应的ViewRoot对象和PhoneWindow对象分别通过各自的成员变量mView和mDecor引用了共一个DecorView对象。每一个ViewRoot对象都有一个类型为WindowManager.LayoutParams的成员变量mWindowAttributes,它指向了一个ViewGroup.LayoutParams对象,用来描述与该ViewRoot对象对应的一个Activity组件的UI布局信息。
    总结:
    从上面的分析可以作出这样子的总结,每一个Activity组件都有一个对应的ViewRoot对象、View对象以及WindowManager.LayoutParams对象。这三个对象的对应关系是由WindowManagerImpl类来维护的。具体来说,就是由WindowManagerImpl类的成员变量mRoots、mViews和mParams所描述的三个数组来维护的。例如,假设一个应用程序进程运行有两个Activity组件,那么WindowManagerImpl类的成员变量mRoots、mViews和mParams所描述的三个数组的大小就等于2,其中,mRoots[0]、mViews[0]和mParams[0]对应于第一个启动的Activity组件,而mRoots[1]、mViews[1]和mParams[1]对应于第二个启动的Activity组件。每一个ViewRoot对象都有一个类型为Surface的成员变量mSurface,它指向了一个Java层的Surface对象。这个Java层的Surface对象通过它的成员变量mNativeSurface与一个C++层的Surface对象。这个Surface类是用来在Android应用程序进程这一侧描述应用程序窗口的。在C++层中,每一个Surface对象都有一个对应的SurfaceControl对象。这个对应的SurfaceControl对象是用来设置应用程序窗口的属性,例如,设置大小、位置等属性。 但是,与ViewRoot类的成员变量mSurface所对应的在C++层的Surface对象并没有一个对应的SurfaceControl对象,这是因为ViewRoot类并不需要设置应用程序窗口的属性,它需要做的只是往应用程序窗口的图形缓冲区填充UI数据,即它需要设置的只是应用程序窗口的纹理。应用程序窗口的纹理保存在Java层的Surface类的成员变量mCanvas所描述一个画布(Canvas)中,即通过这个画布可以访问到应用程序窗口的图形缓冲区。当ViewRoot类需要重新绘制与它对应的Activity组件的UI时,它就会调用它的成员函数draw来执行这个绘制的操作。ViewRoot类的成员函数draw首先通过获得保存它的成员变量mSurface内部的一块画布,然后再将这个画布传递给它的成员变量mView所描述的一个View对象的成员函数draw。View类的成员函数draw得到了这块画布之后,就可以随心所欲地上面绘制应用程序窗口的纹理了。这些纹理的绘制工作是通过Skia图形库API来进行的。那么应用程序窗口的属性是由谁来管理的呢?敬请关注下回分析!

0 0
原创粉丝点击