前言
Hardware Bitmap(硬件位图)
是Android8.0加入的新功能,通过设置Bitmap的config为Bitmap.Config.HARDWARE
,创建所谓的Hardware Bitmap,它不同与其他Config
的Bitmap,Hardware Bitmap对应的像素数据是存储在显存中,并对图片仅在屏幕上绘制的场景做了优化;
硬件位图的介绍参考Glide文档
何如使用Hardware Bitmap
创建Hardware Bitmap
众所周知,Bitmap的创建一般是调用BitmapFactory这个工厂类来实现,由于Hardware Bitmap需要配置Bitmap.Config.HARDWARE
属性,一个基本的获取用Hardware Bitmap的写法如下:1
2
3val options = BitmapFactory.Options()
options.inPreferredConfig = Bitmap.Config.HARDWARE
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.dog, options)
主要是需要设置BitmapFactory.Options
的inPreferredConfig
为Bitmap.Config.HARDWARE
;
针对HARDWARE情况BitmapFactory的提示
如果设置了inPreferredConfig = Bitmap.Config.HARDWARE
,千万不要设置options.inMutable = true
,这样会引起报错,因为Hardware Bitmap
是不可变的,也不能被利用;另外inBitmap
属性也没有必要设置,因为硬件位图不需要当前进程的缓存复用,如果设置inBitmap
可能会替换掉之前设置的inPreferredConfig
属性;
使用Hardware Bitmap
通过上一步的创建,我们获得Bitmap对象,首先我们可以通过bitmap.getConfig()
获取到当前Bitmap是不是Hardware,其次,大多数情况下,我们是把Bitmap
设置给ImageView
控件;1
imageView.setImageBitmap(bitmap)
一行代码搞定imageView没错,这行代码一般情况下是没有问题的,那么问题在哪里?
首先,硬件位图只支持GPU的绘制,言外之意是这个ImageView必须在开启硬件加速的Activity中,而且当前这个ImageView不能设置软件层 (software layer type);
开启硬件加速的代码
1
2
3
4
5
6
7//application级别开启硬件加速
<application android:hardwareAccelerated="true">
<activity ..../>
</>
//activity级别开启硬件加速
<activity android:hardwareAccelerated="true"/>在View 上使用software layer type
1
2
3ImageView imageView = …
imageView.setImageBitmap(hardwareBitmap);
imageView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
如果我们满足硬件加速和不设置software layer type这两个条件,在正真使用中还有坑,其中最大的也最频繁发生的就是通过Canvas来改变Bitmap的形状或者其他的转换;
拿圆形图片做例子
假设我们需要显示圆形图片,一般解决方案有两种:通过自定义控件处理和通过Glide等工具类直接剪裁Bitmap;当Bitmap剪裁遇到HARDWARE
就是问题的开始;
- 通过自定义控件比如
CircleImageView
的方案,onDraw()
方法如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18@Override
protected void onDraw(Canvas canvas) {
if (mDisableCircularTransformation) {
super.onDraw(canvas);
return;
}
if (mBitmap == null) {
return;
}
if (mCircleBackgroundColor != Color.TRANSPARENT) {
canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius, mCircleBackgroundPaint);
}
canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius, mBitmapPaint);
if (mBorderWidth > 0) {
canvas.drawCircle(mBorderRect.centerX(), mBorderRect.centerY(), mBorderRadius, mBorderPaint);
}
}
这是CircleImageView
重新onDraw()
方法,通过自定义控件实现剪切圆角,在设置硬件位图Bitmap时,一般都没有问题;
- 通过类似
Glide
等直接处理Bitmap
的方式剪裁圆形图片,基本代码如下:1
2
3
4
5
6Canvas canvas = new Canvas(resultBitmap);
// Draw a circle
canvas.drawCircle(radius, radius, radius, CIRCLE_CROP_SHAPE_PAINT);
// Draw the bitmap in the circle
canvas.drawBitmap(inBitmap, null, destRect, CIRCLE_CROP_BITMAP_PAINT);
clear(canvas);
通过类似工具类的形式直接对Bitmap
进行修改,执行到canvas.drawBitmap
就会报异常,异常信息是java.lang.IllegalStateException: Software rendering doesn't support hardware bitmaps
;
如何避免报异常
我大致想了这么两个方案:
- 方案一:所有关于剪切Bitmap的操作都改成自定义控件,在自定义控件的
onDraw
中实现; - 方案二:寻找一种方案,解决掉自己创建的
Canvas
不报异常,这样就能继续用工具类来处理Bitmap
;
方案一技术实现比较简单,把项目中所用用到处理Bitmap的逻辑都换成自定义控件,但是可能涉及到很多处代码的修改,是一个功夫活;
方案二实施起来有点障碍,因为除了通过new Canvas(Bitmap)获取画布,还能通过什么方式能拿到Canvas,对了还有SurfaceView也是可以拿到Canvas,但是SurfaceView不支持硬件加速,所以直接就Pass了,想实现方案二我认为得弄清自定义控件onDraw()
方法中Canvas
从何而来;
分析Canvas流程
View.onDraw()中的Canvas从何而来
我们知道,View
的绘制流程是从ViewRootImpl.performTraversals()
这个方法开始
performTraversals()1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
if (!cancelDraw && !newSurface) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
//调动performDraw()
performDraw();
} else {
if (isViewVisible) {
// Try again
scheduleTraversals();
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).endChangingAnimations();
}
mPendingTransitions.clear();
}
}
performTraversals()
方法调用performDraw()
,然后performDraw()
方法中又调用draw(fullRedrawNeeded)
,大部门绘制的逻辑都是在draw(fullRedrawNeeded)
方法中;
draw(fullRedrawNeeded)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
//省略代码
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
} else {
if (mAttachInfo.mThreadedRenderer != null &&
!mAttachInfo.mThreadedRenderer.isEnabled() &&
mAttachInfo.mThreadedRenderer.isRequested()) {
//省略代码
//drawSoftware
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
}
从draw(fullRedrawNeeded)
方法可以看到,如果支持硬件加速,调用mAttachInfo.mThreadedRenderer.draw()
方法,否则调用drawSoftware()
方法,绘制的基本流程从这里分叉;
drawSoftware如何获得Canvas
drawSoftware()1
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
27private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
// Draw with software renderer.
final Canvas canvas;
try {
final int left = dirty.left;
final int top = dirty.top;
final int right = dirty.right;
final int bottom = dirty.bottom;
canvas = mSurface.lockCanvas(dirty);
Surface.lockCanvas()
//noinspection ConstantConditions
if (left != dirty.left || top != dirty.top || right != dirty.right
|| bottom != dirty.bottom) {
attachInfo.mIgnoreDirtyState = true;
}
canvas.setDensity(mDensity);
} catch (Surface.OutOfResourcesException e) {
handleOutOfResourcesException(e);
return false;
} catch (IllegalArgumentException e) {
mLayoutRequested = true;
return false;
}
//省略代码
}
从drawSoftware()
方法可以知道,软件绘制的流程是从Surface.lockCanvas()
获得Canvas
对象;
View体系硬件加速Canvas创建过程
ThreadedRenderer.draw()1
2
3
4
5
6
7void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
attachInfo.mIgnoreDirtyState = true;
final Choreographer choreographer = attachInfo.mViewRootImpl.mChoreographer;
choreographer.mFrameInfo.markDrawStart();
//调用updateRootDisplayList更新DisplayList
updateRootDisplayList(view, callbacks);
}
ThreadedRenderer.updateRootDisplayList()1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Record View#draw()");
updateViewTreeDisplayList(view);
if (mRootNodeNeedsUpdate || !mRootNode.isValid()) {
//通过RootNode.start创建DisplayListCanvas
DisplayListCanvas canvas = mRootNode.start(mSurfaceWidth, mSurfaceHeight);
try {
final int saveCount = canvas.save();
canvas.translate(mInsetLeft, mInsetTop);
callbacks.onPreDraw(canvas);
canvas.insertReorderBarrier();
canvas.drawRenderNode(view.updateDisplayListIfDirty());
canvas.insertInorderBarrier();
callbacks.onPostDraw(canvas);
canvas.restoreToCount(saveCount);
mRootNodeNeedsUpdate = false;
} finally {
//最终调用end方法
mRootNode.end(canvas);
}
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
View.updateDisplayListIfDirty()1
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
45public RenderNode updateDisplayListIfDirty() {
final RenderNode renderNode = mRenderNode;
//省略代码
int layerType = getLayerType();
//创建DisplayListCanvas
final DisplayListCanvas canvas = renderNode.start(width, height);
canvas.setHighContrastText(mAttachInfo.mHighContrastText);
try {
//判断layerType
if (layerType == LAYER_TYPE_SOFTWARE) {
buildDrawingCache(true);
Bitmap cache = getDrawingCache(true);
if (cache != null) {
canvas.drawBitmap(cache, 0, 0, mLayerPaint);
}
} else {
computeScroll();
canvas.translate(-mScrollX, -mScrollY);
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
dispatchDraw(canvas);//dispatchDraw
drawAutofilledHighlight(canvas);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().draw(canvas);
}
if (debugDraw()) {
debugDrawFocus(canvas);
}
} else {
//调用draw()方法
draw(canvas);
}
}
} finally {
renderNode.end(canvas);
setDisplayListProperties(renderNode);
}
} else {
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
}
return renderNode;
}
从上面基本流程可以看出,硬件加速下Canvas的创建是调用RenderNode.create()
方法,每个View
都有自己的RenderNode
,RenderNode
的创建是在View的构造方法中;
View构造方法1
2
3
4
5
6
7
8public View(Context context) {
mContext = context;
mResources = context != null ? context.getResources() : null;
mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED | FOCUSABLE_AUTO;
//省略
mRenderNode = RenderNode.create(getClass().getName(), this);
//省略
}
RenderNode
通过调用静态方法create
得到RenderNode
对象,我们继续看RenderNode.create()
方法
RenderNode.create()1
2
3
4
5
6
7/**
* @param name The name of the RenderNode, used for debugging purpose. May be null.
* @return A new RenderNode.
*/
public static RenderNode create(String name, @Nullable View owningView) {
return new RenderNode(name, owningView);
}
create()
方法有两个参数,第一个name,第二个是owningView,而且是可以为空的,从注释上来看,name只是为了调试用,而且owningView可以为空,我们可以用反射去创建一个简单的RenderNode
;
尝试创建一个Canvas
回顾一下,写出一个简单的创建一个硬件加速Canvas的代码:1
2
3
4
5
6
7
8第一行,创建RenderNode
RenderNode node = RenderNode.create("helloworld", null);
第二行,创建DisplayListCanvas
final DisplayListCanvas canvas = node.start(bitmapWidth, bitmapHeight);
第三行,执行canvas的操作
canvas.xxx();
第四行,执行node.end()方法
node.end(canvas);
一个简单的DisplayListCanvas
创建流程在脑海中浮现出来,但是还有个问题,我们执行完canvas
的绘制操作之后,生成的产物Bitmap
从哪里得到,我们回顾和ViewRootImpl
打交道的硬件加速绘制相关的类是ThreadedRenderer
,我们刚才看了这个类的draw()
方法和updateRootDisplayList()
方法,很有意思,它还有一个这个静态的方法createHardwareBitmap(RenderNode node, int width, int height)
;
ThreadedRenderer.createHardwareBitmap()1
2
3public static Bitmap createHardwareBitmap(RenderNode node, int width, int height) {
return nCreateHardwareBitmap(node.getNativeDisplayList(), width, height);
}
该方法根据传入的RenderNode
创建一个硬件加速的Bitmap并返回,要求传入的这个node
必须是根root,在这里,一个完整的获取替换Canvas的流程应该是这样;
1 | 第一行,创建RenderNode |
基于上面的伪代码分析,我写了一个避免反射调优化版的Hardware Canvas
,基本调用如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17//创建HardwareCanvasManager
val hardwareCanvasManager = HardwareCanvasManager()
try {
//获取canvas
val canvas = hardwareCanvasManager.createCanvas(size, size)
//画圆形or其他绘制
canvas.drawCircle(radius, radius, radius, CIRCLE_CROP_SHAPE_PAINT);
//画原图,通过画笔设置SRC_IN属性
canvas.drawBitmap(inBitmap, null, destRect, CIRCLE_CROP_BITMAP_PAINT);
//得到bitmap
val buildBitmap = hardwareCanvasManager.buildBitmap()
//将bitmap设置给ImageView
iv.setImageBitmap(buildBitmap)
} finally {
//清理工作
hardwareCanvasManager.clean()
}
总结
这篇水文主要是分析View绘制下Canvas的创建流程,关于硬件加速的更详细的介绍,推荐大家看这篇文章https://www.jianshu.com/p/40f660e17a73。