一、普通View生成图片的原理

我们先来分析下从普通View中获取图片的方法。代码如下:

public Bitmap getBitmapFromView(View view){

if (view == null) {

return null;

}

view.setDrawingCacheEnabled(true);

Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache());

view.setDrawingCacheEnabled(false);

view.destroyDrawingCache();

return bitmap;

}

上面是从普通view获取图像的方法,核心API是view.getDrawingCache(),跟踪源码可知最终调用到View.java的buildDrawingCacheImpl()方法。我们来研究下这个方法的实现。

frameworks\base\core\java\android\view\View.java

private void buildDrawingCacheImpl() {

Bitmap bitmap = Bitmap.createBitmap(mResources.getDisplayMetrics(), width, height, quality);

Canvas canvas = new Canvas(bitmap);

final int restoreCount = canvas.save();

if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {

mPrivateFlags &= ~PFLAG_DIRTY_MASK;

dispatchDraw(canvas);

} else {

draw(canvas);

}

canvas.restoreToCount(restoreCount);

}

上面是我精简后的方法,可以很清晰的看到普通View生成图像的原理就是,生成一个新的Bitmap,把这个新的Bitmap设置给一个Canvas,然后再调用源View的Draw方法,将图像原型绘制到新Bitmap上。简单说,就是通过Canvas把源View的图像原型绘制到新Bitmap中,这样再将新Bitmap保存起来就得到了View的图像。

在Android中绘制一个二维图像需要四个基本组件:

1、a Bitmap:保存图像像素数据(to hold the pixels)

2、a Canvas:包含一系列绘制和图像变换的方法(to host the draw calls,writing into the bitmap)

3、a drawing primitive:图像原型 (e.g. Rect, Path, text, Bitmap)

4、a paint:画笔描述绘制颜色、风格 (to describe the colors and styles for the drawing)

一句话描述:canvas 用画笔把图像原型绘制到bitmap上。

二、同理为啥不能从SurfaceView中获取图片呢?

从上分析中可以知道获取普通View的图形就是调用View的Draw方法在新的Bitmap上再绘制一次。那为啥同样的逻辑在SurfaceView上无效呢?让我们来看下SurfaceView的Draw方法的实现。

frameworks\base\core\java\android\view\SurfaceView.java

@Override

public void draw(Canvas canvas) {

if (mWindowType != WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {

// draw() is not called when SKIP_DRAW is set

if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) {

// punch a whole in the view-hierarchy below us

canvas.drawColor(0, PorterDuff.Mode.CLEAR);

}

}

super.draw(canvas);

}

SurfaceView的Draw方法及其简单,就上面这几行代码。关键代码就这行canvas.drawColor(0, PorterDuff.Mode.CLEAR);源码中注释已经解释了这行代码的作用,就是在View层打一个洞露出View层下面的东西。从下面备注可以看到使用PorterDuff.Mode.CLEAR模式drawColor就是绘制全透明。

PorterDuff.Mode 我的理解就是两张图片重叠的部分图像合成模式。下面是PorterDuff.Mode的部分源码。

Sa:全称为Source alpha,表示源图的Alpha通道;

Sc:全称为Source color,表示源图的颜色;

Da:全称为Destination alpha,表示目标图的Alpha通道;

Dc:全称为Destination color,表示目标图的颜色.

代码注释就是重叠部分图像合成的计算公式。

frameworks\base\graphics\java\android\graphics\PorterDuff.java

public enum Mode {

/** [0, 0] */

CLEAR (0),

/** [Sa, Sc] */

SRC (1),

/** [Da, Dc] */

DST (2),

/** [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] */

SRC_OVER (3),

...

}

Draw方法最终调用了super.draw(canvas),实际调用View的onDraw方法来绘制View的内容,但是我们看SurfaceView的源码发现它没有实现onDraw方法。也就是说在普通View递归绘制过程中,SurfaceView在View层只绘制了一个透明窗口。

看到这里就明白了为啥从SurfaceView中获取不到图像缓存了。普通View获取图像换成的原理是调用View的Draw方法在新的Bitmap上绘制一次View的内容,但是SurfaceView比较特别,它的展示内容绘制不是通过draw流程绘制的,所以我们通过这种方式获取不到图像缓存。

如果是这样,那又会有一个疑问了,SurfaceView上展示的图像内容到底是怎么绘制的呢,和普通View的图像绘制有什么区别呢?

三、Android上图像渲染流程

在View和SurfaceView上绘制文字

上面代码以绘制文字为例,展示了在普通View和SurfaceView上绘制图像的代码实现。它们的共同点是都是用canvas来绘制图像。不同的地方是普通View是从复写的onDraw(Canvas canvas)方法中获取到canvas的,而SurfaceView是从surface中获取canvas来绘制的。

3.1 普通View的绘制

想要弄清楚View是怎么绘制的得先弄明白View是怎么创建出来的。我们先来看下View的创建流程。

Android界面创建过程

Android应用开发都都知道,在Android应用中创建一个交互界面使用的四大组件之一的Activity,在Activity的onResume生命周期方法执行后界面就展示出来了。如上图所示界面创建流程大致分三个步骤:

步骤一:创建Activity,这个过程会创建一个PhoneWindow实例;

步骤二:在Activity的onCreate生命周期中setContentView设置应用开发者定义的布局View。布局设置的过程是委派给PhoneWindow来完成的。PhoneWindow先创建界面根布局,其中包括了一些系统信息展示的区域,然后把应用开发者传进来的应用界面放置到应用信息展示区域。整个界面布局形成一棵布局树ViewTree。

步骤三:在Activity的onResume生命周期中将ViewTree添加到WMS中,WMS通过ViewRootImpl来触发ViewTree的递归测量、布局和绘制的流程。这个过程完成后界面就展示出来了。

从上面流程图可以看出界面绘制是从ViewRootImpl中开始触发的。来看下精简后的performTraversals方法。

frameworks\base\core\java\android\view\ViewRootImpl.java

private void performTraversals() {

...

performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

...

performLayout(lp, mWidth, mHeight);

...

performDraw();

...

}

就是我们熟知的measure - layout - draw流程。今天我们主要关心View的绘制,我们来看下Draw的流程,主要看下在View的Draw方法中传递进来Canvas对象是怎么产生的。

frameworks\base\core\java\android\view\ViewRootImpl.java

final Surface mSurface = new Surface();

private void performDraw() {

...

mIsDrawing = true;

Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");

try {

draw(fullRedrawNeeded);

} finally {

mIsDrawing = false;

Trace.traceEnd(Trace.TRACE_TAG_VIEW);

}

...

}

private void draw(boolean fullRedrawNeeded) {

Surface surface = mSurface;

if (!surface.isValid()) {

return;

}

if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {

return;

}

...

}

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,

boolean scalingRequired, Rect dirty) {

...

// Draw with software renderer.

final Canvas canvas;

try {

canvas = mSurface.lockCanvas(dirty);

...

// 这里就调用到View里了,平时复写View的onDraw(Canvas canvas)方法绘制图像时用到的canvas就是这里传递下去的。

mView.draw(canvas);

...

} finally {

try {

surface.unlockCanvasAndPost(canvas);

} catch (IllegalArgumentException e) {

Log.e(mTag, "Could not unlock surface", e);

mLayoutRequested = true;

return false;

}

}

return true;

}

从上述源码可以看到ViewRootImpl有一个Surface属性,当界面绘制时,就调用mSurface.lockCanvas方法获取一个Canvas对象传递个View递归绘制。ViewRootImpl简易类图如下。

ViewRootImpl类图

Canvas: 封装了一系列绘制的方法;

Surface: 图像数据保存区。

通过下面的Surface的源码可以看到mSurface.lockCanvas实际就是Canvas设置了一个Bitmap。而后的View递归绘制就是在Surface创建的Bitmap上绘制。

frameworks\base\core\java\android\view\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;

}

}

frameworks\base\core\jni\android_view_Surface.cpp

static jlong nativeLockCanvas(JNIEnv* env, jclass clazz, jlong nativeObject, jobject canvasObj, jobject dirtyRectObj) {

sp surface(reinterpret_cast(nativeObject));

ANativeWindow_Buffer outBuffer;

status_t err = surface->lock(&outBuffer, dirtyRectPtr);

SkImageInfo info = SkImageInfo::Make(outBuffer.width, outBuffer.height,

convertPixelFormat(outBuffer.format),

outBuffer.format == PIXEL_FORMAT_RGBX_8888

? kOpaque_SkAlphaType : kPremul_SkAlphaType,

GraphicsJNI::defaultColorSpace());

SkBitmap bitmap;

ssize_t bpr = outBuffer.stride * bytesPerPixel(outBuffer.format);

bitmap.setInfo(info, bpr);

if (outBuffer.width > 0 && outBuffer.height > 0) {

bitmap.setPixels(outBuffer.bits);

} else {

// be safe with an empty bitmap.

bitmap.setPixels(NULL);

}

Canvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvasObj);

// 给Canvas设置Bitmap

nativeCanvas->setBitmap(bitmap);

sp lockedSurface(surface);

lockedSurface->incStrong(&sRefBaseOwner);

return (jlong) lockedSurface.get();

}

到这里普通View的绘制就算是跑通了。一个PhoneWindow实例就对应一个界面,以它通过树形结构组织Views,把根View设置到ViewRootImpl实例中,ViewRootImpl实例和根部局实例是一一对应的,ViewRootImpl接收系统消息来后通过根部局触发递归绘制。我们的界面像素数据保存在Surface中,这个Surface就是在ViewRootImpl中创建的。

view绘制

从上面图可以看出虽然各个view都有自己的onDraw方法,但是他们使用的canvas是同一个对象,实际上他们是在同一个surface上的不同区域绘制图像数据。

3.1 SurfaceView的绘制

我们再来详细看下在SurfaceView上绘制文字的过程。在SurfaceView这个绘制场景中我们屡一下前面讲到图像绘制的四要素,图像原型就是我们需要绘制的文字、画笔就是绘制是创建的paint实例、绘制方法就是canvas对象的drawText方法、像素承载容器就是surface。

SurfaceView类图

从上图可以看出在SurfaceView绘制过程中有两个surface。一个是继承自普通View绘制流程从ViewRootImpl传递出来的mSurface1,另一个是SurfaceView自己的属性mSurface2。在View数递归绘制过程中,SurfaceView只在mSurface1上绘制了一个透明区域,没有绘制任何实质的内容。真正SurfaceView展示的内容是直接操作mSurface2来绘制的。也就是说SurfaceView显示内容更新不需要走View树递归绘制的过程,直接操作自己私有的mSurface2即可,这也是为什么我们可以通过非UI线程来更新SurfaceView显示内容的原因。

SurfaceView绘制

到这里我们SurfaceView的绘制流程也清楚了。到这里文章标题的疑问就比较好回答了。从普通view中获取图像的方法view.getDrawingCache()实质是调用View树绘制的方法在新的bitmap上再绘制一次图像原型。但是SurfaceView的展示图像却不是在View树绘制流程中绘制的。

四、如何解决这个问题

5.1 SurfaceView内容是开发者绘制的

既然绘制工作是自己做的,那么获取图片时可以模仿view.getDrawingCache()方法实现一个SurfaceView的getDrawingCache()方法即可。

5.2 SurfaceView显示内容是其他模块绘制的

常见的我们将surface设置到MediaPlayer、MediaCodec模块中,显示内容由这些模块来绘制的,那么绘制方法我们就是未知的也就实现不了类getDrawingCache()的功能。这种情况下我们可以换用TextureView来实现。

android surfaceview 图片,为啥从SurfaceView中获取不到图片?相关推荐

  1. android xml获取指定,android:如何从xml文件中获取信息?

    我得到一个程序,从一个链接的服务器获取天气.我已经做了一些将字符串结合到URL的字符串.我现在需要从XML文件中获取信息.android:如何从xml文件中获取信息? 这是我的代码:(我更换了,为了安 ...

  2. Android程序如何实现从网络中获取一张图片

    在学习黎活明的Android视频的时候,有一讲是说怎么从网络中获取图片.这里,我就做一个笔记和总结吧. 首先,我们要知道图片这种文件,不能以我们印象中那些基本数据类型来定义或是以这些类型从网络中获取图 ...

  3. 1 图片channels_深度学习中各种图像库的图片读取方式

    深度学习中各种图像库的图片读取方式总结 在数据预处理过程中,经常需要写python代码搭建深度学习模型,不同的深度学习框架会有不同的读取数据方式(eg:Caffe的python接口默认BGR格式,Te ...

  4. python爬虫怎么下载图片到手机_python爬虫获取京东手机图片的图文教程

    如题,首先当然是要打开京东的手机页面 因为要获取不同页面的所有手机图片,所以我们要跳转到不同页面观察页面地址的规律,这里观察第二页页面 由观察可以得到,第二页的链接地址很有可能是 https://li ...

  5. react前端显示图片_在react中怎么动态渲染图片?

    在react中怎么动态渲染图片?下面本篇文章给大家介绍一下.有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助. React 动态渲染图片,提升用户体验 市场上竞争是非常残酷的,众所周知, ...

  6. java 图片合并成pdf_Java中PDF的转换(图片)与展示

    解决的问题 有些时候我们需要在项目中展示PDF,但是直接在浏览器中加入PDF展示的插件,存在兼容性问题,某些浏览器显示效果不理想,所以我们可以将PDF转为图片,然后已图片的方式展示,效果很好. 那么怎 ...

  7. html怎样把图片放进画布中,HTML5 canvas操作图片

    1.canvas操作图像的能力 canvas更有意思的一项特性就是图像操作能力.可以用于动态的图像合成或者作为图形的背景,以及游戏界面(Sprites)等等.浏览器支持的任意格式的外部图片都可以使用, ...

  8. axure中怎么把图片变圆_orcad中怎么创建带图片的Title Block?

    一般来说,Title Block都是调用系统本身自带的,或者是修改自带的文件,所以这里我们直接复制一个系统自带的Title Block,修改后保存在路径下,进行关联即可. 第一步,从系统自带的模板Ca ...

  9. html背景透明图片不透明,css中背景透明的图片不透明怎么解决

    css中背景透明的图片不透明怎么解决 一.使用滤镜解决img { background: transparent; -ms-filter: "progid:DXImageTransform. ...

最新文章

  1. Chemical Science | 基于金属的片段分子库用于筛选候选药物
  2. 参加第十届的队员给智能车竞赛的建议:抄能力+钞能力使得一届不如一届
  3. Jenkins插件之环境变量插件EnvInject
  4. ajax提交数据服务端返回报错
  5. 湖南大学第十四届ACM程序设计新生杯(重现赛)- FFind the AFei Numbers(数位dp)
  6. 2023年多播ABR市场将达8亿美元
  7. asp.net 2中的图片上传
  8. python如何操作oracle数据库_python操作oracle数据库
  9. TextView属性的静态使用与动态使用
  10. 学习VIM之2014
  11. samba for linux下载,Samba 4.4.3 SMB for Linux 发布下载
  12. mysql字段命名_Mysql 01—数据库表字段的命名规则
  13. mysql 查询正在运行的事务并且杀掉该事务
  14. 中国移动Cmpp java实现_CMPP-java 中国移动CMPP协议java开发包 - 下载 - 搜珍网
  15. Nginx实现静态资源服务器
  16. 蓝牙耳机买什么品牌好一些?2022蓝牙耳机品牌排行榜10强
  17. 滁州市区地图小区楼盘图矢量高清cdr|pdf2021年(高品质)
  18. 局域网资产发现过程(利用工具nmap、masscan)
  19. 银行卡资费转帐汇款取款_收费标准参考_中行_建行_工行_农行_招行_兴业等
  20. SNP位点上下游序列查找1.0 2020-9-27

热门文章

  1. 详解浏览器解析一个URL的全过程
  2. 【Matlab】矩阵中选取任意子矩阵
  3. [云炬创业学笔记]第一章创业是什么测试4
  4. 燃烧学往年精选真题解析2018-01-01
  5. 十四、“沥沥雨丝如落泪,麻麻密密总为愁。”(2021.5.2)
  6. linux 软硬文件类型,linux文件属性和类型、系统链接文件、软链接和硬链接
  7. 减小Delphi的Exe文件大小
  8. SCOPE_IDENTITY 和 @@IDENTITY 的区别
  9. Sql获取表信息(包括结构及字段说明)
  10. 【CyberSecurityLearning 35】基础环境搭建