美文网首页 移动 前端 Python Android Java相机
Android图形系统(九)-View、Canvas与Surfa

Android图形系统(九)-View、Canvas与Surfa

作者: Stan_Z | 来源:发表于2018-11-19 20:03 被阅读147次

    我们已经分析了,mWindowSession.addToDisplay 通过WMS.addWindow 我们建立了app与SurfaceFlinger服务连接。并且通过requestLayout中的relayoutWindow, app请求SurfaceFlinger创建了Surface。那么接下来,我们再分析下app的视图是如何被绘制到GraphicFrame上的。这里面牵扯到的View、Canvas与Surface的关系,用这篇文章来梳理一下。

    之前我们讲到requestLayout,开始了view的measure、layout、draw流程,我们从performDraw开始关注下最后的视图绘制工作:

    //ViewRootImpl
    private void performDraw() {
          ...
          draw(fullRedrawNeeded);
          ...
    }
    

    然后看ViewRootImpl的draw方法:

    //ViewRootImpl
      private void draw(boolean fullRedrawNeeded) {
           Surface surface = mSurface;
        ...
                   if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                       return;
                   }
        ...
       }
    

    这里我们看到,surface 在这里被接收了,并传入了drawSoftware。

    //ViewRootImpl
      private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
               boolean scalingRequired, Rect dirty) {
    // Draw with software renderer.
    final Canvas canvas;
    canvas = mSurface.lockCanvas(dirty);  //1.获取Canvas
    ...
    mView.draw(canvas); //2.通过Canvas绘制视图
    ...
    surface.unlockCanvasAndPost(canvas); //3.绘制结束
    }
    

    在drawSoftware方法中,我们重点关注如上三个方法:

    一、 Surface的lockCanvas函数
    //Surface
    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;
       }
    }
    

    其最终调用了native函数nativeLockCanvas

    //android_view_Surface.cpp
    static jlong nativeLockCanvas(JNIEnv* env, jclass clazz,
           jlong nativeObject, jobject canvasObj, jobject dirtyRectObj) {
       sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject));
        ...
       ANativeWindow_Buffer outBuffer;
        //从SurfaceFlinger中申请内存buffer
       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);
       //新建了一个SkBitmap,并进行了一系列设置
       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);
        //把这个bitmap放入Canvas中
       nativeCanvas->setBitmap(bitmap);
       ...
       sp<Surface> lockedSurface(surface);
       lockedSurface->incStrong(&sRefBaseOwner);
       return (jlong) lockedSurface.get();
    }
    

    这里主要关注几个点:

    1.1 surface->lock(&outBuffer, dirtyRectPtr)

    调用了Surface的lock函数实际上主要是调用了Surface的dequeueBuffer,而这个函数的主要目的是从SurfaceFlinger中申请GraphicBuffer, 这个buffer是用来传递绘制的元数据的。

    1.2 GraphicsJNI::getNativeCanvas(env, canvasObj)

    构造一个native的Canvas对象(SKCanvas),再返回这个Canvas对象,java层的Canvas对象其实只是对SKCanvas对象的一个简单包装,所有绘制方法都是转交给SKCanvas来做。

    1.3 SkBitmap bitmap

    Canvas底层是通过2D图形引擎skia进行图形绘制的,SkBitmap是skia中很重要的一个类,很多画图动作涉及到SkBitmap,它封装了与位图相关的一系列操作。那么在这里,bitmap对下设置了获取的内存buffer,对上关联了Canvas ,即把这个bitmap放入了Canvas中( nativeCanvas->setBitmap(bitmap) )

    总结:Surface的lockCanvas函数会通过jni调用对应的native方法,本质是通过Surface的dequeueBuffer获取一块用于存放绘制元数据的GraphicBuffer,然后构造一个SKBitmap,它是绘制的核心, 对下关联buffer,对上关联canvas。

    二、mView.draw(canvas)

    这其实就是通过Canvas去实现具体的绘制。
    以TextView的一部分绘制代码为例:

    protected void onDraw(Canvas canvas) {
    ...
    if (dr.mShowing[Drawables.LEFT] != null) {
       canvas.save();//坐标系的原点,坐标轴方向的信息。
       canvas.translate(scrollX + mPaddingLeft + leftOffset,
                        scrollY + compoundPaddingTop +
                        (vspace - dr.mDrawableHeightLeft) / 2);
       dr.mShowing[Drawables.LEFT].draw(canvas);
       canvas.restore();//恢复Canvas之前保存的状态
      }
    ...
    }
    

    主要看绘制:

    //Canvas.java
    public void translate(float dx, float dy) {
        native_translate(mNativeCanvasWrapper, dx, dy);
    }
    
    //android_graphics_Canvas.cpp
    static void translate(JNIEnv*, jobject, jlong canvasHandle, jfloat dx, jfloat dy) {
        get_canvas(canvasHandle)->translate(dx, dy);
    }
    
    //external/skia/src/core/SkCanvas.cpp
    void SkiaCanvas::translate(float dx, float dy) {
        mCanvas->translate(dx, dy);
    }
    
    //external/skia/src/core/SkCanvas.cpp
    void SkCanvas::translate(SkScalar dx, SkScalar dy) {
       if (dx || dy) {
           this->checkForDeferredSave();
           fMCRec->fMatrix.preTranslate(dx,dy);
           // Translate shouldn't affect the is-scale-translateness of the matrix.
          SkASSERT(fIsScaleTranslate == fMCRec->fMatrix.isScaleTranslate());
          FOR_EACH_TOP_DEVICE(device->setGlobalCTM(fMCRec->fMatrix));
         this->didTranslate(dx,dy);
      }
    }
    

    从SkCanvas.cpp的路径我们可以看出,这已经在skia绘制引擎部分,这里就不深究了。只要了解:我们通过java层Canvas封装的api调用底层SKCanvas来完成真正的绘制工作就足够了。

    三、surface.unlockCanvasAndPost(canvas);
    //Surface.java
    public void unlockCanvasAndPost(Canvas canvas) {
            synchronized (mLock) {
                checkNotReleasedLocked();
                if (mHwuiContext != null) {
                    mHwuiContext.unlockAndPost(canvas);
                } else {
                    unlockSwCanvasAndPost(canvas);
                }
            }
        }
    

    正常是调用unlockSwCanvasAndPost:

    //Surface.java
    private void unlockSwCanvasAndPost(Canvas canvas) {
        if (canvas != mCanvas) {
            throw new IllegalArgumentException("canvas object must be the same instance that "
                    + "was previously returned by lockCanvas");
        }
        if (mNativeObject != mLockedObject) {
            Log.w(TAG, "WARNING: Surface's mNativeObject (0x" +
                    Long.toHexString(mNativeObject) + ") != mLockedObject (0x" +
                    Long.toHexString(mLockedObject) +")");
        }
        if (mLockedObject == 0) {
            throw new IllegalStateException("Surface was not locked");
        }
        try {
            nativeUnlockCanvasAndPost(mLockedObject, canvas);
        } finally {
            nativeRelease(mLockedObject);
            mLockedObject = 0;
        }
    }
    

    往下看native方法

    //android_view_Surface.cpp
    static void nativeUnlockCanvasAndPost(JNIEnv* env, jclass clazz,
            jlong nativeObject, jobject canvasObj) {
        sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject));
        if (!isSurfaceValid(surface)) {
            return;
        }
        // detach the canvas from the surface
        Canvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvasObj);
        nativeCanvas->setBitmap(SkBitmap());
        // unlock surface
        status_t err = surface->unlockAndPost();
        if (err < 0) {
            doThrowIAE(env);
        }
    }
    

    看下如何解锁surface:

    //Surface.cpp
    status_t Surface::unlockAndPost()
    {
        if (mLockedBuffer == 0) {
            ALOGE("Surface::unlockAndPost failed, no locked buffer");
            return INVALID_OPERATION;
        }
        int fd = -1;
        status_t err = mLockedBuffer->unlockAsync(&fd);//通过Gralloc模块,最后是操作的ioctl
        ALOGE_IF(err, "failed unlocking buffer (%p)", mLockedBuffer->handle);
        err = queueBuffer(mLockedBuffer.get(), fd);
        ALOGE_IF(err, "queueBuffer (handle=%p) failed (%s)",
                mLockedBuffer->handle, strerror(-err));
        mPostedBuffer = mLockedBuffer;
        mLockedBuffer = 0;
        return err;
    }
    

    我们看到了queueBuffer函数, 而在Surface的queueBuffer函数中调用了如下函数:

    mGraphicBufferProducer->queueBuffer
    

    这个函数最终会将BufferItem的buffer清除,通知消费者的onFrameAvailable接口。然后消费者可以根据mSlots的序号再来拿buffer。

    //frameworks/native/libs/gui/BufferQueueProducer.cpp
    status_t BufferQueueProducer::queueBuffer(int slot,
          const QueueBufferInput &input, QueueBufferOutput *output) {
      ...
    
        item.mGraphicBuffer.clear();
        item.mSlot = BufferItem::INVALID_BUFFER_SLOT;
        // Call back without the main BufferQueue lock held, but with the callback
        // lock held so we can ensure that callbacks occur in order
        {
            Mutex::Autolock lock(mCallbackMutex);
            while (callbackTicket != mCurrentCallbackTicket) {
                mCallbackCondition.wait(mCallbackMutex);
            }
            if (frameAvailableListener != NULL) {
                frameAvailableListener->onFrameAvailable(item);
            } else if (frameReplacedListener != NULL) {
                frameReplacedListener->onFrameReplaced(item);
            }
            ++mCurrentCallbackTicket;
            mCallbackCondition.broadcast();
        }
    ...
    }
    

    所以整个过程看起来还是比较简单的。最后把整个流程再简单总结下,View、Canvas与Surface的关系也就一目了然了:

    1. Surface通过dequeueBuffer流程(具体操作在此不多赘述)获取一块存放绘制数据的buffer。

    2. View 在onDraw的时候,通过传入的Canvas进行绘制。(这里只是一个绘制的入口而已,本文是针对requestLayout流程来讲述的,当然你单独用Canvas实现绘制也是一样的)。

    3. 调用java层的CanvasAPI,实际真正负责绘制工作的是底层的Skia引擎,这里核心类包括SKCanvas(画家)以及SKBitmap(画布),绘制好的内容放入Surface 通过dequeueBuffer获取到的GraphicBuffer。

    4. 绘制完毕后,Surface通过queueBuffer将存放好绘制数据的buffer投递到队列中,并通知SurfaceFlinger消费。

    参考:
    https://blog.csdn.net/kc58236582/article/details/52879698
    https://blog.csdn.net/jianpan_zouni/article/details/77649271

    相关文章

      网友评论

        本文标题:Android图形系统(九)-View、Canvas与Surfa

        本文链接:https://www.haomeiwen.com/subject/ekoafqtx.html