zoukankan      html  css  js  c++  java
  • 第六篇 ANDROID窗口系统机制之显示机制

    第六篇 ANDROID窗口系统机制之显示机制

    ANDROID的显示系统是整个框架中最复杂的系统之一,涉及包括窗口管理服务、VIEW视图系统、SurfaceFlinger本地服务、硬件加速等。窗口管理服务与SurfaceFlinger本地服务都属于系统服务,客户端采用远程代理模式访问服务,而这部分机制在上一篇博文《窗口管理服务实现机制》已经分析过,本篇主要解析视图如何绘制相关的部分。

    窗口中显示的页面和控件以树的形式组织成一颗以主视图为根的视图树,系统要显示输出时统一调用主视图的draw 函数,由主视图的draw 函数负责各个子视图(如LayoutWidgets等)的递归绘制和效果处理。

     

    主视图的draw 函数由ViewRootImpl对象的draw 函数调用,在draw 函数中根据硬件加速与否采用不同的方式创建不同的画布,并作为参数调用相同的相同的视图绘制函数(视图的draw 接口)。

    在硬件加速打开的情况下, 采用HardwareRenderer对象创建的画布进行硬件加速绘制,否则则采用ViewRootImpl对象对应的Surface对象创建的画布进行软绘制,画布由Surface对象的lockCanvas函数经过JNI调用获取绘制输出缓冲区后初始化一个bitmap,并赋值给Surface对象相关的Canvas对象中的mNativeCanvas 然后返回Canvas对象。以后视图的绘制实际上是在Canvas对象的bitmap上绘制。

          在硬件加速的情况下绘制分两步,第一步是把视图的各种绘制函数作为绘制指令(包含操作指令和 绘制参数)写到DisplayListRenderer对象的SkWriter32对象中,第二步是读取SkWriter32对象中保存的绘制指令调用OPENGL相关函数完成实际绘制。把视图的各种绘制函数翻译成绘制指令保存起来,可以达到重用的目的,在视图绘制过一次且没有或很少发生改变的情况下,在视图重绘时,可以重用原先DisplayListRenderer对象保存的操作指令,不用再按照原先复杂的操作顺序(每一步都需要经过JAVA对象的操作函数通过JNI调用C++本地对象的操作函数)继续重新绘制一遍,而只需对于发生改变的部分按照上面的顺序进行录制及绘制,而对于没有发生改变的视图把原先保存的操作指令重新读取出来重放一次就可以了,提高视图的显示速度。

           整个视图绘制相关的JAVA类图如下图:

         

      视图绘制相关的对象主要由五个对象完成。视图(view)和画布(canvas)对象,视图在具体画布对象上完成各种绘制图形操作,根据不同需求视图绘制可以使用不同的画布对象(当前有三个具体的画布对象,两个硬件加速使用的继承于HardwareCanvasGLES20CanvasGLES20RecordingCanvas,不使用硬件加速的CompatibleCanvas)。通过使用抽象接口和桥接设计模式(Bridge模式)视图的绘制操作可以不管具体的具体画布对象是什么,也就是不论是否使用硬件加速与否,统一由一个绘制函数完成当前视图及子视图的递归绘制,只是根据函数参数传进去不同的画布对象。

      另外三个对象是Gl20RendererGLES20DisplayListGLES20RenderLayer.

       Gl20Renderer对象相当于硬件加速视图绘制模型的呈现引擎,负责整个与硬件加速相关的视图绘制过程,包括创建具体的DisplayListHardwareLayer对象,调用视图相关函数完成DisplayList命令的录制和在HardwareLayer上的绘制,DisplayList录制命令的重放及HardwareLayer在主显示缓冲区上的复合等工作。Gl20Renderer对象对应的画布为GLES20Canvas类型,在Gl20Renderer对象画布上的绘制实际绘制在OPENGL绘制上下文对应的主缓冲区上。Gl20Renderer派生自GlRenderer,GlRenderer又派生自HardwareRenderer。HardwareRenderer可以是说是DisplayListHardwareLayer对象的工厂,采用了工厂方法创建了具体的DisplayListHardwareLayer对象,也采用简单工厂方法实例化其子类Gl20Renderer。Gl20Renderer对象对应的GLES20Canvas画布也是采用工厂方法实例化的。

      GLES20DisplayListGLES20RenderLayer具体负责视图的绘制流程。

       GLES20DisplayList类是DisplayList的具体类GLES20DisplayList对象创建具体的DisplayList对象及绘制用的画布(GLES20RecordingCanvas画布),完成视图绘制操作的DisplayList命令录制等工作。  

       GLES20RenderLayer类是HardwareLayer的具体类,负责创建硬件Layer层和绘制用到的画布(GLES20Canvas)等工作。为了有效支持视图的多层绘制,在3.0以上版本视图对象可以通过创建一个HardwareLayer层完成视图的图形在硬件纹理上的绘制操作或者其它特效操作,这就是GLES20RenderLayer对象的作用,创建独立的层并返回相应的画布供视图绘制使用。GLES20RecordingCanvas画布类是GLES20Canvas画布类的派生类。

      DisplayListHardwareLayer类与相关画布的关系也相当于工厂关系,也是采用了工厂方法由其子类实例化具体的画布,DisplayList的子类GLES20DisplayList还采用了对象池的方法返回对应画布的实例

       JAVA对象创建的每种画布与C++ Render对象一一对应,通过JNI 调用相应的Render 对象完成实际绘制工作。GLES20DisplayList对象创建的画布对应下面的DisplayLis呈现对象(DisplayListRenderer),实际负责视图绘制操作的录制,视图的绘制命令最后保存在本地DisplayList对象中,绘制命令的重放由本地DisplayList对象的replay函数完成。在GLES20RenderLayer对象创建的画布上的绘制由下面的Layer呈现对象(LayerRender)完成。在Gl20Renderer创建的画布上的绘制由其对应的呈现对象(OpenGLRenderer)完成,负责在OPENGL绘制上下文对应的主缓冲区上的绘制。下面是C++层的类图。GLES20DisplayListGl20Renderer都派生自OpenGLRenderer

         

      GLES20DisplayList和GLES20RenderLayer对应的画布对象及Gl20Renderer对应的画布对象分别由GLES20Canvas类的不同构造函数进行实例化。

         如下是三个画布对象的构造函数代码片断。

         /**

         * Creates a canvas to render directly on screen.

             这个构造函数构造直接呈现在屏幕上的画布。

             该构造函数由Gl20Renderer对象调用createCanvas函数调用。

         */

        GLES20Canvas(boolean translucent) {

            this(false, translucent);

        }

        /**

         * Creates a canvas to render into an FBO.

          这个构造函数创建一个呈现在硬件纹理 FramebuferObjectFBO)上的画布,其通过nCreateLayerRenderer(layer) JNI本地接口创建一个LayerRender C++ 对象。

     该构造函数由GLES20RenderLayer对象实例化时调用,由GLES20RenderLayer对象的start函数返回其创建的画布供视图绘制使用。

         */

        GLES20Canvas(int layer, boolean translucent) {

            mOpaque = !translucent;

            mRenderer = nCreateLayerRenderer(layer);

            setupFinalizer();

    }

    /*

    这个构造函数在参数recordtrue时创建一个视图绘制用的DisplayList画布。构造函数通过nCreateDisplayListRenderer JNI本地接口创建一个GLES20DisplayList C++ 对象。GLES20RecordingCanvas对象的实例化就是这种情况,GLES20RecordingCanvas对象由GLES20DisplayList对象的start函数获取并返回。

    参数recordflase时创建一个直接呈现在屏幕上的画布 。构造函数调用nCreateRenderer() JNI本地接口创建一个OpenGLRenderer C++ 对象。

    */

        protected GLES20Canvas(boolean record, boolean translucent) {

            mOpaque = !translucent;

            if (record) {

                mRenderer = nCreateDisplayListRenderer();

            } else {

                mRenderer = nCreateRenderer();

            }

            setupFinalizer();

    }

    /*

    这是上面构造函数调用的对应的JNI接口,分别创建了不同C++ 呈现对象。

    */

    static OpenGLRenderer* android_view_GLES20Canvas_createLayerRenderer(JNIEnv* env,

            jobject clazz, Layer* layer) {

        if (layer) {

            return new LayerRenderer(layer);

        }

        return NULL;

    }

     

         static OpenGLRenderer* android_view_GLES20Canvas_createDisplayListRenderer(JNIEnv* env,

            jobject clazz) {

        return new DisplayListRenderer;

    }

    static OpenGLRenderer* android_view_GLES20Canvas_createRenderer(JNIEnv* env, jobject clazz) {

        RENDERER_LOGD("Create OpenGLRenderer");

        return new OpenGLRenderer;

    }

       视图在GLES20DisplayListGLES20RenderLayer对象的画布上的绘制流程相似,视图在DisplayList对象对应画布上的绘制(绘制指令的录制)通过调用视图的getDisplayList函数完成;视图在GLES20RenderLayer对象(HardwareLayer)对应画布上的绘制通过调用视图的getHardwareLayer函数完成。

     

                          支持硬件加速的VIEW图形绘制序列图

          1、在ViewRootImpl对象的draw 函数调用过程中,在硬件加速打开的情况下, 调用Gl20Renderer draw 函数(mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this, currentDirty), draw 函数第一个参数为要显示的视图,第二个参数传进去的是ViewRootImpl对象的mAttachInfo对象,第三个参数需要类型为HardwareDrawCallbacks的回调,而HardwareDrawCallbacksViewRootImpl类中定义,因此这里传进去this,第四个参数为要绘制的脏区域;

    2draw 函数调用onPreDraw(dirty)函数,其实际调用与Gl20Renderer对象对应的画布对象(类型为GLES20Canvas)的onPreDraw函数,在 画布对象的onPreDraw函数中根据dirty是否为NULL调用JNI本地接口nPrepareDirty或者nPrepare,通过本地对象OpenGLRenderer设置要呈现的脏区域

          3draw 函数接着先调用画布的保存函数(save)保存当前画布的上下文,接着调用回调的onHardwarePreDraw函数,里面调用画布translate 函数进行坐标定位工作;

    4draw 函数接着调用视图的getDisplayList函数进行视图绘制操作的录制工作,通过视图在DisplayList对象创建的画布上的绘制完成操作指令的录制,也就是把操作指令保存到C++层的DisplayList对象中的;

    5、在视图的getDisplayList函数中,首先判断当前视图的DisplayList对象是否存在和是否有效(本视图以前已经正常绘制过,操作指令已经保存) ,如果是则调用ViewGroupdispatchGetDisplayList函数来循环调用各个子视图的getDisplayList函数,然后返回跳到第15步执行;

    6getDisplayList函数调用Gl20Renderer 对象的createDisplayList函数创建DisplayList对象,具体类型为GLES20DisplayList

    7getDisplayList函数接着调用DisplayList对象的start函数返回画布对象,实际画布类型为GLES20RecordingCanvas,对应的C++呈现对象为DisplayListRenderer

    8getDisplayList函数调用GLES20RecordingCanvas画布对象的setViewport函数;

    9 getDisplayList函数接着调用GLES20RecordingCanvas对象onPreDraw函数,通过 JNI nPrepare本地接口调用本地对象DisplayListRenderer设置要显示的脏区域,参数为null设置为整个显示区域;

    10getDisplayList函数接着调用视图的computeScroll函数计算滚动位置;

    11getDisplayList函数接着调用GLES20RecordingCanvas画布对象的translate 函数,该函数实际一方面通过JNI接口调用本地DisplayListRenderer对象把该操作翻译成操作指令保存到本地mWriter对象中,另一方面还调用父类OpenGLRenderertranslate(dx, dy)函数完成坐标转换工作;对于显示区域方面的操作DisplayListRenderer对象都是先保存操作指令,然后调用OpenGLRenderer类的相同函数完成实际工作;

    12getDisplayList函数接着调用视图的dispatchDraw(canvas)函数或着draw(canvas)函数进行视图的图形元素在画布上(GLES20RecordingCanvas)的递归绘制,实际通过JNI调用DisplayListRenderer对象的相应函数把操作翻译成操作指令保存到mWriter对象中;

    13、视图递归绘制结束后,getDisplayList函数接着调用GLES20RecordingCanvas画布对象的onPostDraw()函数,其通过JNI nFinish本地接口调用本地对象DisplayListRendererfinish完成一些绘制完成后的操作;

    14、在getDisplayList函数最后调用DisplayList对象的end()函数,DisplayList对象的end()函数又调用GLES20RecordingCanvas画布对象的end函数;GLES20RecordingCanvas画布对象的end函数通过JNI调用本地DisplayListRenderer对象的getDisplayList函数实例化一个本地DisplayList对象(或者重用原先已创建的本地DisplayList对象),把上面录制的操作指令保存到该对象中,mWriter对象中录制的操作指令复制到如本地DisplayList对象的mReader中;本地DisplayListRenderer对象的getDisplayList函数返回实例化的本地DisplayList对象引用保存到GLES20DisplayList对象中供下次重用;DisplayList对象的end()函数接着调用画布的recycle函数通过JNI调用把DisplayListRenderer对象的mWriter对象复位,资源释放。视图绘制结束,getDisplayList函数返回;

    15Gl20Renderer draw 函数接着调用对应画布的drawDisplayList函数,其通过JNI接口调用OpenGLRenderer对象的drawDisplayList函数;在OpenGLRenderer对象的drawDisplayList函数中调用在上一步实例化的本地DisplayList对象的replay函数,完成上面保存的操作指令的重放;

    16Gl20Renderer draw 函数接着先调用回调的onHardwarePreDraw函数,然后调用画布的restoreToCount函数进行第三步保存的画布场景的恢复。在onHardwarePreDraw函数中如果mResizeBuffer不为NULL时(mResizeBuffer对象是一个HardwareLayer对象,是在ViewRootImpl对象在执行performTraversals函数中在视图界面需要resize时创建和绘制的),调用画布的drawHardwareLayer 函数 ,把mResizeBuffer层和Gl20Renderer 对应的画布(对应主显示缓冲区)进行复合;

    17draw 函数接着调用onPostDraw函数,进行主画布绘制结束后的一些操作;

    18 最后调用EGL10eglSwapBuffers函数把主画布的缓冲区刷新到硬件屏幕上,完成整个视图绘制和刷新显示工作。

     

                                      

                             子视图的layerType LAYER_TYPE_HARDWARE时的绘制序列图

          视图在独立HardwareLayer层上的绘制,实际是在GLES20RenderLayer对象对应的本地LayerRender对象的FBO(硬件纹理)上绘制输出的。在完成层画布绘制后或者调用GLES20DisplayList对象对应画布的drawHardwareLayer 函数在DisplayList对象中添加层绘制指令,或者调用Gl20Renderer 对应画布的drawHardwareLayer 函数 ,把层和Gl20Renderer 对应的主显示画布进行复合。

    上图为在视图递归绘制时碰到子视图的layerType LAYER_TYPE_HARDWARE时的绘制序列图,由ViewGroup 对象的drawChild函数触发。视图在硬件层上的绘制工作主要通过视图的getHardwareLayer函数完成。getHardwareLayer函数的绘制流程和getDisplayList函数的绘制流程相似。整个流程如下:

          1、 在视图的mHardwareLayer对象没有创建时首先要通过Gl20Renderer 对象的createHardwareLayer函数创建一个,实际类型为GLES20RenderLayer

          2、 先保存当前画布,然后调用GLES20RenderLayer对象的start 函数返回一个画布作为当前画布,返回的画布类型为GLES20Canvas类型,但底层对应的呈现对象为LayerRender对象;

          3、 和主视图的绘制相似,接着首先调用上一步返回的画布对象的setViewpor函数、onPreDraw函数,然后调用画布的save()函数保存画布场景,然后调用视图computeScroll函数,接着调用画布的translate函数;

          4、 接着调用视图的dispatchDraw(canvas)函数或draw(canvas)函数进行子视图的图形元素在GLES20RenderLayer对象对应画布上(GLES20Canvas)的递归绘制,实际是通过JNI调用LayerRender对象的相应函数进行视图界面元素在硬件层上的绘制;

          5、 完成子视图在HardwareLayer层上的递归绘制后,先调用画布的restoreToCount函数恢复原先保存的画布场景,再调用画布的onPostDraw()进行绘制前的结束清理工作,接着调用GLES20RenderLayer对象的end函数结束本次绘制操作,并恢复第二步保存的旧的画布后返回;

        最后一步调用GLES20DisplayList对象对应画布的drawHardwareLayer 函数,在DisplayListRenderer对象中添加层绘制指令,结束本次流程。

  • 相关阅读:
    TCP为什么是个可靠的协议
    socket网络编程快速上手(二)——细节问题(4)
    socket网络编程快速上手(二)——细节问题(3)
    socket网络编程快速上手(二)——细节问题(2)
    socket网络编程快速上手(二)——细节问题(1)
    socket网络编程快速上手(一)
    多线程编程中使用pthread_create内存泄露问题
    股市量化智能分析2035
    忘记mysql root密码的解决方法
    CentOS 下快速安装部署Nginx网站
  • 原文地址:https://www.cnblogs.com/liangxiaofeng/p/3491418.html
Copyright © 2011-2022 走看看