android 获取当前画布,Android硬件位图填坑之获取硬件画布

前言

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的写法如下:

val 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控件;

imageView.setImageBitmap(bitmap)

复制代码

一行代码搞定imageView没错,这行代码一般情况下是没有问题的,那么问题在哪里?

首先,硬件位图只支持GPU的绘制,言外之意是这个ImageView必须在开启硬件加速的Activity中,而且当前这个ImageView不能设置软件层 (software layer type);

开启硬件加速的代码

//application级别开启硬件加速

>

//activity级别开启硬件加速

复制代码在View 上使用software layer type

ImageView imageView = …

imageView.setImageBitmap(hardwareBitmap);

imageView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

复制代码

如果我们满足硬件加速和不设置software layer type这两个条件,在正真使用中还有坑,其中最大的也最频繁发生的就是通过Canvas来改变Bitmap的形状或者其他的转换;

拿圆形图片做例子

假设我们需要显示圆形图片,一般解决方案有两种:通过自定义控件处理和通过Glide等工具类直接剪裁Bitmap;当Bitmap剪裁遇到HARDWARE就是问题的开始;

通过自定义控件比如CircleImageView的方案,onDraw()方法如下:

@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的方式剪裁圆形图片,基本代码如下:

Canvas 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()

boolean 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)

if (!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()

private 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()

void 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()

private 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()

public 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构造方法

public 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()

/**

* @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的代码:

第一行,创建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()

public static Bitmap createHardwareBitmap(RenderNode node, int width, int height) {

return nCreateHardwareBitmap(node.getNativeDisplayList(), width, height);

}

复制代码

该方法根据传入的RenderNode创建一个硬件加速的Bitmap并返回,要求传入的这个node必须是根root,在这里,一个完整的获取替换Canvas的流程应该是这样;

第一行,创建RenderNode

RenderNode node = RenderNode.create("helloworld", null);

第二行,创建DisplayListCanvas

final DisplayListCanvas canvas = node.start(width, height);

第三行,执行canvas的操作

canvas.xxx();

第四行,执行node.end()方法

node.end(canvas);

第五行,调用createHardwareBitmap生成Bitmap

bitmap = ThreadedRenderer.createHardwareBitmap(node,width,height)

复制代码

基于上面的伪代码分析,我写了一个避免反射调优化版的Hardware Canvas,基本调用如下:

//创建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()

}

复制代码

Github传送门

总结

这篇水文主要是分析View绘制下Canvas的创建流程,关于硬件加速的更详细的介绍,推荐大家看这篇文章www.jianshu.com/p/40f660e17…。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/300869.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JSP数据库连接方式总结

现在有好多初学jsp的网友经常会问数据库怎么连接啊&#xff0c;怎么老出错啊&#xff1f;所以我集中的在这写篇文章供大家参考&#xff0c;其实这种把数据库逻辑全部放在jsp里未必是好的做法&#xff0c;但是有利于初学者学习&#xff0c;所以我就这样做了&#xff0c;当大家学…

Java中Filter的理解

Filter工作原理当客户端发出Web资源的请求时&#xff0c;Web服务器根据应用程序配置文件设置的过滤规则进行检查&#xff0c;若客户请求满足过滤规则&#xff0c;则对客户请求&#xff0f;响应进行拦截&#xff0c;对请求头和请求数据进行检查或改动&#xff0c;并依次通过过滤…

一句话征服了美国人,这位饱受争议的数学博士竟从未上过学?

全世界只有3.14 % 的人关注了青少年数学之旅前两天&#xff0c;有位不愿意透露姓名的模友问了超模君一个问题&#xff1a;虽然这个问题超模君已经解答过无数遍了&#xff0c;但看到模友如此虔诚的态度&#xff0c;超模君决定今天再给模友们讲一个犹太小伙用数学征服美国军官的故…

使用 C# 开发浏览器扩展

使用 C# Blazor 开发浏览器扩展Intro前段时间听了 Justin 大佬分享的 Blazor 开发浏览器扩展&#xff0c;觉得很不错&#xff0c;C# 可以做更多有趣的事情了&#xff0c;很多需要在服务器端做的事情可能就可以在客户端里实现了&#xff0c;而且高度可以复用已有的 C# 代码&…

一个设置ip的vbs脚本

经常在两个网段间转换 常改ip&#xff0c;找了一个改ip的脚本稍微改了一下&#xff0c;让他适合我的情况&#xff08;自动判断我的ip&#xff09;strComputer "."SetobjWMIService GetObject("winmgmts:\\"&strComputer &"\root\cimv2")…

心动的本质是什么_《心动的信号3》:在“烟火气”里嗑糖,素人恋爱究竟有多上头?...

文 | 土豆2018年&#xff0c;一档画风清新&#xff0c;以素人恋爱为主体、辅之以明星观察为核心的恋爱社交真人秀节目&#xff0c;走红于市场。彼时国内综艺市场&#xff0c;尚且还处于竞技类真人秀、偶像综艺的爆发期——《心动的信号》播出以后&#xff0c;不仅成功开启了国内…

android 如何动态设置margin,Android 动态设置margin

android的view中有setPadding&#xff0c;但是没有直接的setMargin方法。如果要在代码中设置该怎么做呢&#xff1f;可以通过设置view里面的LayoutParams设置&#xff0c;而这个LayoutParams是根据该view在不同的GroupView而不同的。布局文件如下:xmlns:tools"http://sche…

公司服务器iSCSI网络硬盘连接故障

今天检查备份服务器工作状态&#xff0c;发现iSCSI硬盘连接出现了故障。然后刷新了一下系统状态&#xff0c;发现过了几秒钟后又重新连接上了。心里很疑惑&#xff0c;于是开始检查服务器日志。发现在9月13日凌晨4点20分左右有两个来源为l2nd的消息。稍前的一个信息告知网络控制…

老是担心数学学不好?是因为你的数学老师不是爱因斯坦!

各位模友&#xff0c;大家好我是小木相信上学的时候&#xff0c;数学对于很多人来说&#xff0c;无疑是个坑&#xff01;好不容易毕业了&#xff0c;好奇又好学的小表妹每次都能完美地引起小木的心酸历程。就在小木一边回忆起自己的心酸历程的同时&#xff0c;不禁感叹&#xf…

创业95%失败不是因项目本身

95%的人想过个人创业&#xff1b;95%的人一直只是停留在想象的阶段&#xff1b;95%的人创业失败&#xff1b;95%的失败不是因为项目本身的问题。 多年以前&#xff0c;就曾有句著名的口号&#xff1a;“十亿人民九亿商&#xff0c;还有一亿要开张。”中国人的个人创业意识普及…

oracle 产看执行计划_ODBA 技能SPM计划

OBA技能1-获取执行计划OBA技能2-执行计划顺序OBA技能&#xff13;-执行计划顺序表连接ODBA 技能&#xff14;实战执行计划ODBA 技能5 固定执行计划因为每次统计信息作业在收集完信息后&#xff0c;会触发ACS自适应游标管理程序&#xff0c;进行对绑定变量的窥探工作&#xff0c…

android动画设置的单位,Kotlin语言入门—实现单位转换,view设置,动画等

dp转换为px在android开发中&#xff0c;dp sp px之间的转换是不可避免的&#xff0c;在使用java语言开发时&#xff0c;往往会做个工具类进项转化。这样的工具类在网上很多&#xff0c;这里就不在展示了。如果使用Kotlin语言开发&#xff0c;则可以通过通过Extension来优雅的解…

微软面向初学者的机器学习课程:1.1-机器学习介绍

写在前面&#xff1a;最近在参与microsoft/ML-For-Beginners的翻译活动&#xff0c;欢迎有兴趣的朋友加入&#xff08;https://github.com/microsoft/ML-For-Beginners/issues/71&#xff09;机器学习介绍![机器学习&#xff0c;人工智能&#xff0c;深度学习-有什么区别?](ht…

margin-top绑架父节点问题的分析

转载至&#xff1a;http://www.benben.cc/blog/?p98 现象&#xff1a; 当两个空的块级元素嵌套时&#xff0c;如果内部的块设置有margin-top属性&#xff0c;而且父元素没有下边解决方法所述的特征&#xff0c;那么内部块的margin-top属性会绑架父元素&#xff08;即将margin-…

遭央视曝光的“AI算命”,背后竟然隐藏了一个价值千亿的市场!?

全世界只有3.14 % 的人关注了青少年数学之旅还记得儿时算命先生曾对我说&#xff1a;等你25岁那年&#xff0c;会黄袍加身&#xff0c;每天与大鱼大肉为伍。如今眼看着25逐步逼近&#xff0c;数据汪看到美团的外卖小哥都有种莫名的“亲切感”。爆红的“AI算命”言归正传&#x…

OnIntialDialog() (Dialog应用)和 OnIntialUpdate(View应用)的 程序起始点

Dialog为基的应用框架的程序的起始点在OnIntialDialog()FormView为基的应用框架的程序起始点在OnIntialUpdate() 把 需要起始就装入的代码放在 CformView&#xff1a;OnIntialUpdate();GetParentFrame()->RecalcLayout(); Resize ParenttoFit(); 三个语句之后&#xff08;上…

微软面向初学者的机器学习课程:1.2-机器学习的历史

写在前面&#xff1a;最近在参与microsoft/ML-For-Beginners的翻译活动&#xff0c;欢迎有兴趣的朋友加入&#xff08;https://github.com/microsoft/ML-For-Beginners/issues/71&#xff09;机器学习的历史作者Tomomi Imura[1]课前测验[2]在本课中&#xff0c;我们将走过机器学…

android布局中画圆角矩形,Android 自定义View之圆角矩形轨迹图

一、原理说明主要是通过计算轨迹的坐标点加入到集合中&#xff0c;然后对集合进行相应截取&#xff0c;传入canvas中。二、具体代码实现/*** 原理是先通过尺寸把各个轨迹的坐标计算出来&#xff0c;然后再截取相应坐标&#xff0c;进行重绘。** author lz* Time 2019-3-27*/pub…

类QQ右下角弹出框(Qt)

2019独角兽企业重金招聘Python工程师标准>>> 使用Qt写的类QQ右下角弹出框 /***main.cpp */ #include <QtGui/QApplication> #include "dialog.h"int main(int argc, char *argv[]) {QApplication a(argc, argv);Dialog w;w.show();return a.exec()…

实现贝叶斯分类器_机器学习实战项目-朴素贝叶斯

朴素贝叶斯 概述贝叶斯分类是一类分类算法的总称&#xff0c;这类算法均以贝叶斯定理为基础&#xff0c;故统称为贝叶斯分类。本章首先介绍贝叶斯分类算法的基础——贝叶斯定理。最后&#xff0c;我们通过实例来讨论贝叶斯分类的中最简单的一种: 朴素贝叶斯分类。贝叶斯理论 &a…