Android 4.4 Graphic系统详解(3) 一个view的绘制之旅

本章从Graphic的角度来分析Android系统中一个基础的view是如何被绘制出来的(只讨论硬件加速打开的场景):
下面我们以TextView这个类的onDraw函数为例看下,这个类是很多view的父类。

[cpp]  view plain copy
  1. protected void onDraw(Canvas canvas) {  
  2.         // Draw the background for this view  
  3.         super.onDraw(canvas);  
  4.   
  5.         final int compoundPaddingLeft = getCompoundPaddingLeft();  
  6.         final int compoundPaddingTop = getCompoundPaddingTop();  
  7.         final int compoundPaddingRight = getCompoundPaddingRight();  
  8.         final int compoundPaddingBottom = getCompoundPaddingBottom();  
  9.         final int scrollX = mScrollX;  
  10.         final int scrollY = mScrollY;  
  11.         final int right = mRight;  
  12.         final int left = mLeft;  
  13.         final int bottom = mBottom;  
  14.         final int top = mTop;  
  15.         final boolean isLayoutRtl = isLayoutRtl();  
  16.         final int offset = getHorizontalOffsetForDrawables();  
  17.         final int leftOffset = isLayoutRtl ? 0 : offset;  
  18.         final int rightOffset = isLayoutRtl ? offset : 0 ;  
  19.   
  20.         final Drawables dr = mDrawables;  
  21.         if (dr != null) {  
  22.   
  23.             int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;  
  24.             int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;  
  25.   
  26.             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()  
  27.             // Make sure to update invalidateDrawable() when changing this code.  
  28.             if (dr.mDrawableLeft != null) {  
  29.                 canvas.save();  
  30.                 canvas.translate(scrollX + mPaddingLeft + leftOffset,  
  31.                                  scrollY + compoundPaddingTop +  
  32.                                  (vspace - dr.mDrawableHeightLeft) / 2);  
  33.                 dr.mDrawableLeft.draw(canvas);  
  34.                 canvas.restore();  
  35.             }  
  36.   
  37.         mTextPaint.setColor(color);  
  38.         mTextPaint.drawableState = getDrawableState();  
  39.   
  40.         canvas.save();  
  41.         canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);  
  42.         canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);  
  43.   
  44.         if (mEditor != null) {  
  45.             mEditor.onDraw(canvas, layout, highlight, highlightPaint, cursorOffsetVertical);  
  46.         } else {  
  47.             layout.draw(canvas, highlight, highlightPaint, cursorOffsetVertical);  
  48.         }  
  49.   
  50.         if (mMarquee != null && mMarquee.shouldDrawGhost()) {  
  51.             final int dx = (int) mMarquee.getGhostOffset();  
  52.             mBidiFormat = BidiFormatter.getInstance();  
  53.             if (!mBidiFormat.isRtl(mText.toString())) {  
  54.                 canvas.translate(+dx, 0.0f);  
  55.             } else {  
  56.                 canvas.translate(-dx, 0.0f);  
  57.             }  
  58.             layout.draw(canvas, highlight, highlightPaint, cursorOffsetVertical);  
  59.         }  
  60.   
  61.         //NAGSM_ANDROID_HQ_FRWK Gate +  
  62.         if(GateConfig.isGateEnabled() && GateConfig.isGateLcdtextEnabled())  
  63.             Log.i("GATE""<GATE-M>LCDSTR:" + mText + "/LCDSTR</GATE-M>");  
  64.         //NAGSM_ANDROID_HQ_FRWK Gate -  
  65.         canvas.restore();  
  66.   
  67.         if (mTextStrikeThroughEnabled) {  
  68.             drawTextStrikethrough(canvas);  
  69.         }  
  70.     }  

这个函数很长,所以我们还是按照老办法,分段来分析这个函数。

1 位置的计算

 
  1. final int compoundPaddingLeft = getCompoundPaddingLeft();  
  2.       final int compoundPaddingTop = getCompoundPaddingTop();  
  3.       final int compoundPaddingRight = getCompoundPaddingRight();  
  4.       final int compoundPaddingBottom = getCompoundPaddingBottom();  
  5.       final int scrollX = mScrollX;  
  6.       final int scrollY = mScrollY;  
  7.       final int right = mRight;  
  8.       final int left = mLeft;  
  9.       final int bottom = mBottom;  
  10.       final int top = mTop;  
  11.       final boolean isLayoutRtl = isLayoutRtl();  
  12.       final int offset = getHorizontalOffsetForDrawables();  
  13.       final int leftOffset = isLayoutRtl ? 0 : offset;  
  14.       final int rightOffset = isLayoutRtl ? offset : 0 ;  

首先是一系列位置的计算,这些在app编码设计时都可以修改赋值,应该都是一些屏幕上绝对坐标,后面的绘制会使用到这些坐标。

2 Drawable的绘制

 
  1. int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;  
  2.             int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;  
  3.             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()  
  4.             // Make sure to update invalidateDrawable() when changing this code.  
  5.             if (dr.mDrawableLeft != null) {  
  6.                 canvas.save();  
  7.                 canvas.translate(scrollX + mPaddingLeft + leftOffset,  
  8.                                  scrollY + compoundPaddingTop +  
  9.                                  (vspace - dr.mDrawableHeightLeft) / 2);  
  10.                 dr.mDrawableLeft.draw(canvas);  
  11.                 canvas.restore();  
  12.             }  

首先是view上下左右四个方向上的drawable的绘制(代码只贴了左边的drawable的绘制),我们是可以再view上设置这么多drawable的,如下图:
此处输入图片的描述
下面我们一个个来看下调用的函数。

2.1 Canvas.save 状态的保存

看代码之前,我们来看下api中对这个函数的说明:

 
  1. /** 
  2.     * Based on saveFlags, can save the current matrix and clip onto a private 
  3.     * stack. Subsequent calls to translate,scale,rotate,skew,concat or 
  4.     * clipRect,clipPath will all operate as usual, but when the balancing 
  5.     * call to restore() is made, those calls will be forgotten, and the 
  6.     * settings that existed before the save() will be reinstated. 
  7.     * 
  8.     * @param saveFlags flag bits that specify which parts of the Canvas state 
  9.     *                  to save/restore 
  10.     * @return The value to pass to restoreToCount() to balance this save() 
  11.     */ 

从api里我们可以看到这个函数的作用,它是为了保存当前的一些状态,比如矩阵,坐标等等。后面函数可以随意根据自己的需要进行一些translate,scale,rotate,skew,concat之类的操作,然后在restore调用后,这些变化将恢复到save时的状态。类似与游戏存档读档的功能。
在硬件加速打开的场景下,这里实际上是调用了GLES20Canvas的save函数,

 
  1. public int save() {  
  2.      return nSave(mRenderer, Canvas.CLIP_SAVE_FLAG | Canvas.MATRIX_SAVE_FLAG);  
  3.  }  
  4.   
  5.  static jint android_view_GLES20Canvas_save(JNIEnv* env, jobject clazz, OpenGLRenderer* renderer,  
  6.      jint flags) {  
  7.  return renderer->save(flags);  
  8.  }  

我们看到,最终实现还是通过renderer来实现,基本上Canvas的命令都是这么一个过程。

 
  1. int DisplayListRenderer::save(int flags) {  
  2.     addStateOp(new (alloc()) SaveOp(flags));  
  3.     return OpenGLRenderer::save(flags);  
  4. }  

来看下save函数的具体实现,上面这个函数分为两部分,首先是通过addStateOp将具体操作对应的Op保存在了一个容器里面;接着通过调用OpenGLRenderer的函数,使调用立刻生效。我们来分别看下。

2.1.1 addStateOp操作命令的保存

我们首先要来理解一些Op的概念。在displaylist生效的状态下,硬件加速不会针对每一条命令都发送给GPU执行,而是先录制在displaylist里面,在最后通过回放的方式一起执行(这个详细的过程我们会在HWUI一章中讲到)。

目前我们可以这样理解Op,就是HWUI里面为了所有类型的操作都实现了对应的Op类,存储时只要存储一个类的对象即可。所有的Op都是DisplayListOp的子类,又分为两大类,一类是状态类的StateOp,一类是绘制类的DrawOp。Op在被回放时,会调用它们的replay函数,一般来说,StateOp会继续调用applyState来实现真正的逻辑,DrawOp则是调用applyDraw。

我们来看下这次保存的这个SaveOp是执行时是做了什么操作。

 
  1. virtual void applyState(OpenGLRenderer& renderer, int saveCount) const {  
  2.     renderer.save(mFlags);  
  3. }  

这里无非就是执行了OpenGLRenderer的save方法,我们可以这样认为,OpenGLRenderer就可以实现renderer的所有功能,一旦我们调用OpenGLRenderer类的某个函数,那就意味着这条命令会被立刻执行(而不是先保存起来)。

2.1.2 save命令的执行

我们来继续看下save命令在OpenGLRenderer里面的执行(顺便提一句,从DisplayListRenderer::save代码我们看到,其实保存完Op后,save命令其实立刻被执行了。事实上,应该是所有的状态类Op在保存完之后都会立刻执行,而绘制类Op并不会执行,猜测原因应该是状态类Op其实是不需要GPU运算的,因此可以立刻执行):

 
  1. int OpenGLRenderer::save(int flags) {  
  2.     return saveSnapshot(flags);  
  3. }  
  4. int OpenGLRenderer::saveSnapshot(int flags) {  
  5.     mSnapshot = new Snapshot(mSnapshot, flags);  
  6.     return mSaveCount++;  
  7. }  

我们发现,这里其实就是将当前的状态保存在了一个快照Snapshot里面。所以我们下面来看下快照的概念。

2.1.3 快照Snapshot

本小节参考(http://blog.csdn.net/wind_hzx/article/details/20315471)
Snapshot可以称为快照,hwui就是通过Snapshot的方式来组织整个渲染状态的,Snapshot保存了当前渲染所需的视口,变换矩阵,裁剪区域,fbo信息等。同时还保存了当前Snapshot的上一个Snapshot,也就是通过栈的方式来组织Snapshot之间的关系的。
此处输入图片的描述
如图所示,在OpenGLRenderer类创建的时候,将会创建一个空的Snapshot,也就是FirstSnapshot,也可以称之为根节点,接着每次创建新的节点,都将会保存上一次的节点指针,新节点都用指针mSnapshot,来指向当年的节点,其实这就是用栈的形式来存储hwui的绘制状态,通过设置好Snapshot后,后续的绘图操作都会在mSnapshot(当前快照)上进行操作,当当前的绘制操作完成后,可以返回到上一次的Snapshot中,下一次的绘制操作不会影响上一次的绘制操作,但是上一次的绘制操作,可以通过设置来确定是否可以影响下一次Snapshot的渲染状态。
其实Snapshot很简单,本质上就是一个保存状态的快照,类似于网页快照的概念。唯一一点稍微复杂的逻辑,就是会根据flag的不同,对之前状态进行一些变换,进而生成当前的状态。

2.2 translate坐标变换

保存完状态后,函数调用translate开始进行绘制前的坐标变换。后面这种调用我们不再贴中途调用的流程,因为流程基本都是GLES20Canvas→JNI→DisplayListRenderer的顺序,我们直接看DisplayListRenderer的函数:

 
  1. void DisplayListRenderer::translate(float dx, float dy) {  
  2.     mHasTranslate = true;  
  3.     mTranslateX += dx;  
  4.     mTranslateY += dy;  
  5.     insertRestoreToCount();  
  6.     OpenGLRenderer::translate(dx, dy);  
  7. }  
  8. void DisplayListRenderer::insertRestoreToCount() {  
  9.     if (mRestoreSaveCount >= 0) {  
  10.         DisplayListOp* op = new (alloc()) RestoreToCountOp(mRestoreSaveCount);  
  11.         mDisplayListData->displayListOps.add(op);  
  12.         mRestoreSaveCount = -1;  
  13.     }  
  14. }  

和前面save函数一样,这里同样是保存了一个Op,然后立刻进行了坐标的变换。但是有趣的是,这里保存的一个Op的类型是一个RestoreToCountOp类型,看起来在执行时也压根没有做坐标变换(这个还可以理解,毕竟在录制过程中已经先做好了坐标变化,那么录制到的绘制命令坐标已经是更改过的,因此,实际上直接拿来执行即可),而且做了一些奇怪的操作(这部分代码相当奇怪,需要详细的展开研究)。
我们暂且跳过。

2.3 Drawable的绘制

接下来就是将当前的Drawable进行绘制,这里根据Drawable具体类型的不同,函数也不同,我们这里分析一个最常见的BitmapDrawable的draw过程:

 
  1. public void draw(Canvas canvas) {  
  2.             final BitmapState state = mBitmapState;  
  3.             if (state.mRebuildShader) {  
  4.                 Shader.TileMode tmx = state.mTileModeX;  
  5.                 Shader.TileMode tmy = state.mTileModeY;  
  6.                 if (tmx == null && tmy == null) {  
  7.                     state.mPaint.setShader(null);  
  8.                 } else {  
  9.                     state.mPaint.setShader(new BitmapShader(bitmap,  
  10.                             tmx == null ? Shader.TileMode.CLAMP : tmx,  
  11.                             tmy == null ? Shader.TileMode.CLAMP : tmy));  
  12.                 }  
  13.                 state.mRebuildShader = false;  
  14.                 copyBounds(mDstRect);  
  15.             }  
  16.             Shader shader = state.mPaint.getShader();  
  17.             if (shader == null) {  
  18.                 canvas.drawBitmap(bitmap, null, mDstRect, state.mPaint);  
  19.             } else {  
  20.                 canvas.drawRect(mDstRect, state.mPaint);  
  21.             }  
  22.     }  

由于我们重点分析Graphic层面的逻辑,因此这里和上层有关的一些概念我们不再讲解,Shader有关的知识可以参考这篇博客:http://blog.csdn.net/ldj299/article/details/6166071,BitmapShader可以参考这篇:http://blog.csdn.net/sjf0115/article/details/7267532。

我们这里重点分析下canvas的两个draw函数:drawBitmap和drawRect。依然是直接看DisplayListRenderer的对应函数:

 
  1. status_t DisplayListRenderer::drawBitmap(SkBitmap* bitmap, float left, float top, SkPaint* paint) {  
  2.     bitmap = refBitmap(bitmap);  
  3.     paint = refPaint(paint);  
  4.     addDrawOp(new (alloc()) DrawBitmapOp(bitmap, left, top, paint));  
  5.     return DrawGlInfo::kStatusDone;  
  6. }  

这里添加了一个新的DrawBitmapOp,它在reply时会调用OpenGlRenderer的drawBitmap函数。绘制的执行过程,我们会在DisplayList的回放过程中一起分析。

3 layout.draw的绘制

onDraw函数的中段很长一部分是一些参数的设置,我们这里不详细研究上层的逻辑,我们接下来重点看下一个绘制函数,layout.draw。

 
  1. public void draw(Canvas canvas, Path highlight, Paint highlightPaint,  
  2.         int cursorOffsetVertical) {  
  3.     final long lineRange = getLineRangeForDraw(canvas);  
  4.     int firstLine = TextUtils.unpackRangeStartFromLong(lineRange);  
  5.     int lastLine = TextUtils.unpackRangeEndFromLong(lineRange);  
  6.     if (lastLine < 0) return;  
  7.   
  8.     drawBackground(canvas, highlight, highlightPaint, cursorOffsetVertical,  
  9.             firstLine, lastLine);  
  10.     drawText(canvas, firstLine, lastLine);  
  11. }  

我们直接看drawText,这个函数同样很长,但是大多数是view层面的一些参数设置,我们依然只关心Graphic层面的绘制:
其实重点只是这一点:

 
  1. {  
  2. ...  
  3.             if (directions == DIRS_ALL_LEFT_TO_RIGHT && !mSpannedText && !hasTabOrEmoji) {  
  4.                 canvas.drawText(buf, start, end, x, lbaseline, paint);  
  5.             } else {  
  6.                 tl.set(paint, buf, start, end, dir, directions, hasTabOrEmoji, tabStops);  
  7.                 tl.draw(canvas, x, ltop, lbaseline, lbottom);  
  8.             }  
  9. ...  
  10. }  

看到了canvas的drawText,在打开硬件加速的情况下,我们可以按照老规矩直接看DisplayListRenderer的对应函数:

 
  1. status_t DisplayListRenderer::drawText(const char* text, int bytesCount, int count,  
  2.         float x, float y, const float* positions, SkPaint* paint,  
  3.         float totalAdvance, const Rect& bounds, DrawOpMode drawOpMode) {  
  4.   
  5.     text = refText(text, bytesCount);  
  6.     positions = refBuffer<float>(positions, count * 2);  
  7.     paint = refPaint(paint);  
  8.   
  9.     DrawOp* op = new (alloc()) DrawTextOp(text, bytesCount, count,  
  10.             x, y, positions, paint, totalAdvance, bounds);  
  11.     addDrawOp(op);  
  12.     return DrawGlInfo::kStatusDone;  
  13. }  

这里依然是op的创建,然后添加,目前并不会真正执行,我们会在DisplayList的回放过程中一起分析。

小节

上面我们分析了从view怎么调用到Graphic层面,来将view分解到Graphic的Op层面。不管是什么view,整个流程都是一致的,可能只在最后调用时是创建了bitmap的op还是text的op等等的区别。因为本文主要从Graphic的层面分析绘制问题,因为我们这里没有详细展开view层面全部完整的设置和调用过程。但是通过上面的方法,我想我们可以分析任何一个View的创建Op的过程。
其实我们发现view层面生成Op的过程还是很简单的,无非只是根据参数的不同进行一些设置,然后根据要绘制内容的不同调用canvas不同的方法,本质上,Framework层面的调用和我们在app中使用canvas也并无明显不同。
Canvas调用成功后,通过GLES20Canvas→JNI→DisplayListRenderer的顺序,调用了DisplayListRenderer的方法,DisplayListRenderer一般只是直接生成对应Op并保存,等待后面一起调用OpenGLRenderer的方法真正执行。需要指出的是,对应state类的Op,并不会等待,而是在DisplayListRenderer中被调用时就会直接执行。
对于绘制类的Op,在保存后,会等待执行,至于执行的时机,以及具体如何执行,我们将在后文HWUI讲解完成之后再次进行分析Op命令的真正执行过程

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值