Android UICanvas 画布 ⑥ ( Canvas 绘图源码分析 | ViewRootImpl#draw 方法源码 | ViewRootImpl#drawSoftware 方法源码 )

Posted 韩曙亮

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android UICanvas 画布 ⑥ ( Canvas 绘图源码分析 | ViewRootImpl#draw 方法源码 | ViewRootImpl#drawSoftware 方法源码 )相关的知识,希望对你有一定的参考价值。

文章目录


Canvas 状态保存机制 中 , 存在两个栈结构 , 分别是 状态栈 图层栈 ;

其中 图层栈 又称为 Layer 栈 ;


Canvas 画布中 , 有 2 2 2 套坐标系 , 分别是 :

  • Canvas 自身坐标系
  • Canvas 绘图坐标系




一、Canvas 绘图源码分析



参考 【Android 应用开发】UI绘制流程 ( 生命周期机制 | 布局加载机制 | UI 绘制流程 | 布局测量 | 布局摆放 | 组件绘制 | 瀑布流布局案例 ) 博客 , android 的 UI 界面绘制流程为 :

  • 布局测量
  • 布局摆放
  • 组件绘制

这里 分析 Android 组件绘制过程中 , Canvas 画布相关操作 ;

在绘制时 , 最终调用的方法是 ViewRootImpl#draw 方法 , 在该方法中

Surface surface 是最终绘制的面板 ,

Surface surface = mSurface;

绘图时 , 首先要确认绘制区域 , 下面的代码就是 在 手机界面 中 定位出 绘制区域 用的 , 这块绘制区域 是属于 Surface 的 ,

        final Rect dirty = mDirty;
        if (mSurfaceHolder != null) 
            // The app owns the surface, we won't draw.
            dirty.setEmpty();
            if (animating && mScroller != null) 
                mScroller.abortAnimation();
            
            return;
        

        if (fullRedrawNeeded) 
            mAttachInfo.mIgnoreDirtyState = true;
            dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
        

在该方法最后调用了 ViewRootImpl#drawSoftware 方法 , 进行下一步绘制操作 ;

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

ViewRootImpl#drawSoftware 方法中 , 由 ViewRootImpl#mSurface 生成 Canvas 画布 , 并为 Canvas 设置绘制坐标 , Rect dirty 可以理解为 Canvas 的绘制坐标区域 ;

        // 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);

            // The dirty rectangle can be modified by Surface.lockCanvas()
            //noinspection ConstantConditions
            if (left != dirty.left || top != dirty.top || right != dirty.right
                    || bottom != dirty.bottom) 
                attachInfo.mIgnoreDirtyState = true;
            

            // TODO: Do this in native
            canvas.setDensity(mDensity);
         




二、ViewRootImpl#draw 方法源码



ViewRootImpl#draw 方法源码 :

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks 
    private void draw(boolean fullRedrawNeeded) 
    	// 具体绘画的面板
        Surface surface = mSurface;
        if (!surface.isValid()) 
            return;
        

        if (DEBUG_FPS) 
            trackFPS();
        

        if (!sFirstDrawComplete) 
            synchronized (sFirstDrawHandlers) 
                sFirstDrawComplete = true;
                final int count = sFirstDrawHandlers.size();
                for (int i = 0; i< count; i++) 
                    mHandler.post(sFirstDrawHandlers.get(i));
                
            
        

        scrollToRectOrFocus(null, false);

        if (mAttachInfo.mViewScrollChanged) 
            mAttachInfo.mViewScrollChanged = false;
            mAttachInfo.mTreeObserver.dispatchOnScrollChanged();
        

        boolean animating = mScroller != null && mScroller.computeScrollOffset();
        final int curScrollY;
        if (animating) 
            curScrollY = mScroller.getCurrY();
         else 
            curScrollY = mScrollY;
        
        if (mCurScrollY != curScrollY) 
            mCurScrollY = curScrollY;
            fullRedrawNeeded = true;
            if (mView instanceof RootViewSurfaceTaker) 
                ((RootViewSurfaceTaker) mView).onRootViewScrollYChanged(mCurScrollY);
            
        

        final float appScale = mAttachInfo.mApplicationScale;
        final boolean scalingRequired = mAttachInfo.mScalingRequired;

        int resizeAlpha = 0;

        final Rect dirty = mDirty;
        if (mSurfaceHolder != null) 
            // The app owns the surface, we won't draw.
            dirty.setEmpty();
            if (animating && mScroller != null) 
                mScroller.abortAnimation();
            
            return;
        

        if (fullRedrawNeeded) 
            mAttachInfo.mIgnoreDirtyState = true;
            dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
        

        if (DEBUG_ORIENTATION || DEBUG_DRAW) 
            Log.v(mTag, "Draw " + mView + "/"
                    + mWindowAttributes.getTitle()
                    + ": dirty=" + dirty.left + "," + dirty.top
                    + "," + dirty.right + "," + dirty.bottom + " surface="
                    + surface + " surface.isValid()=" + surface.isValid() + ", appScale:" +
                    appScale + ", width=" + mWidth + ", height=" + mHeight);
        

        mAttachInfo.mTreeObserver.dispatchOnDraw();

        int xOffset = -mCanvasOffsetX;
        int yOffset = -mCanvasOffsetY + curScrollY;
        final WindowManager.LayoutParams params = mWindowAttributes;
        final Rect surfaceInsets = params != null ? params.surfaceInsets : null;
        if (surfaceInsets != null) 
            xOffset -= surfaceInsets.left;
            yOffset -= surfaceInsets.top;

            // Offset dirty rect for surface insets.
            dirty.offset(surfaceInsets.left, surfaceInsets.right);
        

        boolean accessibilityFocusDirty = false;
        final Drawable drawable = mAttachInfo.mAccessibilityFocusDrawable;
        if (drawable != null) 
            final Rect bounds = mAttachInfo.mTmpInvalRect;
            final boolean hasFocus = getAccessibilityFocusedRect(bounds);
            if (!hasFocus) 
                bounds.setEmpty();
            
            if (!bounds.equals(drawable.getBounds())) 
                accessibilityFocusDirty = true;
            
        

        mAttachInfo.mDrawingTime =
                mChoreographer.getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;

        if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) 
            if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) 
                // If accessibility focus moved, always invalidate the root.
                boolean invalidateRoot = accessibilityFocusDirty || mInvalidateRootRequested;
                mInvalidateRootRequested = false;

                // Draw with hardware renderer.
                mIsAnimating = false;

                if (mHardwareYOffset != yOffset || mHardwareXOffset != xOffset) 
                    mHardwareYOffset = yOffset;
                    mHardwareXOffset = xOffset;
                    invalidateRoot = true;
                

                if (invalidateRoot) 
                    mAttachInfo.mThreadedRenderer.invalidateRoot();
                

                dirty.setEmpty();

                // Stage the content drawn size now. It will be transferred to the renderer
                // shortly before the draw commands get send to the renderer.
                final boolean updated = updateContentDrawBounds();

                if (mReportNextDraw) 
                    // report next draw overrides setStopped()
                    // This value is re-sync'd to the value of mStopped
                    // in the handling of mReportNextDraw post-draw.
                    mAttachInfo.mThreadedRenderer.setStopped(false);
                

                if (updated) 
                    requestDrawWindow();
                

                mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
             else 
                // If we get here with a disabled & requested hardware renderer, something went
                // wrong (an invalidate posted right before we destroyed the hardware surface
                // for instance) so we should just bail out. Locking the surface with software
                // rendering at this point would lock it forever and prevent hardware renderer
                // from doing its job when it comes back.
                // Before we request a new frame we must however attempt to reinitiliaze the
                // hardware renderer if it's in requested state. This would happen after an
                // eglTerminate() for instance.
                if (mAttachInfo.mThreadedRenderer != null &&
                        !mAttachInfo.mThreadedRenderer.isEnabled() &&
                        mAttachInfo.mThreadedRenderer.isRequested()) 

                    try 
                        mAttachInfo.mThreadedRenderer.initializeIfNeeded(
                                mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
                     catch (OutOfResourcesException e) 
                        handleOutOfResourcesException(e);
                        return;
                    

                    mFullRedrawNeeded = true;
                    scheduleTraversals();
                    return;
                

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

        if (animating) 
            mFullRedrawNeeded = true;
            scheduleTraversals();
        
    

源码参考路径 : frameworks/base/core/java/android/view/ViewRootImpl.java





三、ViewRootImpl#drawSoftware 方法源码



ViewRootImpl#drawSoftware 方法源码 :

    /**
     * @return true if drawing was successful, false if an error occurred
     */
    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);

            // The dirty rectangle can be modified by Surface.lockCanvas()
            //noinspection ConstantConditions
            if (left != dirty.left || top != dirty.top || right != dirty.right
                    || bottom != dirty.bottom) 
                attachInfo.mIgnoreDirtyState = true;
            

            // TODO: Do this in native
            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());
                //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());
            
            try 
                canvas.translate(-xoff, -yoff);
                if (mTranslator != null) 
                    mTranslator.translateCanvas(canvas);
                
                canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
                attachInfo.mSetIgnoreDirtyState = false;

                mView.draw(canvas);

                drawAccessibilityFocusedDrawableIfNeeded(canvas);
             finally 
                if (!attachInfo.mSetIgnoreDirtyState) 
                    // Only clear the flag if it was not set during the mView.draw() call
                    attachInfo.mIgnoreDirtyState = false;
                
            
         finally 
            try 
                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;
    

源码参考路径 : frameworks/base/core/java/android/view/ViewRootImpl.java

以上是关于Android UICanvas 画布 ⑥ ( Canvas 绘图源码分析 | ViewRootImpl#draw 方法源码 | ViewRootImpl#drawSoftware 方法源码 )的主要内容,如果未能解决你的问题,请参考以下文章

Android UICanvas 画布 ② ( Canvas 状态栈 | Canvas 状态栈出栈到指定层级 )

Android UICanvas 画布 ⑦ ( Canvas 绘制显示区域 | Canvas 绘制矩形源码分析 )

Android UICanvas 画布 ⑤ ( Canvas 坐标系 | Canvas 绘图坐标系变换示例 )

Android UICanvas 画布 ③ ( Canvas 图层栈 | Canvas#saveLayer() 新建图层 | Canvas 状态栈保存信息标志位 )

Android UICanvas 画布 ④ ( Canvas 坐标系 | Canvas 自身坐标系 | Canvas 绘图坐标系 )

Android UICanvas 画布 ⑧ ( Canvas 绘图坐标系 2x2 矩阵 | Canvas 绘图坐标系 3x3 操作矩阵 )