8.4.5 lockCanvas和unlockCanvasAndPost分析

最后更新于:2022-04-02 05:52:36

这一节,分析精简流程中的最后两个函数lockCanvas和unlockCanvasAndPost。 1. lockCanvas分析 据前文分析可知,UI在绘制前都需要通过lockCanvas得到一块存储空间,也就是所说的BackBuffer。这个过程中最终会调用Surface的lock函数。其代码如下所示: **Surface.cpp** ~~~ status_t Surface::lock(SurfaceInfo* other,Region* dirtyIn, bool blocking) { //传入的参数中,other用来接收一些返回信息,dirtyIn表示需要重绘的区域 ...... if (mApiLock.tryLock() != NO_ERROR) {//多线程的情况下要锁住 ...... returnWOULD_BLOCK; } //设置usage标志,这个标志在GraphicBuffer分配缓冲时有指导作用 setUsage(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN); //定义一个GraphicBuffer,名字就叫backBuffer。 spbackBuffer; //①还记得我们说的2个元素的缓冲队列吗?下面的dequeueBuffer将取出一个空闲缓冲 status_terr = dequeueBuffer(&backBuffer); if (err== NO_ERROR) { //② 锁住这块buffer err = lockBuffer(backBuffer.get()); if(err == NO_ERROR) { const Rect bounds(backBuffer->width, backBuffer->height); Region scratch(bounds); Region& newDirtyRegion(dirtyIn ? *dirtyIn : scratch); ...... //mPostedBuffer是上一次绘画时使用的Buffer,也就是现在的frontBuffer const sp& frontBuffer(mPostedBuffer); if (frontBuffer !=0 && backBuffer->width ==frontBuffer->width && backBuffer->height == frontBuffer->height && !(mFlags & ISurfaceComposer::eDestroyBackbuffer)) { const Region copyback(mOldDirtyRegion.subtract(newDirtyRegion)); if (!copyback.isEmpty() && frontBuffer!=0) { /③把frontBuffer中的数据拷贝到BackBuffer中,这是为什么? copyBlt(backBuffer,frontBuffer, copyback); } } mDirtyRegion = newDirtyRegion; mOldDirtyRegion = newDirtyRegion; void* vaddr; //调用GraphicBuffer的lock得到一块内存,内存地址被赋值给了vaddr, //后续的作画将在这块内存上展开 status_t res = backBuffer->lock( GRALLOC_USAGE_SW_READ_OFTEN |GRALLOC_USAGE_SW_WRITE_OFTEN, newDirtyRegion.bounds(),&vaddr); mLockedBuffer = backBuffer; //other用来接收一些信息。 other->w =backBuffer->width; //宽度信息 other->h =backBuffer->height; other->s =backBuffer->stride; other->usage =backBuffer->usage; other->format = backBuffer->format; other->bits = vaddr; //最重要的是这个内存地址 } } mApiLock.unlock(); returnerr; } ~~~ 在上面的代码中,列出了三个关键点: - 调用dequeueBuffer得到一个空闲缓冲,也可以叫空闲缓冲出队。 - 调用lockBuffer。 - 调用copyBlt函数,把frontBuffer数据拷贝到backBuffer中,这是为什么? 来分析这三个关键点。 (1)dequeueBuffer的分析 dequeueBuffer的目的很简单,就是选取一个空闲的GraphicBuffer,其代码如下所示: **Surface.cpp** ~~~ status_tSurface::dequeueBuffer(sp* buffer) { android_native_buffer_t* out; status_t err = dequeueBuffer(&out);//调用另外一个dequeueBuffer if(err == NO_ERROR) { *buffer = GraphicBuffer::getSelf(out); } returnerr; } ~~~ 这其中又调用了另外一个dequeueBuffer函数。它的代码如下所示: **Surface.cpp** ~~~ intSurface::dequeueBuffer(android_native_buffer_t** buffer) { sp client(getClient()); //①调用SharedBufferClient的dequeue函数,它返回当前空闲的缓冲号 ssize_tbufIdx = mSharedBufferClient->dequeue(); const uint32_t usage(getUsage()); /* mBuffers就是我们前面在Surface创建中介绍的那个二元sp数组。 这里定义的backBuffer是一个引用类型,也就是说如果修改backBuffer的信息, 就相当于修改了mBuffers[bufIdx] */ const sp&backBuffer(mBuffers[bufIdx]); //mBuffers定义的GraphicBuffer使用的也是无参构造函数,所以此时还没有真实的存储被创建 if(backBuffer == 0 || //第一次进来满足backBuffer为空这个条件 ((uint32_t(backBuffer->usage) & usage) != usage) || mSharedBufferClient->needNewBuffer(bufIdx)) { //调用getBufferLocked,需要进去看看。 err = getBufferLocked(bufIdx, usage); if(err == NO_ERROR) { mWidth =uint32_t(backBuffer->width); mHeight = uint32_t(backBuffer->height); } } ...... } ~~~ 上面列出了一个关键点,就是SharedBufferClient的dequeue函数,暂且记住这个调用,后面会有单独章节分析生产/消费步调控制。先看getBufferLocked函数,其代码如下所示: **Surface.cpp** ~~~ tatus_t Surface::getBufferLocked(int index, intusage) { sp s(mSurface); status_t err = NO_MEMORY; //注意这个currentBuffer也被定义为引用类型 sp¤tBuffer(mBuffers[index]); //终于用上了ISurface对象,调用它的requestBuffer得到指定索引index的Buffer sp buffer =s->requestBuffer(index, usage); if (buffer != 0) { err = mSharedBufferClient->getStatus(); if(!err && buffer->handle != NULL) { //getBufferMapper返回GraphicBufferMapper对象 //调用它的registerBuffer干什么?这个问题我们在8.4.7节回答 err = getBufferMapper().registerBuffer(buffer->handle); if (err == NO_ERROR) { //把requestBuffer得到的值赋给currentBuffer,由于currentBuffer是引用类型, //实际上相当于mBuffers[index]=buffer currentBuffer = buffer; //设置currentBuffer的编号 currentBuffer->setIndex(index); mNeedFullUpdate = true; } }else { err = err<0 ? err : NO_MEMORY; } return err; } ~~~ 至此,getBufferLocked的目的,已比较清晰了: - 调用ISurface的requestBuffer得到一个GraphicBuffer对象,这个GraphicBuffer对象被设置到本地的mBuffers数组中。看来Surface定义的这两个GraphicBuffer和Layer定义的两个GraphicBuffer是有联系的,所以图8-18中只画了两个GraphicBuffer。 我们已经知道,ISurface的Bn端实际上是定义在Layer.类中的SurfaceLayer,下面来看它实现的requestBuffer。由于SurfaceLayer是Layer的内部类,它的工作最终都会交给Layer来处理,所以这里可直接看Layer的requestBuffer函数: **Layer.cpp** ~~~ sp Layer::requestBuffer(intindex, int usage) { sp buffer; sp ourClient(client.promote()); //lcblk就是那个SharedBufferServer对象,下面这个调用确保index号GraphicBuffer //没有被SF当做FrontBuffer使用。 status_t err = lcblk->assertReallocate(index); ...... if(err != NO_ERROR) { return buffer; } uint32_t w, h; { Mutex::Autolock _l(mLock); w= mWidth; h= mHeight; /* mBuffers是SF端创建的一个二元数组,这里取出第index个元素,之前说过, mBuffers使用的也是GraphicBuffer的无参构造函数,所以此时也没有真实存储被创建。 */ buffer = mBuffers[index]; mBuffers[index].clear(); } constuint32_t effectiveUsage = getEffectiveUsage(usage); if(buffer!=0 && buffer->getStrongCount() == 1) { //①分配物理存储,后面会分析这个。 err = buffer->reallocate(w, h, mFormat, effectiveUsage); } else{ buffer.clear(); //使用GraphicBuffer的有参构造,这也使得物理存储被分配 buffer = new GraphicBuffer(w, h, mFormat, effectiveUsage); err = buffer->initCheck(); } ...... if(err == NO_ERROR && buffer->handle != 0) { Mutex::Autolock _l(mLock); if(mWidth && mHeight) { mBuffers[index] = buffer; mTextures[index].dirty = true; }else { buffer.clear(); } } returnbuffer;//返回 } ~~~ 不管怎样,此时跨进程的这个requestBuffer返回的GraphicBuffer,已经和一块物理存储绑定到一起了。所以dequeueBuffer顺利返回了它所需的东西。接下来则需调用lockBuffer。 (2)lockBuffer的分析 lockBuffer的代码如下所示: **Surface.cpp** ~~~ int Surface::lockBuffer(android_native_buffer_t*buffer) { sp client(getClient()); status_t err = validate(); int32_t bufIdx = GraphicBuffer::getSelf(buffer)->getIndex(); err =mSharedBufferClient->lock(bufIdx); //调用SharedBufferClient的lock return err; } ~~~ 来看这个lock函数: **SharedBufferStack.cpp** ~~~ status_t SharedBufferClient::lock(int buf) { LockCondition condition(this, buf);//这个buf是BackBuffer的索引号 status_t err = waitForCondition(condition); returnerr; } ~~~ * * * * * **注意**,给waitForCondition函数传递的是一个LockCondition类型的对象,前面所说的函数对象的作用将在这里见识到,先看waitForCondition函数: * * * * * **SharedBufferStack.h** ~~~ template //这是一个模板函数 status_t SharedBufferBase::waitForCondition(Tcondition) { constSharedBufferStack& stack( *mSharedStack ); SharedClient& client( *mSharedClient ); constnsecs_t TIMEOUT = s2ns(1); Mutex::Autolock _l(client.lock); while((condition()==false) && //注意这个condition()的用法 (stack.identity == mIdentity) && (stack.status == NO_ERROR)) { status_t err = client.cv.waitRelative(client.lock, TIMEOUT); if(CC_UNLIKELY(err != NO_ERROR)) { if (err == TIMED_OUT) { if (condition()) {//注意这个:condition(),condition是一个对象 break; } else { } } else { return err; } } } return(stack.identity != mIdentity) ? status_t(BAD_INDEX) : stack.status; } ~~~ waitForCondition函数比较简单,就是等待一个条件为真,这个条件是否满足由condition()这条语句来判断。但这个condition不是一个函数,而是一个对象,这又是怎么回事? 这就是Funcition Object(函数对象)的概念。函数对象的本质是一个对象,不过是重载了操作符(),这和重载操作符+、-等没什么区别。可以把它当作是一个函数来看待。 为什么需要函数对象呢?因为对象可以保存信息,所以调用这个对象的()函数就可以利用这个对象的信息了。 来看condition对象的()函数。刚才传进来的是LockCondition,它的()定义如下: **SharedBufferStack.cpp** ~~~ boolSharedBufferClient::LockCondition::operator()() { //stack、buf等都是这个对象的内部成员,这个对象的目的就是根据读写位置判断这个buffer是 //否空闲。 return(buf != stack.head || (stack.queued > 0 && stack.inUse != buf)); } ~~~ SharedBufferStack的读写控制,比Audio中的环形缓冲看起来要简单,实际上它却比较复杂。本章会在扩展部分进行分析。这里给读者准备一个问题,也是我之前百思不得其解的问题: 既然已经调用dequeue得到了一个空闲缓冲,为什么这里还要lock呢? (3)拷贝旧数据 在第三个关键点中,可看到这样的代码: **Surface.cpp** ~~~ status_t Surface::lock(SurfaceInfo* other,Region* dirtyIn, bool blocking) { ...... const sp& frontBuffer(mPostedBuffer); if (frontBuffer !=0 && backBuffer->width ==frontBuffer->width && backBuffer->height == frontBuffer->height && !(mFlags & ISurfaceComposer::eDestroyBackbuffer)) { const Region copyback(mOldDirtyRegion.subtract(newDirtyRegion)); if (!copyback.isEmpty() && frontBuffer!=0) { //③把frontBuffer中的数据拷贝到BackBuffer中,这是为什么? copyBlt(backBuffer,frontBuffer, copyback); } } ...... } ~~~ 上面这段代码所解决的,其实是下面这个问题: 在大部分情况下,UI只有一小部分会发生变化(例如一个按钮被按下去,导致颜色发生变化),这一小部分UI只对应整个GraphicBuffer中的一小块存储(就是在前面代码中见到的dirtyRegion),如果整块存储都更新,则会极大地浪费资源。怎么办? 这就需要将变化的图像和没有发生变化的图像进行叠加。上一次绘制的信息保存在mPostedBuffer中,而这个mPostedBuffer则要在unLockAndPost函数中设置。这里将根据需要,把mPostedBuffer中的旧数据拷贝到BackBuffer中。后续的绘画只要更新脏区域就可以了,这会节约不少资源。 OK,lockCanvas返回后,应用层将在这块画布上尽情作画。假设现在已经在BackBuffer上绘制好了图像,下面就要通过unlockCanvasAndPost进行后续工作了。一起来看。 2. unlockCanvasAndPost的分析 进入精简流程的最后一步,就是unlockCanvasAndPost函数,它的代码如下所示: **Surface.cpp** ~~~ status_t Surface::unlockAndPost() { //调用GraphicBuffer的unlock函数 status_t err = mLockedBuffer->unlock(); //get返回这个GraphicBuffer的编号,queueBuffer将含有新数据的缓冲加入队中。 err =queueBuffer(mLockedBuffer.get()); mPostedBuffer = mLockedBuffer; //保存这个BackBuffer为mPostedBuffer mLockedBuffer = 0; returnerr; } ~~~ 来看queueBuffer调用,代码如下所示: **Surface.cpp** ~~~ intSurface::queueBuffer(android_native_buffer_t* buffer) { sp client(getClient()); int32_t bufIdx =GraphicBuffer::getSelf(buffer)->getIndex(); //设置脏Region mSharedBufferClient->setDirtyRegion(bufIdx,mDirtyRegion); //更新写位置。 err =mSharedBufferClient->queue(bufIdx); if (err== NO_ERROR) { //client是BpSurfaceFlinger,调用它的signalServer,这样SF就知道新数据准备好了 client->signalServer(); } returnerr; } ~~~ 这里,与读写控制有关的是queue函数,其代码如下所示: **SharedBufferStack.cpp** ~~~ status_t SharedBufferClient::queue(int buf) { //QueueUpdate也是一个函数对象 QueueUpdate update(this); //调用updateCondition函数。 status_t err = updateCondition( update ); SharedBufferStack& stack( *mSharedStack ); constnsecs_t now = systemTime(SYSTEM_TIME_THREAD); stack.stats.totalTime = ns2us(now - mDequeueTime[buf]); returnerr; } ~~~ 这个updateCondition函数的代码如下所示: **SharedBufferStack.h** ~~~ template status_t SharedBufferBase::updateCondition(Tupdate) { SharedClient& client( *mSharedClient ); Mutex::Autolock _l(client.lock); ssize_t result = update();//调用update对象的()函数 client.cv.broadcast(); //触发同步对象 returnresult; } ~~~ updateCondition函数和前面介绍的waitForCondition函数一样,都是使用的函数对象。queue操作使用的是QueueUpdate类,关于它的故事,将在拓展部分讨论。 3. lockCanvas和unlockCanvasAndPost的总结 总结一下lockCanvas和unlockCanvasAndPost这两个函数的工作流程,用图8-20表示: :-: ![](http://img.blog.csdn.net/20150802162842059?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 图8-20 lockCanvas和unlockCanvasAndPost流程总结
';