一、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);
// ..........
}
// ..........
}
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和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阶段的源码如下:
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
方法主要做了以下几件事:
同步锁定mLock对象,用于保证线程安全。
检查Surface是否已经被释放,如果是,抛出异常。
检查Surface是否已经被锁定,如果是,抛出异常。
调用nativeLockCanvas方法,传入mNativeObject、mCanvas和inOutDirty参数,该方法会返回一个mLockedObject对象,用于标识Surface的锁定状态。
返回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()
方法主要做了以下几件事:
- 清除View的脏标志,设置View的绘制标志,表示View已经被绘制过。
- 如果View不是完全不透明的,绘制View的背景,该方法会调用Drawable的draw方法,传入Canvas对象,用于在Surface上绘制Drawable的内容。
- 根据View的边缘渐变标志,判断是否需要绘制边缘渐变效果,如果不需要,直接调用onDraw方法,传入Canvas对象,用于在Surface上绘制View的内容,该方法是一个空方法,一般由子类重写,实现具体的绘制逻辑,例如TextView会绘制文本,ImageView会绘制图片等。如果需要,调用drawWithFadingEdges方法,传入Canvas对象,用于在Surface上绘制带有边缘渐变效果的View的内容,该方法会先保存Canvas的状态,然后根据边缘渐变的方向,裁剪Canvas的区域,然后调用onDraw方法,传入Canvas对象,用于在Surface上绘制View的内容,最后恢复Canvas的状态。
- 调用dispatchDraw方法,传入Canvas对象,用于在Surface上绘制View的子View的内容,该方法会遍历View的子View列表,根据它们的可见性、透明度、动画等属性,决定是否需要绘制,如果需要,就调用子View的draw方法,传入Canvas对象,用于在Surface上绘制子View的内容,该方法是一个递归的过程,直到所有的子View都被绘制完毕。
- 如果View有Overlay,且Overlay不为空,调用Overlay的getOverlayView方法,获取Overlay的View对象,然后调用Overlay的View对象的dispatchDraw方法,传入Canvas对象,用于在Surface上绘制Overlay的内容,该方法与View的dispatchDraw方法类似,也是一个递归的过程,直到所有的Overlay的子View都被绘制完毕。
- 调用onDrawForeground方法,传入Canvas对象,用于在Surface上绘制View的前景,该方法会绘制View的滚动条、前景Drawable等内容。
- 如果开启了调试模式,调用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方法主要做了以下几件事:
同步锁定mLock对象,用于保证线程安全。
检查Surface是否已经被释放,如果是,抛出异常。
检查Surface是否已经被锁定,如果否,抛出异常。
调用nativeUnlockCanvasAndPost方法,传入mNativeObject和canvas参数,该方法会将绘制结果提交到Surface的缓冲区中,并通知SurfaceFlinger进行合成。
调用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()
方法的源码如下:
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和View内的Canvas的区别
View使用的Canvas | SurfaceView使用的Canvas | |
---|---|---|
所属的Surface | Window的Surface | SurfaceView自己的Surface |
绘制线程 | 只能在UI线程中绘制 | 可以在独立的线程中绘制 |
绘制时机 | 受到View树的测量、布局和绘制的影响,需要等待Window的刷新 | 不受View的绘制流程的控制,可以在任何时候进行绘制 |