Android視窗管理分析(4):Android View繪製記憶體的分配、傳遞、使用

看書的小蝸牛發表於2017-10-20

前文Android匿名共享記憶體(Ashmem)原理分析了匿名共享記憶體,它最主要的作用就是View檢視繪製,Android檢視是按照一幀一幀顯示到螢幕的,而每一幀都會佔用一定的儲存空間,通過Ashmem機制APP與SurfaceFlinger共享繪圖資料,提高圖形處理效能,本文就看Android是怎麼利用Ashmem分配及繪製的:

View檢視記憶體的分配

前文Window新增流程中描述了:在新增視窗的時候,WMS會為APP分配一個WindowState,以標識當前視窗並用於視窗管理,同時向SurfaceFlinger端請求分配Layer抽象圖層,在SurfaceFlinger分配Layer的時候建立了兩個比較關鍵的Binder物件,用於填充WMS端Surface,一個是sp handle:是每個視窗標識的控制程式碼,將來WMS同SurfaceFlinger通訊的時候方便找到對應的圖層。另一個是sp gbp :共享記憶體分配的關鍵物件,同時兼具Binder通訊的功能,用來傳遞指令共享記憶體的控制程式碼,注意,這裡只是抽象建立了物件,並未真正分配每一幀的記憶體,記憶體的分配要等到真正繪製的時候才會申請,首先看一下分配流程:

  • 分配的時機:什麼時候分配
  • 分配的手段:如何分配
  • 傳遞的方式:如何跨程式傳遞

Surface被抽象成一塊畫布,只要擁有Surface就可以繪圖,其根本原理就是Surface握有可以繪圖的一塊記憶體,這塊記憶體是APP端在需要的時候,通過sp gbp向SurfaceFlinger申請的,那麼首先看一下APP端如何獲得sp gbp這個服務代理的,之後再看如何利用它申請記憶體,在WMS利用向SurfaceFlinger申請填充Surface的時候,會請求SurfaceFlinger分配這把劍,並將其控制程式碼交給自己

sp<SurfaceControl> SurfaceComposerClient::createSurface(
        const String8& name,  uint32_t w, uint32_t h, PixelFormat format, uint32_t flags){
      sp<SurfaceControl> sur;
      ...
      if (mStatus == NO_ERROR) {
        sp<IBinder> handle;
        sp<IGraphicBufferProducer> gbp;
        <!--關鍵點1 獲取圖層的關鍵資訊handle, gbp-->
        status_t err = mClient->createSurface(name, w, h, format, flags,
                &handle, &gbp);
         <!--關鍵點2 根據返回的圖層關鍵資訊 建立SurfaceControl物件-->
        if (err == NO_ERROR) {
            sur = new SurfaceControl(this, handle, gbp);
        }
    }
    return sur;
}複製程式碼

看關鍵點1,這裡其實就是建立了一個sp gbp容器,並請求SurfaceFlinger分配填充內容,SurfaceFlinger收到請求後會為WMS建立與APP端對應的Layer,同時為其分配sp gbp,並填充到Surface中返回給APP,

status_t SurfaceFlinger::createNormalLayer(const sp<Client>& client,
        const String8& name, uint32_t w, uint32_t h, uint32_t flags, PixelFormat& format,
        sp<IBinder>* handle, sp<IGraphicBufferProducer>* gbp, sp<Layer>* outLayer){
    ...
    <!--關鍵點 1 -->
    *outLayer = new Layer(this, client, name, w, h, flags);
    status_t err = (*outLayer)->setBuffers(w, h, format, flags);
    <!--關鍵點 2-->
    if (err == NO_ERROR) {
        *handle = (*outLayer)->getHandle();
        *gbp = (*outLayer)->getProducer();
    }
  return err;
}

void Layer::onFirstRef() {
    sp<IGraphicBufferProducer> producer;
    sp<IGraphicBufferConsumer> consumer;
    <!--建立producer與consumer-->
    BufferQueue::createBufferQueue(&producer, &consumer);
    mProducer = new MonitoredProducer(producer, mFlinger);
    mSurfaceFlingerConsumer = new SurfaceFlingerConsumer(consumer, mTextureName,
            this);
   ...
}

void BufferQueue::createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
        sp<IGraphicBufferConsumer>* outConsumer,
        const sp<IGraphicBufferAlloc>& allocator) {
    sp<BufferQueueCore> core(new BufferQueueCore(allocator));
    sp<IGraphicBufferProducer> producer(new BufferQueueProducer(core));
    sp<IGraphicBufferConsumer> consumer(new BufferQueueConsumer(core));
    *outProducer = producer;
    *outConsumer = consumer;
}複製程式碼

從上面兩個函式可以很清楚的看到Producer/Consumer的模型原樣,也就說每個圖層Layer都有自己的producer/ consumer,sp gbp對應的其實是BufferQueueProducer,而BufferQueueProducer是一個Binder通訊物件,在服務端是:

class BufferQueueProducer : public BnGraphicBufferProducer,
                            private IBinder::DeathRecipient {}複製程式碼

在APP端是

class BpGraphicBufferProducer : public BpInterface<IGraphicBufferProducer>{}複製程式碼

IGraphicBufferProducer Binder實體在SurfaceFlinger中建立後,打包到Surface物件,並通過binder通訊傳遞給APP端,APP段通過反序列化將其恢復出來,如下:

status_t Surface::readFromParcel(const Parcel* parcel, bool nameAlreadyRead) {
    if (parcel == nullptr) return BAD_VALUE;

    status_t res = OK;
    if (!nameAlreadyRead) {
        name = readMaybeEmptyString16(parcel);
        // Discard this for now
        int isSingleBuffered;
        res = parcel->readInt32(&isSingleBuffered);
        if (res != OK) {
            return res;
        }
    }
    sp<IBinder> binder;
    res = parcel->readStrongBinder(&binder);
    if (res != OK) return res;
   <!--interface_cast會將其轉換成BpGraphicBufferProducer-->
    graphicBufferProducer = interface_cast<IGraphicBufferProducer>(binder);
    return OK;
}複製程式碼

自此,APP端就獲得了申請記憶體的控制程式碼BpGraphicBufferProducer,它真正發揮作用是在第一次繪圖時,看一下ViewRootImpl中的draw

   private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty) {
            final Canvas canvas;
        try {
            final int left = dirty.left;
            final int top = dirty.top;
            final int right = dirty.right;
            final int bottom = dirty.bottom;
            <!--關鍵點1 獲取繪圖記憶體-->
            canvas = mSurface.lockCanvas(dirty);
        try {
           try {
               <!--關鍵點2 繪圖-->
               mView.draw(canvas);
            }              
        } finally {
            try {
            <!--關鍵點 3 繪圖結束 ,通知surfacefling混排,更新顯示介面-->
              surface.unlockCanvasAndPost(canvas);
            } catch (IllegalArgumentException e) {}複製程式碼

先看關鍵點1,記憶體的分配時機其實就在這裡,直接進入到native層

static jlong nativeLockCanvas(JNIEnv* env, jclass clazz,
        jlong nativeObject, jobject canvasObj, jobject dirtyRectObj) {
    sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject));
    ...
    status_t err = surface->lock(&outBuffer, dirtyRectPtr);
    ...
    sp<Surface> lockedSurface(surface);
    lockedSurface->incStrong(&sRefBaseOwner);
    return (jlong) lockedSurface.get();
}複製程式碼

surface.cpp的lock會進一步呼叫dequeueBuffer函式來請求分配記憶體:

int Surface::dequeueBuffer(android_native_buffer_t** buffer, int* fenceFd) {
    ...
    int buf = -1;
    sp<Fence> fence;
    nsecs_t now = systemTime();
    <!--申請buffer,並獲得識別符號-->
    status_t result = mGraphicBufferProducer->dequeueBuffer(&buf, &fence,
            reqWidth, reqHeight, reqFormat, reqUsage);
    ...
    if ((result & IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION) || gbuf == 0) {
    <!--申請的記憶體是在surfaceflinger程式中,Surface通過呼叫requestBuffer將圖形緩衝區對映到Surface所在程式-->        
        result = mGraphicBufferProducer->requestBuffer(buf, &gbuf);
   ...
}複製程式碼

最終會呼叫BpGraphicBufferProducer的dequeueBuffer向服務端請求分配記憶體,這裡用到了匿名共享記憶體的知識,在Linux中一切都是檔案,共享記憶體也看成一個檔案。分配成功之後,需要跨程式傳遞tmpfs臨時檔案的描述符fd。先看下申請的邏輯:

    class BpGraphicBufferProducer : public BpInterface<IGraphicBufferProducer>{
    virtual status_t dequeueBuffer(int *buf, sp<Fence>* fence, bool async,
            uint32_t w, uint32_t h, uint32_t format, uint32_t usage) {
        Parcel data, reply;
        data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
        data.writeInt32(async);
        data.writeInt32(w);
        data.writeInt32(h);
        data.writeInt32(format);
        data.writeInt32(usage);
        //通過BpBinder將要什麼的buffer的相關引數儲存到data,傳送給BBinder
        status_t result = remote()->transact(DEQUEUE_BUFFER, data, &reply);
        if (result != NO_ERROR) {
            return result;
        }
        //BBinder給BpBinder返回了一個int,並不是緩衝區的記憶體
        *buf = reply.readInt32();
        bool nonNull = reply.readInt32();
        if (nonNull) {
            *fence = new Fence();
            reply.read(**fence);
        }
        result = reply.readInt32();
        return result;
    }
}複製程式碼

在client側,也就是BpGraphicBufferProducer側,通過DEQUEUE_BUFFER後核心只返回了一個*buf = reply.readInt32();其實是陣列mSlots的下標,在BufferQueue中有個和mSlots對應的陣列,也是32個,一一對應,

status_t BnGraphicBufferProducer::onTransact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
      case DEQUEUE_BUFFER: {
            CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
            bool async      = data.readInt32();
            uint32_t w      = data.readInt32();
            uint32_t h      = data.readInt32();
            uint32_t format = data.readInt32();
            uint32_t usage  = data.readInt32();
            int buf;
            sp<Fence> fence;
            //呼叫BufferQueue的dequeueBuffer
            //也返回一個int的buf
            int result = dequeueBuffer(&buf, &fence, async, w, h, format, usage);
            //將buf和fence寫入parcel,通過binder傳給client
            reply->writeInt32(buf);
            reply->writeInt32(fence != NULL);
            if (fence != NULL) {
                reply->write(*fence);
            }
            reply->writeInt32(result);
            return NO_ERROR;
}複製程式碼

可以看到BnGraphicBufferProducer端獲取到長寬及格式,之後利用BufferQueueProducer的dequeueBuffer來申請記憶體,記憶體可能已經申請,也可能未申請,未申請,則直接申請新記憶體,每個surface可以對應32塊記憶體:

status_t BufferQueueProducer::dequeueBuffer(int *outSlot,
        sp<android::Fence> *outFence, uint32_t width, uint32_t height,
        PixelFormat format, uint32_t usage) {
    ...
        sp<GraphicBuffer> graphicBuffer(mCore->mAllocator->createGraphicBuffer(
                width, height, format, usage,
                {mConsumerName.string(), mConsumerName.size()}, &error));複製程式碼

mCore其實就是上面的BufferQueueCore,mCore->mAllocator = new GraphicBufferAlloc(),最終會利用GraphicBufferAlloc物件分配共享記憶體:

sp<GraphicBuffer> GraphicBufferAlloc::createGraphicBuffer(uint32_t width,
        uint32_t height, PixelFormat format, uint32_t usage,
        std::string requestorName, status_t* error) {

    <!--直接new新建-->
    sp<GraphicBuffer> graphicBuffer(new GraphicBuffer(
            width, height, format, usage, std::move(requestorName)));
    status_t err = graphicBuffer->initCheck();
    return graphicBuffer;
}複製程式碼

從上面看到,直接new GraphicBuffer新建影象記憶體,

GraphicBuffer::GraphicBuffer(uint32_t inWidth, uint32_t inHeight,
        PixelFormat inFormat, uint32_t inUsage, std::string requestorName)
    : BASE(), mOwner(ownData), mBufferMapper(GraphicBufferMapper::get()),
      mInitCheck(NO_ERROR), mId(getUniqueId()), mGenerationNumber(0){
    ...
    handle = NULL;
    mInitCheck = initSize(inWidth, inHeight, inFormat, inUsage,
            std::move(requestorName));
}

status_t GraphicBuffer::initSize(uint32_t inWidth, uint32_t inHeight,
        PixelFormat inFormat, uint32_t inUsage, std::string requestorName)
{
    GraphicBufferAllocator& allocator = GraphicBufferAllocator::get();
    uint32_t outStride = 0;
    <!--請求分配記憶體-->
    status_t err = allocator.allocate(inWidth, inHeight, inFormat, inUsage,
            &handle, &outStride, mId, std::move(requestorName));
    if (err == NO_ERROR) {
        width = static_cast<int>(inWidth);
        height = static_cast<int>(inHeight);
        format = inFormat;
        usage = static_cast<int>(inUsage);
        stride = static_cast<int>(outStride);
    }
    return err;
}

 status_t GraphicBufferAllocator::allocate(uint32_t width, uint32_t height,
        PixelFormat format, uint32_t usage, buffer_handle_t* handle,
        uint32_t* stride, uint64_t graphicBufferId, std::string requestorName)
{
     ...
    auto descriptor = mDevice->createDescriptor();
    auto error = descriptor->setDimensions(width, height);
    error = descriptor->setFormat(static_cast<android_pixel_format_t>(format));
    error = descriptor->setProducerUsage(
            static_cast<gralloc1_producer_usage_t>(usage));
    error = descriptor->setConsumerUsage(
            static_cast<gralloc1_consumer_usage_t>(usage));
    <!--這裡的device就是抽象的硬體裝置-->
    error = mDevice->allocate(descriptor, graphicBufferId, handle);
    error = mDevice->getStride(*handle, stride);
    ...
    return NO_ERROR;
}複製程式碼

上面程式碼的mDevice就是利用hw_get_module及gralloc1_open獲取到的硬體抽象層device,hw_get_module裝載HAL模組,會載入相應的.so檔案gralloc.default.so,它實現位於 hardware/libhardware/modules/gralloc.cpp中,最後將device對映的函式操作載入進來。這裡我們關心的是allocate函式,先分析普通圖形緩衝區的分配,它最終會呼叫gralloc_alloc_buffer()利用匿名共享記憶體進行分配,之前的文章Android匿名共享記憶體(Ashmem)原理分析了Android是如何通過匿名共享記憶體進行通訊的,這裡就直接用了:

static int gralloc_alloc_buffer(alloc_device_t* dev,
        size_t size, int usage, buffer_handle_t* pHandle)
{
    int err = 0;
    int fd = -1;
    size = roundUpToPageSize(size);
    // 建立共享記憶體,並且設定名字跟size
    fd = ashmem_create_region("gralloc-buffer", size);
    if (err == 0) {
        private_handle_t* hnd = new private_handle_t(fd, size, 0);
        gralloc_module_t* module = reinterpret_cast<gralloc_module_t*>(
                dev->common.module);
         // 執行mmap,將記憶體對映到自己的程式
        err = mapBuffer(module, hnd);
        if (err == 0) {
            *pHandle = hnd;
        }
    }

    return err;
}複製程式碼

mapBuffer會進一步呼叫ashmem的驅動,在tmpfs新建檔案,同時開闢虛擬記憶體,

int mapBuffer(gralloc_module_t const* module,
            private_handle_t* hnd)
    {
        void* vaddr; 
        // vaddr有個毛用?
        return gralloc_map(module, hnd, &vaddr);
    }

static int gralloc_map(gralloc_module_t const* module,
        buffer_handle_t handle,
        void** vaddr)
{
    private_handle_t* hnd = (private_handle_t*)handle;
    if (!(hnd->flags & private_handle_t::PRIV_FLAGS_FRAMEBUFFER)) {
        size_t size = hnd->size;
        void* mappedAddress = mmap(0, size,
                PROT_READ|PROT_WRITE, MAP_SHARED, hnd->fd, 0);
        if (mappedAddress == MAP_FAILED) {
            return -errno;
        }
        hnd->base = intptr_t(mappedAddress) + hnd->offset;
    }
    *vaddr = (void*)hnd->base;
    return 0;
}複製程式碼

View繪製記憶體的傳遞

分配之後,會繼續利用BpGraphicBufferProducer的requestBuffer,申請將共享記憶體給對映到當前程式:

virtual status_t requestBuffer(int bufferIdx, sp<GraphicBuffer>* buf) {
    Parcel data, reply;
    data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
    data.writeInt32(bufferIdx);
    status_t result =remote()->transact(REQUEST_BUFFER, data, &reply);
    if (result != NO_ERROR) {
        return result;
    }
    bool nonNull = reply.readInt32();
    if (nonNull) {
        *buf = new GraphicBuffer();
        reply.read(**buf);
    }
    result = reply.readInt32();
    return result;
}複製程式碼

private_handle_t物件用來抽象圖形緩衝區,其中儲存著與共享記憶體對應tmpfs檔案的fd,GraphicBuffer物件會通過序列化,將這個fd會利用Binder通訊傳遞給App程式,APP端獲取到fd之後,便可以同mmap將共享記憶體對映到自己的程式空間,進而進行圖形繪製。等到APP端對GraphicBuffer的反序列化的時候,會將共享記憶體mmap到當前程式空間:

status_t Parcel::read(Flattenable& val) const  
{  
    // size  
    const size_t len = this->readInt32();  
    const size_t fd_count = this->readInt32();  
    // payload  
    void const* buf = this->readInplace(PAD_SIZE(len));  
    if (buf == NULL)  
        return BAD_VALUE;  
    int* fds = NULL;  
    if (fd_count) {  
        fds = new int[fd_count];  
    }  
    status_t err = NO_ERROR;  
    for (size_t i=0 ; i<fd_count && err==NO_ERROR ; i++) {  
        fds[i] = dup(this->readFileDescriptor());  
        if (fds[i] < 0) err = BAD_VALUE;  
    }  
    if (err == NO_ERROR) {  
        err = val.unflatten(buf, len, fds, fd_count);  
    }  
    if (fd_count) {  
        delete [] fds;  
    }  
    return err;  
}  複製程式碼

進而呼叫GraphicBuffer::unflatten:

status_t GraphicBuffer::unflatten(void const* buffer, size_t size,
        int fds[], size_t count)
{
   ...
    mOwner = ownHandle;
    <!--將共享記憶體對映當前記憶體空間-->
    if (handle != 0) {
        status_t err = mBufferMapper.registerBuffer(handle);
    }
    return NO_ERROR;
}複製程式碼

mBufferMapper.registerBuffer函式對應gralloc_register_buffer

struct private_module_t HAL_MODULE_INFO_SYM = {
    .base = {
        .common = {
            .tag = HARDWARE_MODULE_TAG,
            .version_major = 1,
            .version_minor = 0,
            .id = GRALLOC_HARDWARE_MODULE_ID,
            .name = "Graphics Memory Allocator Module",
            .author = "The Android Open Source Project",
            .methods = &gralloc_module_methods
        },
        .registerBuffer = gralloc_register_buffer,
        .unregisterBuffer = gralloc_unregister_buffer,
        .lock = gralloc_lock,
        .unlock = gralloc_unlock,
    },
    .framebuffer = 0,
    .flags = 0,
    .numBuffers = 0,
    .bufferMask = 0,
    .lock = PTHREAD_MUTEX_INITIALIZER,
    .currentBuffer = 0,
};複製程式碼

最後會呼叫gralloc_register_buffer,通過mmap真正將tmpfs檔案對映到程式空間:

static int gralloc_register_buffer(gralloc_module_t const* module,
                                   buffer_handle_t handle)
{
    ...
    if (cb->ashmemSize > 0 && cb->mappedPid != getpid()) {
        void *vaddr;
        <!--mmap-->
        int err = map_buffer(cb, &vaddr);
        cb->mappedPid = getpid();
    }

    return 0;
}複製程式碼

終於我們用到tmpfs中檔案對應的描述符fd0->cb->fd

static int map_buffer(cb_handle_t *cb, void **vaddr)
{
    if (cb->fd < 0 || cb->ashmemSize <= 0) {
        return -EINVAL;
    }

    void *addr = mmap(0, cb->ashmemSize, PROT_READ | PROT_WRITE,
                      MAP_SHARED, cb->fd, 0);
    cb->ashmemBase = intptr_t(addr);
    cb->ashmemBasePid = getpid();
    *vaddr = addr;
    return 0;
}複製程式碼

到這裡記憶體傳遞成功,App端就可以應用這塊記憶體進行圖形繪製了。

View繪製記憶體的使用

關於記憶體的使用,我們回到之前的Surface lock函式,記憶體經過反序列化,拿到記憶體地址後,會封裝一個ANativeWindow_Buffer返回給上層呼叫:

status_t Surface::lock(
        ANativeWindow_Buffer* outBuffer, ARect* inOutDirtyBounds)
{
     ...
        void* vaddr;
        <!--lock獲取地址-->
        status_t res = backBuffer->lock(
                GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
                newDirtyRegion.bounds(), &vaddr);

        if (res != 0) {
            err = INVALID_OPERATION;
        } else {
            mLockedBuffer = backBuffer;
            outBuffer->width  = backBuffer->width;
            outBuffer->height = backBuffer->height;
            outBuffer->stride = backBuffer->stride;
            outBuffer->format = backBuffer->format;

                <!--關鍵點 設定虛擬記憶體的地址-->
            outBuffer->bits   = vaddr;
        }
    }
    return err;
}複製程式碼

ANativeWindow_Buffer的資料結構如下,其中bits欄位與虛擬記憶體地址對應,

typedef struct ANativeWindow_Buffer {
    // The number of pixels that are show horizontally.
    int32_t width;

    // The number of pixels that are shown vertically.
    int32_t height;

    // The number of *pixels* that a line in the buffer takes in
    // memory.  This may be >= width.
    int32_t stride;

    // The format of the buffer.  One of WINDOW_FORMAT_*
    int32_t format;

    // The actual bits.
    void* bits;

    // Do not touch.
    uint32_t reserved[6];
} ANativeWindow_Buffer;複製程式碼

如何使用,看下Canvas的draw

static void nativeLockCanvas(JNIEnv* env, jclass clazz,
        jint nativeObject, jobject canvasObj, jobject dirtyRectObj) {
    sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject));
    ...
    status_t err = surface->lock(&outBuffer, &dirtyBounds);
    ...
    <!--SkBitmap-->
    SkBitmap bitmap;
    ssize_t bpr = outBuffer.stride * bytesPerPixel(outBuffer.format);
    <!--為SkBitmap填充配置-->
    bitmap.setConfig(convertPixelFormat(outBuffer.format), outBuffer.width, outBuffer.height, bpr);
    <!--為SkBitmap填充格式-->
    if (outBuffer.format == PIXEL_FORMAT_RGBX_8888) {
        bitmap.setIsOpaque(true);
    }
    <!--為SkBitmap填充記憶體-->
    if (outBuffer.width > 0 && outBuffer.height > 0) {
        bitmap.setPixels(outBuffer.bits);
    } else {
        // be safe with an empty bitmap.
        bitmap.setPixels(NULL);
    }

    <!--建立native SkCanvas-->
    SkCanvas* nativeCanvas = SkNEW_ARGS(SkCanvas, (bitmap));
    swapCanvasPtr(env, canvasObj, nativeCanvas);
   ...
}複製程式碼

對於2D繪圖,會用skia庫會填充Bitmap對應的共享記憶體,如此即可完成繪製,本文不深入Skia庫,有興趣自行分析。繪製完成後,通過unlock直接通知SurfaceFlinger服務進行圖層合成。

總結

Android View的繪製建立匿名共享記憶體的基礎上,APP端與SurfaceFlinger通過共享記憶體的方式避免了View檢視資料的拷貝,提高了系統同的檢視處理能力。

作者:看書的小蝸牛
原文連結:Android視窗管理分析(4):Android View繪製記憶體的分配、傳遞、使用
僅供參考,歡迎指正

相關文章