美文网首页
surfaceview和view绘制的区别

surfaceview和view绘制的区别

作者: 长点点 | 来源:发表于2024-01-04 15:50 被阅读0次

    一、SurfaceView和View的绘制流程

    1.View的绘制流程

    View的绘制流程可以分为三个阶段:measure(测量)、layout(布局)和draw(绘制)。这三个阶段是从ViewRootImpl的performTraversals()方法开始,自上而下地遍历整个View树,对每个View进行相应的操作。

    measure阶段的目的是确定每个View的宽度和高度。

    这个阶段会调用每个View的measure()方法,该方法会根据View的LayoutParams和父View的MeasureSpecs来计算出View的MeasureSpecs,然后传递给View的onMeasure()方法,该方法会根据View的MeasureSpecs和自身的内容来设置View的MeasuredWidth和MeasuredHeight。View的MeasureSpecs是一个32位的整数,其中高2位表示测量模式(MeasureSpec.EXACTLY、MeasureSpec.AT_MOST或MeasureSpec.UNSPECIFIED),低30位表示测量大小。View的MeasuredWidth和MeasuredHeight是一个16位的整数,表示View在测量阶段的宽度和高度。

    layout阶段的目的是确定每个View的位置。

    这个阶段会调用每个View的layout()方法,该方法会根据View的MeasuredWidth和MeasuredHeight以及父View的位置和边距来设置View的Left、Top、Right和Bottom。这四个属性表示View在父View坐标系中的位置和大小。View的layout()方法会调用View的onLayout()方法,该方法会根据View的子View的MeasuredWidth和MeasuredHeight以及View自身的布局规则来设置子View的位置。View的onLayout()方法是一个抽象方法,需要由具体的子类来实现,例如LinearLayout、RelativeLayout等。

    draw阶段的目的是将每个View的内容绘制到屏幕上。

    这个阶段会调用每个View的draw()方法,该方法会创建一个Canvas对象,该对象封装了一个Bitmap对象,该Bitmap对象表示View的绘制缓冲区。View的draw()方法会调用View的onDraw()方法,该方法会使用Canvas对象的绘图方法来绘制View的内容,例如drawText()、drawBitmap()等。View的draw()方法还会调用View的dispatchDraw()方法,该方法会遍历View的子View,并调用子View的draw()方法,从而实现View树的递归绘制。View的绘制缓冲区最终会被合成到屏幕上,这个过程由硬件加速或软件渲染来完成。

    下面是View的绘制流程的源码分析,以ViewRootImpl的performTraversals()方法为入口:

    void performTraversals() {
        // ..........
        boolean viewScrolled = false;
        // 第一次绘制或者窗口大小发生变化时,执行measure阶段
        if (mFirst || mReportNextDraw) {
            mFullRedrawNeeded = true;
            mLayoutRequested = true;
        }
        boolean windowSizeMayChange = false;
        // 获取窗口的宽度和高度
        int desiredWindowWidth = mWinFrame.width();
        int desiredWindowHeight = mWinFrame.height();
        // ..........
        // 如果需要执行measure阶段
        if (mLayoutRequested && !mStopped) {
            // ..........
            // 调用View的measure()方法,传入窗口的宽度和高度作为MeasureSpecs
            performMeasure(desiredWindowWidth, desiredWindowHeight);
            // 获取View的MeasuredWidth和MeasuredHeight
            int width = host.getMeasuredWidth();
            int height = host.getMeasuredHeight();
            // ..........
        }
        // ..........
        // 如果需要执行layout阶段
        if ((mLayoutRequested || windowSizeMayChange) && !mStopped) {
            // ..........
            // 调用View的layout()方法,传入View的Left、Top、Right和Bottom
            performLayout(lp, desiredWindowWidth, desiredWindowHeight);
            // ..........
        }
        // ..........
        // 如果需要执行draw阶段
        if (!mStopped) {
            // ..........
            // 调用View的draw()方法,传入一个Canvas对象
            performDraw(canvas);
            // ..........
        }
        // ..........
    }
    
    
    View绘制流程示意图

    2.SurfaceView的绘制流程

    SurfaceView的绘制流程可以分为两个阶段:create(创建)和draw(绘制)。这两个阶段是通过SurfaceHolder的回调方法来触发的,create阶段的目的是创建一个Surface,draw阶段的目的是在Surface上绘制内容。

    create阶段

    调用SurfaceHolder.Callback的surfaceCreated()和surfaceChanged()方法,这两个方法会在Surface被创建或者改变时被调用。在这个阶段,可以获取SurfaceHolder对象,该对象封装了一个Surface对象,该Surface对象表示SurfaceView的绘制缓冲区。可以在这个阶段创建一个绘制线程,并将SurfaceHolder对象传递给该线程,以便在后台进行绘制操作。

    draw阶段

    调用SurfaceHolder.Callback的surfaceDestroyed()方法,该方法会在Surface被销毁时被调用。在这个阶段,可以停止绘制线程,并释放SurfaceHolder对象。绘制线程可以在运行时获取SurfaceHolder对象的锁,然后获取一个Canvas对象,该对象封装了Surface对象,然后使用Canvas对象的绘图方法来绘制内容,例如drawText()、drawBitmap()等。绘制完成后,需要释放Canvas
    下面是SurfaceView的绘制流程的源码分析,以SurfaceView的init()方法为入口:

    private void init() {
        // ..........
        // 创建一个SurfaceHolder对象,该对象封装了一个Surface对象
        mSurfaceHolder = new SurfaceHolder() {
            // ..........
            // 获取Surface对象的锁
            @Override
            public Canvas lockCanvas() {
                return internalLockCanvas(null, false);
            }
            // ..........
            // 释放Surface对象的锁,并将绘制内容合成到屏幕上
            @Override
            public void unlockCanvasAndPost(Canvas canvas) {
                mSurface.unlockCanvasAndPost(canvas);
                // ..........
            }
            // ..........
            // 添加SurfaceHolder.Callback对象,用于监听Surface的状态变化
            @Override
            public void addCallback(Callback callback) {
                synchronized (mCallbacks) {
                    // This is a linear search, but in practice we'll
                    // have only a couple callbacks, so it doesn't matter.
                    if (mCallbacks.contains(callback) == false) {
                        mCallbacks.add(callback);
                    }
                }
            }
            // ..........
        };
        // ..........
    }
    
    SurfaceView绘制流程示意图

    二、SurfaceView和View的绘制原理

    1.View的绘制原理

    View的绘制是基于Window的Surface来实现的,Window的Surface是一个原生的缓冲区,用来保存当前窗口的像素数据,它是由屏幕显示内容合成器(Screen Compositor)所管理的。屏幕显示内容合成器是一个系统级的服务,它负责将所有Window的Surface合成到屏幕上,形成最终的显示效果。屏幕显示内容合成器的具体实现可能有所不同,例如SurfaceFlinger、HWComposer等,但它们的基本原理都是类似的。


    View使用的Canvas绘制合成思维导图

    View的绘制过程中,会通过Window的lockCanvas方法,锁定Window的Surface中的Canvas对象,用来在Surface上绘制View的内容,绘制完成后,会通过unlockCanvasAndPost方法,解锁Surface中的Canvas对象,并将绘制结果提交到Surface的缓冲区中。Surface会将缓冲区中的内容交给屏幕显示内容合成器,由它来将Surface的内容合成到屏幕上,完成绘制流程。

    下面我们重点分析draw阶段的源码,draw阶段的源码如下:

        private void performDraw(Canvas canvas) {
            // .............
            // 如果没有传入Canvas对象,就从Window的Surface中
            // 获取一个Canvas对象,用于在Surface上绘制View的内容
            if (canvas == null) {
                // If the view hierarchy contains a SurfaceView that is not
                // updated (asynchronous mode), we will use the previous frame
                // to render the view hierarchy. This means we won't see the
                // updated state of the SurfaceView but there is no way around
                // this, unless we were to wait for the new frame to be ready.
                // But then, we would end up with a blank frame and drop below
                // 60 fps.
                if (mAttachInfo.mThreadedRenderer != null) {
                    canvas = mAttachInfo.mThreadedRenderer.getCanvas();
                } else {
                    canvas = mSurface.lockCanvas(mDirty);
                }
            }
            // .............
            // 调用View的draw()方法,传入Canvas对象,用于在Surface上绘制View的内容,
            // 该方法会先绘制View的背景,然后根据是否需要边缘渐变效果,
            // 调用onDraw方法或者drawWithFadingEdges方法,用于在Surface上绘制View的内容,
            // 然后调用dispatchDraw方法,用于在Surface上绘制View的子View的内容,
            // 然后调用onDrawForeground方法,用于在Surface上绘制View的前景,
            // 最后调用debugDrawFocus方法,用于在Surface上绘制View的焦点状态
            mAttachInfo.mView.draw(canvas);
            // .............
            // 如果从Window的Surface中获取了Canvas对象,
            // 就释放Surface中的Canvas对象,
            // 并将绘制结果提交到Surface的缓冲区中
            if (mAttachInfo.mThreadedRenderer == null) {
                mSurface.unlockCanvasAndPost(canvas);
            }
            // .............
        }
    

    如果没有传入Canvas对象,就从Window的Surface中获取一个Canvas对象,用于在Surface上绘制View的内容,该过程是通过Surface的lockCanvas方法实现的,该方法会返回一个Canvas对象,用于在Surface上绘制内容。该方法的源码如下:

    public Canvas lockCanvas(Rect inOutDirty) throws Surface.OutOfResourcesException, 
    IllegalArgumentException {
        synchronized (mLock) {
            checkNotReleasedLocked();
            if (mLockedObject != 0) {
                throw new IllegalArgumentException("Surface was already locked");
            }
            mLockedObject = nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);
            return mCanvas;
        }
    }
    

    lockCanvas方法主要做了以下几件事:

    1. 同步锁定mLock对象,用于保证线程安全。

    2. 检查Surface是否已经被释放,如果是,抛出异常。

    3. 检查Surface是否已经被锁定,如果是,抛出异常。

    4. 调用nativeLockCanvas方法,传入mNativeObject、mCanvas和inOutDirty参数,该方法会返回一个mLockedObject对象,用于标识Surface的锁定状态。

    5. 返回mCanvas对象,用于在Surface上绘制内容。

    调用View的draw()方法,传入Canvas对象,用于在Surface上绘制View的内容,draw()方法会先绘制View的背景,然后根据是否需要边缘渐变效果,调用onDraw方法或者drawWithFadingEdges方法,用于在Surface上绘制View的内容,然后调用dispatchDraw方法,用于在Surface上绘制View的子View的内容,然后调用onDrawForeground方法,用于在Surface上绘制View的前景,最后调用debugDrawFocus方法,用于在Surface上绘制View的焦点状态。该方法的源码如下:

    public void draw(Canvas canvas) {
            final int privateFlags = mPrivateFlags;
            final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                    (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
            mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
    
            if (!dirtyOpaque) {
                drawBackground(canvas);
            }
    
            final int viewFlags = mViewFlags;
            boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
            if (!verticalEdges && !horizontalEdges) {
                // common case onDraw(canvas); 
            } else {
                // …
            }
    
            dispatchDraw(canvas);
    
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }
    
            onDrawForeground(canvas);
    
            if (debugDraw()) {
                debugDrawFocus(canvas);
            }
        }
    

    draw()方法主要做了以下几件事:

    1. 清除View的脏标志,设置View的绘制标志,表示View已经被绘制过。
    2. 如果View不是完全不透明的,绘制View的背景,该方法会调用Drawable的draw方法,传入Canvas对象,用于在Surface上绘制Drawable的内容。
    3. 根据View的边缘渐变标志,判断是否需要绘制边缘渐变效果,如果不需要,直接调用onDraw方法,传入Canvas对象,用于在Surface上绘制View的内容,该方法是一个空方法,一般由子类重写,实现具体的绘制逻辑,例如TextView会绘制文本,ImageView会绘制图片等。如果需要,调用drawWithFadingEdges方法,传入Canvas对象,用于在Surface上绘制带有边缘渐变效果的View的内容,该方法会先保存Canvas的状态,然后根据边缘渐变的方向,裁剪Canvas的区域,然后调用onDraw方法,传入Canvas对象,用于在Surface上绘制View的内容,最后恢复Canvas的状态。
    4. 调用dispatchDraw方法,传入Canvas对象,用于在Surface上绘制View的子View的内容,该方法会遍历View的子View列表,根据它们的可见性、透明度、动画等属性,决定是否需要绘制,如果需要,就调用子View的draw方法,传入Canvas对象,用于在Surface上绘制子View的内容,该方法是一个递归的过程,直到所有的子View都被绘制完毕。
    5. 如果View有Overlay,且Overlay不为空,调用Overlay的getOverlayView方法,获取Overlay的View对象,然后调用Overlay的View对象的dispatchDraw方法,传入Canvas对象,用于在Surface上绘制Overlay的内容,该方法与View的dispatchDraw方法类似,也是一个递归的过程,直到所有的Overlay的子View都被绘制完毕。
    6. 调用onDrawForeground方法,传入Canvas对象,用于在Surface上绘制View的前景,该方法会绘制View的滚动条、前景Drawable等内容。
    7. 如果开启了调试模式,调用debugDrawFocus方法,传入Canvas对象,用于在Surface上绘制View的焦点状态,该方法会绘制View的边框、焦点框等内容。

    如果从Window的Surface中获取了Canvas对象,就释放Surface中的Canvas对象,并将绘制结果提交到Surface的缓冲区中,该过程是通过Surface的unlockCanvasAndPost方法实现的,该方法会释放Surface中的Canvas对象,并将绘制结果提交到Surface的缓冲区中。unlockCanvasAndPost方法的源码如下:

    public void unlockCanvasAndPost(Canvas canvas) {
        synchronized (mLock) {
            checkNotReleasedLocked();
            if (mLockedObject == 0) {
                throw new IllegalStateException("Surface was not locked");
            }
            nativeUnlockCanvasAndPost(mNativeObject, canvas);
            nativeRelease(mLockedObject);
            mLockedObject = 0;
        }
    }
    

    unlockCanvasAndPost方法主要做了以下几件事:

    1. 同步锁定mLock对象,用于保证线程安全。

    2. 检查Surface是否已经被释放,如果是,抛出异常。

    3. 检查Surface是否已经被锁定,如果否,抛出异常。

    4. 调用nativeUnlockCanvasAndPost方法,传入mNativeObject和canvas参数,该方法会将绘制结果提交到Surface的缓冲区中,并通知SurfaceFlinger进行合成。

    5. 调用nativeRelease方法,传入mLockedObject参数,该方法会释放Surface的锁定状态,并减少Surface的强引用计数。

    Surface会将缓冲区中的内容交给屏幕显示内容合成器,由它来将Surface的内容合成到屏幕上,完成绘制流程。屏幕显示内容合成器是一个系统级的服务,它负责将所有Window的Surface和其他层合成到屏幕上,形成最终的显示效果。屏幕显示内容合成器的源码位于frameworks/native/services/surfaceflinger目录下,它是一个C++的程序,使用OpenGL ES来进行图形渲染。屏幕显示内容合成器的主要类和方法如下:

    • SurfaceFlinger类,它是屏幕显示内容合成器的核心类,它管理着所有的Surface和Layer,以及与客户端的通信和与硬件的交互。

    • SurfaceFlinger::onMessageReceived方法,它是SurfaceFlinger的消息处理方法,它会根据不同的消息类型,执行不同的操作,例如创建Surface、销毁Surface、更新Surface、合成Surface等。

    • SurfaceFlinger::doComposition方法,它是SurfaceFlinger的合成方法,它会遍历所有的Layer,根据它们的Z-order(层级顺序),将它们的内容绘制到一个FrameBuffer对象上,然后将FrameBuffer对象的内容显示到屏幕上。

    View绘制原理示意图

    2.SurfaceView的绘制原理

    SurfaceView的绘制原理是基于SurfaceView自己的Surface来实现的,SurfaceView的Surface是一个独立于Window的Surface,用来展示Surface中的数据。

    SurfaceView使用的Canvas绘制合成思维导图

    以SurfaceView的init()方法为入口,init()方法是SurfaceView的初始化方法,它会创建一个SurfaceHolder对象,用于管理SurfaceView的Surface,以及一个SurfaceViewUpdateThread对象,用于在非UI线程中更新SurfaceView的内容。

    init()方法的源码如下:

    private void init() {
        // .............
        // 创建一个SurfaceHolder对象,用于管理SurfaceView的Surface
        mSurfaceHolder = new SurfaceHolder() {
            // .............
        };
        // 创建一个SurfaceViewUpdateThread对象,用于在非UI线程中更新SurfaceView的内容
        mSurfaceViewUpdateThread = new SurfaceViewUpdateThread();
        // .............
    }
    
    

    init()方法主要做了以下几件事:

    • 创建一个SurfaceHolder对象,用于管理SurfaceView的Surface,该对象提供了一些方法,用于获取、锁定、解锁和销毁Surface,以及设置Surface的格式、尺寸、回调等属性。
    • 创建一个SurfaceViewUpdateThread对象,用于在非UI线程中更新SurfaceView的内容,该对象是一个继承自Thread的子类,它重写了run()方法,用于在循环中不断地绘制SurfaceView的内容。

    下面重点分析SurfaceView的绘制过程,通过SurfaceHolder的lockCanvas方法,锁定SurfaceView的Surface中的Canvas对象,用来在Surface上绘制内容,绘制完成后,会通过unlockCanvasAndPost方法,解锁Surface中的Canvas对象,并将绘制结果提交到Surface的缓冲区中。SurfaceFlinger会将SurfaceView的Surface和其他层合成到屏幕上,完成绘制流程。

    绘制过程的源码如下:

    class SurfaceViewUpdateThread extends Thread {
        // .............
        @Override
        public void run() {
            // .............
            // 循环绘制SurfaceView的内容
            while (mRunning) {
                // .............
                // 通过SurfaceHolder的lockCanvas方法,锁定SurfaceView的Surface中的Canvas对象,用来在Surface上绘制内容
                Canvas canvas = mSurfaceHolder.lockCanvas();
                if (canvas != null) {
                    // .............
                    // 在Canvas上绘制SurfaceView的内容,例如,使用Paint对象绘制颜色、文本、图形等
                    canvas.drawColor(Color.BLACK);
                    canvas.drawText("Hello, SurfaceView!", 100, 100, mPaint);
                    // .............
                    // 通过SurfaceHolder的unlockCanvasAndPost方法,解锁Surface中的Canvas对象,并将绘制结果提交到Surface的缓冲区中
                    mSurfaceHolder.unlockCanvasAndPost(canvas);
                }
                // .............
            }
        }
    }
    
    
    

    绘制过程主要做了以下两件事:

    • 循环绘制SurfaceView的内容,直到线程停止或者Surface被销毁。

    • 通过SurfaceHolder的lockCanvas方法,锁定SurfaceView的Surface中的Canvas对象,用来在Surface上绘制内容,lockCanvas方法会返回一个Canvas对象,用于在Surface上绘制内容。

    lockCanvas方法的源码如下:

    public Canvas lockCanvas() {
        return internalLockCanvas(null, false);
    }
    
    private final Canvas internalLockCanvas(Rect dirty, boolean hardware) {
        // .............
        // 调用Surface的lockCanvas方法,传入dirty参数,该方法会返回一个Canvas对象,用于在Surface上绘制内容
        Canvas c = mSurface.lockCanvas(dirty);
        // .............
        return c;
    }
    
    
    

    lockCanvas方法主要做了以下两件事:

    • 调用Surface的lockCanvas方法,传入dirty参数,该方法会返回一个Canvas对象,用于在Surface上绘制内容,该方法的源码与前面分析的Window的Surface的lockCanvas方法相同,不再赘述。

    • 在Canvas上绘制SurfaceView的内容,例如,使用Paint对象绘制颜色、文本、图形等,该过程与普通的View的绘制过程类似,不再赘述。

    通过SurfaceHolder的unlockCanvasAndPost方法,解锁Surface中的Canvas对象,并将绘制结果提交到Surface的缓冲区中,unlockCanvasAndPost方法会释放Surface中的Canvas对象,并将绘制结果提交到Surface的缓冲区中。

    unlockCanvasAndPost方法的源码如下:

    
    public void unlockCanvasAndPost(Canvas canvas) {
        // .............
        // 调用Surface的unlockCanvasAndPost方法,传入canvas参数,该方法会释放Surface中的Canvas对象,并将绘制结果提交到Surface的缓冲区中
        mSurface.unlockCanvasAndPost(canvas);
        // .............
    }
    
    

    unlockCanvasAndPost方法主要做了以下两件事:

    • 调用Surface的unlockCanvasAndPost方法,传入canvas参数,该方法会释放Surface中的Canvas对象,并将绘制结果提交到Surface的缓冲区中,该方法的源码与前面分析的Window的Surface的unlockCanvasAndPost方法相同,不再赘述。

    • SurfaceFlinger会将SurfaceView的Surface和其他层合成到屏幕上,完成绘制流程,该过程与前面分析的Window的Surface的合成过程类似,不再赘述。

    SurfaceView绘制原理示意图

    三、SurfaceView和View内的Canvas的区别

    View使用的Canvas SurfaceView使用的Canvas
    所属的Surface Window的Surface SurfaceView自己的Surface
    绘制线程 只能在UI线程中绘制 可以在独立的线程中绘制
    绘制时机 受到View树的测量、布局和绘制的影响,需要等待Window的刷新 不受View的绘制流程的控制,可以在任何时候进行绘制

    相关文章

      网友评论

          本文标题:surfaceview和view绘制的区别

          本文链接:https://www.haomeiwen.com/subject/eeqvndtx.html