surfaceview和view绘制的区别

一、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()方法为入口:

Java 复制代码
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);
        // ..........
    }
    // ..........
}

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()方法为入口:

Java 复制代码
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和View的绘制原理

1.View的绘制原理

View的绘制是基于Window的Surface来实现的,Window的Surface是一个原生的缓冲区,用来保存当前窗口的像素数据,它是由屏幕显示内容合成器(Screen Compositor)所管理的。屏幕显示内容合成器是一个系统级的服务,它负责将所有Window的Surface合成到屏幕上,形成最终的显示效果。屏幕显示内容合成器的具体实现可能有所不同,例如SurfaceFlinger、HWComposer等,但它们的基本原理都是类似的。 View的绘制过程中,会通过Window的lockCanvas方法,锁定Window的Surface中的Canvas对象,用来在Surface上绘制View的内容,绘制完成后,会通过unlockCanvasAndPost方法,解锁Surface中的Canvas对象,并将绘制结果提交到Surface的缓冲区中。Surface会将缓冲区中的内容交给屏幕显示内容合成器,由它来将Surface的内容合成到屏幕上,完成绘制流程。

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

Java 复制代码
    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上绘制内容。该方法的源码如下:

Java 复制代码
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的焦点状态。该方法的源码如下:

java 复制代码
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方法的源码如下:

java 复制代码
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对象的内容显示到屏幕上。

2.SurfaceView的绘制原理

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

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

init()方法的源码如下:

Java 复制代码
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和其他层合成到屏幕上,完成绘制流程。

绘制过程的源码如下:

Java 复制代码
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方法的源码如下:

Java 复制代码
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方法的源码如下:

Java 复制代码
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和View内的Canvas的区别

View使用的Canvas SurfaceView使用的Canvas
所属的Surface Window的Surface SurfaceView自己的Surface
绘制线程 只能在UI线程中绘制 可以在独立的线程中绘制
绘制时机 受到View树的测量、布局和绘制的影响,需要等待Window的刷新 不受View的绘制流程的控制,可以在任何时候进行绘制
相关推荐
vocal44 分钟前
【我的安卓第一课】Android 运行时权限
android·app
yjm4 天前
从一例 Lottie OOM 线上事故读源码
android·app
Keya4 天前
在HarmonyOS(鸿蒙)中H5页面中的视频不会自动播放
app·harmonyos·arkts
iOS阿玮4 天前
哪些产品符合免备案的骚操?看看你的产品符合吗?
uni-app·app·apple
随笔记5 天前
uniapp开发的小程序输入框在ios自动填充密码,如何欺骗苹果手机不让自动填充
前端·ios·app
iOS阿玮7 天前
AppStore教你一招免备案的骚操作!
uni-app·app·apple
菌菇汤9 天前
uni-app实现单选,多选也能搜索,勾选,选择,回显
前端·javascript·vue.js·微信小程序·uni-app·app