# 1. 了解软件绘制与硬件加速绘制
绘制(或者叫渲染)就是把代码中的描述一块区域如何显示的代码/指令软换为一块可以用于显示设备显示的内存 Buffer 的过程。
View 的绘制共分为两种:硬件加速绘制和软件绘制:
- 硬件加速绘制是借助能高效处理图形计算的 GPU 来完成。
- 软件绘制使用 CPU 调用 libSkia 库来进行绘制
从 Android 4.0 开始,系统默认开启硬件加速。我们如果在 App 的 AndroidManifest 里面,在 Application 标签里面加上
android:hardwareAccelerated="false"
就可以关闭硬件绘制,使用软件绘制。
代码层面,硬件绘制的复杂度远超软件绘制,柿子挑软的捏,我们先学习软件绘制。
# 2. skia 库的基本使用
软件绘制会使用 skia 库绘制图形,在分析 View 的软件绘制流程之前,有必要先了解一下 skia 库的基本使用。
使用上,skia 库还是比较简单的:
- SKBitmap 用来存储图形数据
SkBitmap bitmap = new SkBitmap();
//设置位图格式及宽高
bitmap->setConfig(SkBitmap::kRGB_565_Config,800,480);
//分配位图所占空间
bitmap->allocPixels();
2
3
4
5
- SKCanvas 封装了所有画图操作的函数,通过调用这些函数,我们就能实现绘制操作。
//使用前传入bitmap
SkCanvas canvas(bitmap);
//移位,缩放,旋转,变形操作
translate(SkiaScalar dx, SkiaScalar dy);
scale(SkScalar sx, SkScalar sy);
rotate(SkScalar degrees);
skew(SkScalar sx, SkScalar sy);
//绘制操作
drawARGB(u8 a, u8 r, u8 g, u8 b....) //给定透明度以及红,绿,兰3色,填充整个可绘制区域。
drawColor(SkColor color...) //给定颜色color, 填充整个绘制区域。
drawPaint(SkPaint& paint) //用指定的画笔填充整个区域。
drawPoint(...)//根据各种不同参数绘制不同的点。
drawLine(x0, y0, x1, y1, paint) //画线,起点(x0, y0), 终点(x1, y1), 使用paint作为画笔。
drawRect(rect, paint) //画矩形,矩形大小由rect指定,画笔由paint指定。
drawRectCoords(left, top, right, bottom, paint),//给定4个边界画矩阵。
drawOval(SkRect& oval, SkPaint& paint) //画椭圆,椭圆大小由oval矩形指定。
//……其他操作
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- Skpaint 用来设置绘制内容的风格,样式,颜色等信息
//定义画笔
SkPaint paint1, paint2, paint3;
paint1.setAntiAlias(true);
paint1.setColor(SkColorSetRGB(255, 0, 0));
paint1.setStyle(SkPaint::kFill_Style);
paint2.setAntiAlias(true);
paint2.setColor(SkColorSetRGB(0, 136, 0));
paint2.setStyle(SkPaint::kStroke_Style);
paint2.setStrokeWidth(SkIntToScalar(3));
paint3.setAntiAlias(true);
paint3.setColor(SkColorSetRGB(136, 136, 136));
sk_sp<SkTextBlob> blob1 =
SkTextBlob::MakeFromString("Skia!", SkFont(nullptr, 64.0f, 1.0f, 0.0f));
sk_sp<SkTextBlob> blob2 =
SkTextBlob::MakeFromString("Skia!", SkFont(nullptr, 64.0f, 1.5f, 0.0f));
canvas->clear(SK_ColorWHITE);
canvas->drawTextBlob(blob1.get(), 20.0f, 64.0f, paint1);
canvas->drawTextBlob(blob1.get(), 20.0f, 144.0f, paint2);
canvas->drawTextBlob(blob2.get(), 20.0f, 224.0f, paint3);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 3. View 绘制的发起
View 绘制的发起是在 ViewRootImpl#draw
方法中:
// ViewRootImpl
public final Surface mSurface = new Surface();
private boolean draw(boolean fullRedrawNeeded, boolean forceDraw) {
Surface surface = mSurface;
// ......
// mDirty 的计算过程?
final Rect dirty = mDirty;
// .......
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
if (isHardwareEnabled()) { // 硬件绘制
// ......
// 硬件绘制
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
} else {
// ......
// 软件绘制
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)) {
return false;
}
}
}
// .......
return useAsyncReport;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
默认情况下 isHardwareEnabled()
方法返回 true,走硬件绘制路线,做了单独配置则会走软件绘制路线。
# 4. 软件绘制整体流程
软件绘制 drawSoftware
方法的实现如下:
// ViewRootImpl
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
// Draw with software renderer.
final Canvas canvas;
try {
// 关注点1
// 获取画布对象 Canvas
canvas = mSurface.lockCanvas(dirty);
// 密度
canvas.setDensity(mDensity);
} catch (Surface.OutOfResourcesException e) {
handleOutOfResourcesException(e);
return false;
} catch (IllegalArgumentException e) {
Log.e(mTag, "Could not lock surface", e);
// Don't assume this is due to out of memory, it could be
// something else, and if it is something else then we could
// kill stuff (or ourself) for no reason.
mLayoutRequested = true; // ask wm for a new surface next time.
return false;
}
try {
if (DEBUG_ORIENTATION || DEBUG_DRAW) {
Log.v(mTag, "Surface " + surface + " drawing to bitmap w="
+ canvas.getWidth() + ", h=" + canvas.getHeight() + ", dirty: " + dirty
+ ", xOff=" + xoff + ", yOff=" + yoff);
//canvas.drawARGB(255, 255, 0, 0);
}
// If this bitmap's format includes an alpha channel, we
// need to clear it before drawing so that the child will
// properly re-composite its drawing on a transparent
// background. This automatically respects the clip/dirty region
// or
// If we are applying an offset, we need to clear the area
// where the offset doesn't appear to avoid having garbage
// left in the blank areas.
if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
}
dirty.setEmpty();
mIsAnimating = false;
mView.mPrivateFlags |= View.PFLAG_DRAWN;
if (DEBUG_DRAW) {
Context cxt = mView.getContext();
Log.i(mTag, "Drawing: package:" + cxt.getPackageName() +
", metrics=" + cxt.getResources().getDisplayMetrics() +
", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo());
}
// 画布是否需要移动
canvas.translate(-xoff, -yoff);
if (mTranslator != null) {
mTranslator.translateCanvas(canvas);
}
canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
// 关注点2
// root view 绘制
mView.draw(canvas);
drawAccessibilityFocusedDrawableIfNeeded(canvas);
} finally {
try {
// 关注点3
// 提交绘制的内容到 Surface
surface.unlockCanvasAndPost(canvas);
} catch (IllegalArgumentException e) {
Log.e(mTag, "Could not unlock surface", e);
mLayoutRequested = true; // ask wm for a new surface next time.
//noinspection ReturnInsideFinallyBlock
return false;
}
if (LOCAL_LOGV) {
Log.v(mTag, "Surface " + surface + " unlockCanvasAndPost");
}
}
return true;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
- 关注点1,获取 Canvas 对象,Canvas 可以理解为一个画布,会和一块 Buffer 相关联
- 关注点2,View 树使用 Canvas 对象进行绘制
- 关注点3,提交绘制好的 Buffer 去显示
接下来,我们详细分析这三点。
# 5. Canvas 对象的初始化
Surface#lockCanvas
方法会准备一个画布对象返回,给后续的 draw 过程使用。其实现如下:
// /frameworks/base/core/java/android/view/Surface.java
// 指向 Native 层 BBQSurface 对象
long mNativeObject;
// 重要成员
private final Canvas mCanvas = new CompatibleCanvas();
public Canvas lockCanvas(Rect inOutDirty)
throws Surface.OutOfResourcesException, IllegalArgumentException {
synchronized (mLock) {
checkNotReleasedLocked();
if (mLockedObject != 0) {
// Ideally, nativeLockCanvas() would throw in this situation and prevent the
// double-lock, but that won't happen if mNativeObject was updated. We can't
// abandon the old mLockedObject because it might still be in use, so instead
// we just refuse to re-lock the Surface.
throw new IllegalArgumentException("Surface was already locked");
}
// 关注点
mLockedObject = nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);
return mCanvas;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
这里会接着调用 nativeLockCanvas
方法:
// /frameworks/base/core/java/android/view/Surface.java
private static native long nativeLockCanvas(long nativeObject, Canvas canvas, Rect dirty)
throws OutOfResourcesException;
2
3
- 第一个参数传入的是 Surface 的成员 mNativeObject,是一个 native 指针,指向 Native 层 BBQSurface 对象(这个在窗口的添加过程做分析,目前知道即可)。
- 第二个参数 Canvas 来自 Surface 的 mCanvas 成员,是用于绘制的画布
- 第三个参数 Rect dirty,表示需要绘制的区域。
nativeLockCanvas 对应的 JNI 实现:
// /frameworks/base/core/jni/android_view_Surface.cpp
static jlong nativeLockCanvas(JNIEnv* env, jclass clazz,
jlong nativeObject, jobject canvasObj, jobject dirtyRectObj) {
// 取到对应的 Native 层 Surface 对象,实际类型是 BBQSurface
sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject));
if (!isSurfaceValid(surface)) {
jniThrowException(env, IllegalArgumentException, NULL);
return 0;
}
if (!ACanvas_isSupportedPixelFormat(ANativeWindow_getFormat(surface.get()))) {
native_window_set_buffers_format(surface.get(), PIXEL_FORMAT_RGBA_8888);
}
// 用于保存 Java 层传递过来的 Dirty 区域
Rect dirtyRect(Rect::EMPTY_RECT);
Rect* dirtyRectPtr = NULL;
// 拿到 Java 层传递过来的 dirty 区域
if (dirtyRectObj) {
dirtyRect.left = env->GetIntField(dirtyRectObj, gRectClassInfo.left);
dirtyRect.top = env->GetIntField(dirtyRectObj, gRectClassInfo.top);
dirtyRect.right = env->GetIntField(dirtyRectObj, gRectClassInfo.right);
dirtyRect.bottom = env->GetIntField(dirtyRectObj, gRectClassInfo.bottom);
dirtyRectPtr = &dirtyRect;
}
ANativeWindow_Buffer buffer;
// 关注点1,获取 buffer
status_t err = surface->lock(&buffer, dirtyRectPtr);
if (err < 0) {
const char* const exception = (err == NO_MEMORY) ?
OutOfResourcesException : IllegalArgumentException;
jniThrowException(env, exception, NULL);
return 0;
}
// 关注点2
// 利用 java 层 Canvas 对象构建一个新的 `graphics::Canvas` 对象
graphics::Canvas canvas(env, canvasObj);
// 关注点3
// buffer 与 graphics::Canvas 关联
canvas.setBuffer(&buffer, static_cast<int32_t>(surface->getBuffersDataSpace()));
if (dirtyRectPtr) {
canvas.clipRect({dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom});
}
if (dirtyRectObj) {
env->SetIntField(dirtyRectObj, gRectClassInfo.left, dirtyRect.left);
env->SetIntField(dirtyRectObj, gRectClassInfo.top, dirtyRect.top);
env->SetIntField(dirtyRectObj, gRectClassInfo.right, dirtyRect.right);
env->SetIntField(dirtyRectObj, gRectClassInfo.bottom, dirtyRect.bottom);
}
// Create another reference to the surface and return it. This reference
// should be passed to nativeUnlockCanvasAndPost in place of mNativeObject,
// because the latter could be replaced while the surface is locked.
// 返回 Surface
sp<Surface> lockedSurface(surface);
lockedSurface->incStrong(&sRefBaseOwner);
return (jlong) lockedSurface.get();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
- 关注点1,调用
surface::lock
申请一块图形 buffer,lock 的过程在 BBQ 章节已经分析过 native 的代码了,这里不是重点。 - 关注点2,利用 java 层 Canvas 对象构建一个新的
graphics::Canvas
对象 - 关注点3,把 buffer 与
graphics::Canvas
对象关联起来
类相互关系比较复杂,这里先给一个整体的类图。
接下来对照类图,分析本节的三个关注点:
- 5.1 申请图形 Buffer
- 5.2 构建
graphics::Canvas
对象 - 5.3 把 buffer 与
graphics::Canvas
对象关联起来
# 5.1 申请图形 Buffer
surface::lock
会向 BBQ 申请 buffer,结果保存在一个 ANativeWindow_Buffer 对象中:
typedef struct ANativeWindow_Buffer {
/// The number of pixels that are shown horizontally.
int32_t width;
/// The number of pixels that are shown vertically.
int32_t height;
/// The number of *pixels* that a line in the buffer takes in
/// memory. This may be >= width.
int32_t stride;
/// The format of the buffer. One of AHardwareBuffer_Format.
int32_t format;
/// The actual bits.
void* bits;
/// Do not touch.
uint32_t reserved[6];
} ANativeWindow_Buffer;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
每个成员都有比较清晰的注释。
Surface::lock 的实现如下:
sp<GraphicBuffer> mLockedBuffer;
sp<GraphicBuffer> mPostedBuffer;
// /frameworks/native/libs/gui/Surface.cpp
status_t Surface::lock(
ANativeWindow_Buffer* outBuffer, ARect* inOutDirtyBounds)
{
// ......
ANativeWindowBuffer* out;
int fenceFd = -1;
// 关注点1,向 bbq 申请内存,结果保存在 out 中
status_t err = dequeueBuffer(&out, &fenceFd);
ALOGE_IF(err, "dequeueBuffer failed (%s)", strerror(-err));
if (err == NO_ERROR) {
// 当前帧
sp<GraphicBuffer> backBuffer(GraphicBuffer::getSelf(out));
const Rect bounds(backBuffer->width, backBuffer->height);
Region newDirtyRegion;
if (inOutDirtyBounds) {
newDirtyRegion.set(static_cast<Rect const&>(*inOutDirtyBounds));
newDirtyRegion.andSelf(bounds);
} else {
newDirtyRegion.set(bounds);
}
// 前一帧
// frontBuffer 就是前面一帧图像对应的 Buffer
const sp<GraphicBuffer>& frontBuffer(mPostedBuffer);
const bool canCopyBack = (frontBuffer != nullptr &&
backBuffer->width == frontBuffer->width &&
backBuffer->height == frontBuffer->height &&
backBuffer->format == frontBuffer->format);
if (canCopyBack) {
// 关注点2
// 一帧大概率只更新部分区域,不会更新整个窗口
// 这里把不需要更新的区域的数据拷贝到新申请的 buffer 中去
const Region copyback(mDirtyRegion.subtract(newDirtyRegion));
if (!copyback.isEmpty()) {
copyBlt(backBuffer, frontBuffer, copyback, &fenceFd);
}
} else {
// if we can't copy-back anything, modify the user's dirty
// region to make sure they redraw the whole buffer
newDirtyRegion.set(bounds);
mDirtyRegion.clear();
Mutex::Autolock lock(mMutex);
for (size_t i=0 ; i<NUM_BUFFER_SLOTS ; i++) {
mSlots[i].dirtyRegion.clear();
}
}
{ // scope for the lock
Mutex::Autolock lock(mMutex);
int backBufferSlot(getSlotFromBufferLocked(backBuffer.get()));
if (backBufferSlot >= 0) {
Region& dirtyRegion(mSlots[backBufferSlot].dirtyRegion);
mDirtyRegion.subtract(dirtyRegion);
dirtyRegion = newDirtyRegion;
}
}
mDirtyRegion.orSelf(newDirtyRegion);
if (inOutDirtyBounds) {
*inOutDirtyBounds = newDirtyRegion.getBounds();
}
// mmap 映射到当前进程内存空间
void* vaddr;
status_t res = backBuffer->lockAsync(
GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
newDirtyRegion.bounds(), &vaddr, fenceFd);
ALOGW_IF(res, "failed locking buffer (handle = %p)",
backBuffer->handle);
if (res != 0) {
err = INVALID_OPERATION;
} else {
// 关注点3
mLockedBuffer = backBuffer;
outBuffer->width = backBuffer->width;
outBuffer->height = backBuffer->height;
outBuffer->stride = backBuffer->stride;
outBuffer->format = backBuffer->format;
outBuffer->bits = vaddr;
}
}
return err;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
- 关注点1,向 BBQ 申请一块 buffer,结果保存在
ANativeWindowBuffer* out
中 - 关注点2,新的一帧大概率只更新部分区域,不会更新整个窗口,这里把不需要更新的区域的数据拷贝到新申请的 buffer 中去
- 关注点3,新的 buffer,会保存到 Surface 的
sp<GraphicBuffer> mLockedBuffer
成员中
# 5.2 构建 graphics::Canvas
对象
graphics::Canvas 的构造函数实现如下:
// /frameworks/base/libs/hwui/apex/include/android/graphics/canvas.h
ACanvas* mCanvas;
Canvas(JNIEnv* env, jobject canvasObj) :
mCanvas(ACanvas_getNativeHandleFromJava(env, canvasObj)),
mOwnedPtr(false) {}
2
3
4
5
6
ACanvas_getNativeHandleFromJava 函数构建一个 ACanvas 对象给到 ACanvas* mCanvas;
成员。
// /frameworks/base/libs/hwui/apex/android_canvas.cpp
ACanvas* ACanvas_getNativeHandleFromJava(JNIEnv* env, jobject canvasObj) {
return TypeCast::toACanvas(GraphicsJNI::getNativeCanvas(env, canvasObj));
}
// /frameworks/base/libs/hwui/apex/TypeCast.h
// 指针强转为 ACanvas * 类型
static inline ACanvas* toACanvas(Canvas* canvas) {
return reinterpret_cast<ACanvas *>(canvas);
}
2
3
4
5
6
7
8
9
10
getNativeCanvas 获取到 Java 层传递过来的 android::Canvas*
指针返回
// /frameworks/base/libs/hwui/jni/Graphics.cpp
gCanvas_nativeInstanceID = GetFieldIDOrDie(env, gCanvas_class, "mNativeCanvasWrapper", "J");
android::Canvas* GraphicsJNI::getNativeCanvas(JNIEnv* env, jobject canvas) {
ALOG_ASSERT(env);
ALOG_ASSERT(canvas);
ALOG_ASSERT(env->IsInstanceOf(canvas, gCanvas_class));
jlong canvasHandle = env->GetLongField(canvas, gCanvas_nativeInstanceID);
if (!canvasHandle) {
return NULL;
}
return reinterpret_cast<android::Canvas*>(canvasHandle);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
这里会去获取到 Java 层 Canvas 对象的 mNativeCanvasWrapper 成员,该成员的值是一个 android::Canvas
指针。
那 mNativeCanvasWrapper 是什么时候被赋值的呢?
// /frameworks/base/core/java/android/view/Surface.java
public class Surface implements Parcelable {
// ......
private final Canvas mCanvas = new CompatibleCanvas();
// ......
}
2
3
4
5
6
Surface 对应有一个成员 Canvas mCanvas
,在定义时被初始化为子类 CompatibleCanvas。
private final class CompatibleCanvas extends Canvas {
// A temp matrix to remember what an application obtained via {@link getMatrix}
private Matrix mOrigMatrix = null;
@Override
public void setMatrix(Matrix matrix) {
if (mCompatibleMatrix == null || mOrigMatrix == null || mOrigMatrix.equals(matrix)) {
// don't scale the matrix if it's not compatibility mode, or
// the matrix was obtained from getMatrix.
super.setMatrix(matrix);
} else {
Matrix m = new Matrix(mCompatibleMatrix);
m.preConcat(matrix);
super.setMatrix(m);
}
}
@SuppressWarnings("deprecation")
@Override
public void getMatrix(Matrix m) {
super.getMatrix(m);
if (mOrigMatrix == null) {
mOrigMatrix = new Matrix();
}
mOrigMatrix.set(m);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
这里会调用到 Canvas 的构造函数:
// /frameworks/base/graphics/java/android/graphics/Canvas.java
public Canvas() {
if (!isHardwareAccelerated()) {
// 0 means no native bitmap
mNativeCanvasWrapper = nInitRaster(0);
mFinalizer = NoImagePreloadHolder.sRegistry.registerNativeAllocation(
this, mNativeCanvasWrapper);
} else {
mFinalizer = null;
}
}
2
3
4
5
6
7
8
9
10
11
mNativeCanvasWrapper 在这里被赋值为 nInitRaster 方法的返回值。
nInitRaster 是一个 native 方法,对应的 JNI 实现如下:
// /frameworks/base/libs/hwui/jni/android_graphics_Canvas.cpp
static jlong initRaster(JNIEnv* env, jobject, jlong bitmapHandle) {
SkBitmap bitmap;
if (bitmapHandle != 0) {
bitmap::toBitmap(bitmapHandle).getSkBitmap(&bitmap);
}
return reinterpret_cast<jlong>(Canvas::create_canvas(bitmap));
}
// /frameworks/base/libs/hwui/SkiaCanvas.cpp
Canvas* Canvas::create_canvas(const SkBitmap& bitmap) {
return new SkiaCanvas(bitmap);
}
2
3
4
5
6
7
8
9
10
11
12
13
可以看到 mNativeCanvasWrapper 实际是一个 SkiaCanvas 类型的指针。
SkiaCanvas 的构造函数实现如下:
// /frameworks/base/libs/hwui/SkiaCanvas.cpp
std::unique_ptr<SkCanvas> mCanvasOwned;
SkCanvas* mCanvas;
SkiaCanvas::SkiaCanvas(const SkBitmap& bitmap) {
mCanvasOwned = std::unique_ptr<SkCanvas>(new SkCanvas(bitmap));
mCanvas = mCanvasOwned.get();
}
2
3
4
5
6
7
8
SkiaCanvas 中有一个重要成员 SkCanvas* mCanvas
,这个就是 skia 库最终绘制的画布。
类的关系有点繁琐了,我们小结一下:
- 每一个窗口对应一个 ViewRootImpl 对象
- 每一个 ViewRootImpl 对象持有一个
Surface mSurface
成员 - Surface 有两个成员
long mNativeObject
和long mLockedObject;
,都指向 native 层的 BBQSurface 对象,BBQSurface 是 Surface 的子类 - Surfce 有一个
Canvas mCanvas
成员,Canvas 是 BaseCanvas 的子类,BaseCanvas 有一个long mNativeCanvasWrapper
成员,指向 native 层的 SkiaCanvas 对象,SkiaCanvas 是android::Canvas
的子类,有一个SkCanvas* mCanvas;
成员,该成员就是 skia 库中的画布,最终的绘制操作都要通过它实现 - Native 层会构建一个
android::graphics::Canvas
对象,同时获取到 Java 层 Canvas 中long mNativeCanvasWrapper
成员指向的 SkiaCanvas 对象,将其强转为一个 ACanvas 指针,并赋值给android::graphics::Canvas
的ACanvas* mCanvas;
成员。为什么要绕个圈,不让android::graphics::Canvas
直接持有android::Canvas
指针?应该是考虑到代码的解耦。当前使用的 SkiaCanvas,未来可能更新为 xxxCanvas。这是一个 CPP 常见的编程技巧。
# 5.3 Buffer 与 graphics::Canvas
对象关联
接着调用 setBuffer 函数,将 buffer 与 graphics::Canvas
对象关联起来。
// /frameworks/base/libs/hwui/apex/include/android/graphics/canvas.h
bool setBuffer(const ANativeWindow_Buffer* buffer,
int32_t /*android_dataspace_t*/ dataspace) {
return ACanvas_setBuffer(mCanvas, buffer, dataspace);
}
// /frameworks/base/libs/hwui/apex/android_canvas.cpp
bool ACanvas_setBuffer(ACanvas* canvas, const ANativeWindow_Buffer* buffer,
int32_t /*android_dataspace_t*/ dataspace) {
SkBitmap bitmap;
//使用 buffer 和 dataspace 构造 SkBitmap
bool isValidBuffer = (buffer == nullptr) ? false : convert(buffer, dataspace, &bitmap);
// bitmap 设置给 canvas
TypeCast::toCanvas(canvas)->setBitmap(bitmap);
return isValidBuffer;
}
// /frameworks/base/libs/hwui/apex/TypeCast.h
static inline Canvas* toCanvas(ACanvas* canvas) {
return reinterpret_cast<Canvas*>(canvas);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
这里调用 convert,使用 buffer 和 dataspace 来构建一个 SkBitmap 对象。其中 dataspace 是 Surface 的成员,是一个 enum 类型数据,看注释像是用于指定 buffer 的格式。
接着把 SkBitmap 设置给 SkiaCanvas 对象。
convert 的实现如下:
// /frameworks/base/libs/hwui/apex/android_canvas.cpp
static bool convert(const ANativeWindow_Buffer* buffer,
int32_t /*android_dataspace_t*/ dataspace,
SkBitmap* outBitmap) {
if (buffer == nullptr) {
return false;
}
// 构建 SkImageInfo
sk_sp<SkColorSpace> cs(uirenderer::DataSpaceToColorSpace((android_dataspace)dataspace));
SkImageInfo imageInfo = uirenderer::ANativeWindowToImageInfo(*buffer, cs);
size_t rowBytes = buffer->stride * imageInfo.bytesPerPixel();
// 构建 SkSurface
sk_sp<SkSurface> surface = SkSurface::MakeRasterDirect(imageInfo, buffer->bits, rowBytes);
// 相关信息配置给 SkBitmap
if (surface.get() != nullptr) {
if (outBitmap) {
outBitmap->setInfo(imageInfo, rowBytes);
outBitmap->setPixels(buffer->bits);
}
return true;
}
return false;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
整个过程如下图所示:
接着使用 SkBitmap 对象初始化一个 SkCanvas,并赋值给 SkiaCanvas 的 mCanvas 成员。
// /frameworks/base/libs/hwui/SkiaCanvas.cpp
void SkiaCanvas::setBitmap(const SkBitmap& bitmap) {
// deletes the previously owned canvas (if any)
mCanvasOwned.reset(new SkCanvas(bitmap));
mCanvas = mCanvasOwned.get();
// clean up the old save stack
mSaveStack.reset(nullptr);
}
2
3
4
5
6
7
8
9
SkCanvas 的构造函数实现如下:
// /external/skia/src/core/SkCanvas.cpp
SkCanvas::SkCanvas(const SkBitmap& bitmap) : SkCanvas(bitmap, nullptr, nullptr, nullptr) {}
SkCanvas::SkCanvas(const SkBitmap& bitmap,
std::unique_ptr<SkRasterHandleAllocator> alloc,
SkRasterHandleAllocator::Handle hndl,
const SkSurfaceProps* props)
: fMCStack(sizeof(MCRec), fMCRecStorage, sizeof(fMCRecStorage))
, fProps(SkSurfacePropsCopyOrDefault(props))
, fAllocator(std::move(alloc)) {
inc_canvas();
// 构建一个 SkBitmapDevice 对象
sk_sp<SkBaseDevice> device(new SkBitmapDevice(bitmap, fProps, hndl));
// 将 SkBitmapDevice 对象配置给 SkCanvas
this->init(device);
}
void SkCanvas::init(sk_sp<SkBaseDevice> device) {
// SkCanvas.h declares internal storage for the hidden struct MCRec, and this
// assert ensure it's sufficient. <= is used because the struct has pointer fields, so the
// declared size is an upper bound across architectures. When the size is smaller, more stack
static_assert(sizeof(MCRec) <= kMCRecSize);
if (!device) {
device = sk_make_sp<SkNoPixelsDevice>(SkIRect::MakeEmpty(), fProps);
}
// From this point on, SkCanvas will always have a device
SkASSERT(device);
fSaveCount = 1;
fMCRec = new (fMCStack.push_back()) MCRec(device.get());
// The root device and the canvas should always have the same pixel geometry
SkASSERT(fProps.pixelGeometry() == device->surfaceProps().pixelGeometry());
fSurfaceBase = nullptr;
fBaseDevice = std::move(device);
fScratchGlyphRunBuilder = std::make_unique<sktext::GlyphRunBuilder>();
fQuickRejectBounds = this->computeDeviceClipBounds();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// /external/skia/src/core/SkBitmapDevice.cpp
SkBitmapDevice::SkBitmapDevice(const SkBitmap& bitmap, const SkSurfaceProps& surfaceProps,
SkRasterHandleAllocator::Handle hndl)
: INHERITED(bitmap.info(), surfaceProps)
, fBitmap(bitmap)
, fRasterHandle(hndl)
, fRCStack(bitmap.width(), bitmap.height())
, fGlyphPainter(this->surfaceProps(), bitmap.colorType(), bitmap.colorSpace()) {
SkASSERT(valid_for_bitmap_device(bitmap.info(), nullptr));
}
2
3
4
5
6
7
8
9
10
整体的结构如下图所示:
# 6. View 的 draw 过程分析
后续就是遍历整个 View 树,每个 View 调用自己的 onDraw 方法,完成绘制。
在 draw 方法中,通常使用 Java 层 Canvas 类的相关 API 进行绘制。
举个例子:
class TestView(con:Context) : View(con) {
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// 调用 Canvas 的 api 绘制
Paint mPaint1 = new Paint();
Paint mPaint2 = new Paint();
mPaint1.setStrokeWidth(0);//设置画笔宽度0 ,单位px 默认一个像素
mPaint1.setColor(Color.BLUE);
mPaint1.setStyle(Paint.Style.STROKE);
// .......
// 画一个矩形(蓝色)
canvas.drawRect(100, 100, 150, 150, mPaint1);
// 将画布的原点移动到(400,500)
canvas.translate(400,500);
// 再画一个矩形(红色)
canvas.drawRect(100, 100, 150, 150, mPaint2);
//......
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Paint 的初始化:
// /frameworks/base/graphics/java/android/graphics/Paint.java
private long mNativePaint;
public Paint() {
this(ANTI_ALIAS_FLAG);
}
public Paint(int flags) {
mNativePaint = nInit();
NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativePaint);
setFlags(flags | HIDDEN_DEFAULT_PAINT_FLAGS);
// TODO: Turning off hinting has undesirable side effects, we need to
// revisit hinting once we add support for subpixel positioning
// setHinting(DisplayMetrics.DENSITY_DEVICE >= DisplayMetrics.DENSITY_TV
// ? HINTING_OFF : HINTING_ON);
mCompatScaling = mInvCompatScaling = 1;
setTextLocales(LocaleList.getAdjustedDefault());
mColor = Color.pack(Color.BLACK);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
对应的 JNI 实现:
// /frameworks/base/libs/hwui/jni/Paint.cpp
static jlong init(JNIEnv* env, jobject) {
return reinterpret_cast<jlong>(new Paint);
}
2
3
4
// /frameworks/base/libs/hwui/hwui/PaintImpl.cpp
Paint::Paint()
: SkPaint()
, mLetterSpacing(0)
, mWordSpacing(0)
, mFontFeatureSettings()
, mMinikinLocaleListId(0)
, mFamilyVariant(minikin::FamilyVariant::DEFAULT) {
// SkPaint::antialiasing defaults to false, but
// SkFont::edging defaults to kAntiAlias. To keep them
// insync, we manually set the font to kAilas.
mFont.setEdging(SkFont::Edging::kAlias);
}
2
3
4
5
6
7
8
9
10
11
12
13
Paint 是 SkPaint 的子类。
以 setColor 为例看 Paint 的配置
// /frameworks/base/graphics/java/android/graphics/Paint.java
public void setColor(@ColorLong long color) {
ColorSpace cs = Color.colorSpace(color);
nSetColor(mNativePaint, cs.getNativeInstance(), color);
mColor = color;
}
private static native void nSetColor(long paintPtr, long colorSpaceHandle,
@ColorLong long color);
2
3
4
5
6
7
8
9
10
对应的 JNI 实现:
static void setColor(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jint color) {
reinterpret_cast<Paint*>(paintHandle)->setColor(color);
}
2
3
最终调用到 native 层 Paint 的 setColor。
Canvas 绘制以 drawRect 为例进行分析:
实际调用 Canvas 的 API 去 draw:
// /frameworks/base/graphics/java/android/graphics/Canvas.java
public void drawRect(float left, float top, float right, float bottom, @NonNull Paint paint) {
super.drawRect(left, top, right, bottom, paint);
}
2
3
4
// /frameworks/base/graphics/java/android/graphics/BaseCanvas.java
public void drawRect(float left, float top, float right, float bottom, @NonNull Paint paint) {
throwIfHasHwFeaturesInSwMode(paint);
nDrawRect(mNativeCanvasWrapper, left, top, right, bottom, paint.getNativeInstance());
}
// /frameworks/base/graphics/java/android/graphics/BaseCanvas.java
private static native void nDrawRect(long nativeCanvas, float left, float top, float right,
float bottom, long nativePaint);
2
3
4
5
6
7
8
9
// /frameworks/base/libs/hwui/jni/android_graphics_Canvas.cpp
static void drawRect(JNIEnv* env, jobject, jlong canvasHandle, jfloat left, jfloat top,
jfloat right, jfloat bottom, jlong paintHandle) {
const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
get_canvas(canvasHandle)->drawRect(left, top, right, bottom, *paint);
}
static Canvas* get_canvas(jlong canvasHandle) {
return reinterpret_cast<Canvas*>(canvasHandle);
}
2
3
4
5
6
7
8
9
10
// /frameworks/base/libs/hwui/SkiaCanvas.cpp
void SkiaCanvas::drawRect(float left, float top, float right, float bottom, const Paint& paint) {
if (CC_UNLIKELY(paint.nothingToDraw())) return;
// 扔到事务队列中去
applyLooper(&paint, [&](const SkPaint& p) {
// 调用到 skCanvas
// 最终都会通过 SKBitmap 写入到 buffer 中去
mCanvas->drawRect({left, top, right, bottom}, p);
});
}
2
3
4
5
6
7
8
9
10
实际就是调用到 skia 库中的 API。
# 7. Canvas 的提交过程分析
完成绘制,就可以将 buffer 提交给 BBQ:
// /frameworks/base/core/java/android/view/Surface.java
public void unlockCanvasAndPost(Canvas canvas) {
synchronized (mLock) {
checkNotReleasedLocked();
if (mHwuiContext != null) { // 硬件绘制走这里
mHwuiContext.unlockAndPost(canvas);
} else { // 软件绘制走这里
unlockSwCanvasAndPost(canvas);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
// /frameworks/base/core/java/android/view/Surface.java
private void unlockSwCanvasAndPost(Canvas canvas) {
if (canvas != mCanvas) {
throw new IllegalArgumentException("canvas object must be the same instance that "
+ "was previously returned by lockCanvas");
}
if (mNativeObject != mLockedObject) {
Log.w(TAG, "WARNING: Surface's mNativeObject (0x" +
Long.toHexString(mNativeObject) + ") != mLockedObject (0x" +
Long.toHexString(mLockedObject) +")");
}
if (mLockedObject == 0) {
throw new IllegalStateException("Surface was not locked");
}
try {
nativeUnlockCanvasAndPost(mLockedObject, canvas);
} finally {
nativeRelease(mLockedObject);
mLockedObject = 0;
}
}
private static native void nativeUnlockCanvasAndPost(long nativeObject, Canvas canvas);
// 完成一些回收操作
private static native void nativeRelease(long nativeObject);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
对应的 JNI 实现:
// /frameworks/base/core/jni/android_view_Surface.cpp
static void nativeUnlockCanvasAndPost(JNIEnv* env, jclass clazz,
jlong nativeObject, jobject canvasObj) {
sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject));
if (!isSurfaceValid(surface)) {
return;
}
// detach the canvas from the surface
graphics::Canvas canvas(env, canvasObj);
// 解除 canvas 与 buffer 的关联
canvas.setBuffer(nullptr, ADATASPACE_UNKNOWN);
// 关注点
// unlock surface
status_t err = surface->unlockAndPost();
if (err < 0) {
jniThrowException(env, IllegalArgumentException, NULL);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
status_t Surface::unlockAndPost()
{
if (mLockedBuffer == nullptr) {
ALOGE("Surface::unlockAndPost failed, no locked buffer");
return INVALID_OPERATION;
}
int fd = -1;
status_t err = mLockedBuffer->unlockAsync(&fd);
ALOGE_IF(err, "failed unlocking buffer (%p)", mLockedBuffer->handle);
// 提交 buffer 到 BBQ
err = F(mLockedBuffer.get(), fd);
ALOGE_IF(err, "queueBuffer (handle=%p) failed (%s)",
mLockedBuffer->handle, strerror(-err));
// 当前帧变为上一帧
mPostedBuffer = mLockedBuffer;
mLockedBuffer = nullptr;
return err;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21