8.4.5 lockCanvas和unlockCanvasAndPost分析

这一节,分析精简流程中的最后两个函数lockCanvas和unlockCanvasAndPost。

1.lockCanvas分析

根据前文的分析可知,UI在绘制前都需要通过lockCanvas得到一块存储空间,也就是所说的BackBuffer。这个过程中最终会调用Surface的lock函数。其代码如下所示:


[—>Surface.cpp]

status_t Surface:lock(SurfaceInfoother,RegiondirtyIn,bool blocking)

{

//传入的参数中,other用来接收一些返回信息,dirtyIn表示需要重绘的区域。

……

if(mApiLock.tryLock()!=NO_ERROR){//多线程的情况下要锁住。

……

return WOULD_BLOCK;

}

//设置usage标志,这个标志在GraphicBuffer分配缓冲时有指导作用。

setUsage(GRALLOC_USAGE_SW_READ_OFTEN|GRALLOC_USAGE_SW_WRITE_OFTEN);

//定义一个GraphicBuffer,名字就叫backBuffer。

sp<GraphicBuffer>backBuffer;

//①还记得我们说的2个元素的缓冲队列吗?下面的dequeueBuffer将取出一个空闲缓冲。

status_t err=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<GraphicBuffer>&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();

return err;

}


在上面的代码中,列出了三个关键点:

调用dequeueBuffer得到一个空闲缓冲,也可以叫空闲缓冲出队。

调用lockBuffer。

调用copyBlt函数,把frontBuffer数据拷贝到backBuffer中,这是为什么?

来分析这三个关键点。

(1)dequeueBuffer分析

dequeueBuffer的目的很简单,就是选取一个空闲的GraphicBuffer,其代码如下所示:


[—>Surface.cpp]

status_t Surface:dequeueBuffer(sp<GraphicBuffer>*buffer){

android_native_buffer_t*out;

status_t err=dequeueBuffer(&out);//调用另外一个dequeueBuffer。

if(err==NO_ERROR){

*buffer=GraphicBuffer:getSelf(out);

}

return err;

}


这其中又调用了另外一个dequeueBuffer函数。它的代码如下所示:


[—>Surface.cpp]

int Surface:dequeueBuffer(android_native_buffer_t**buffer)

{

sp<SurfaceComposerClient>client(getClient());

//①调用SharedBufferClient的dequeue函数,它返回当前空闲的缓冲号。

ssize_t bufIdx=mSharedBufferClient->dequeue();

const uint32_t usage(getUsage());

/*

mBuffers就是我们前面在Surface创建中介绍的那个二元sp<GraphicBuffer>数组。

这里定义的backBuffer是一个引用类型,也就是说如果修改backBuffer的信息,就相当于修改了mBuffers[bufIdx]。

*/

const sp<GraphicBuffer>&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,int usage)

{

sp<ISurface>s(mSurface);

status_t err=NO_MEMORY;

//注意这个currentBuffer也被定义为引用类型。

sp<GraphicBuffer>&currentBuffer(mBuffers[index]);

//终于用上了ISurface对象,调用它的requestBuffer得到指定索引index的Buffer。

sp<GraphicBuffer>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<GraphicBuffer>Layer:requestBuffer(int index,int usage)

{

sp<GraphicBuffer>buffer;

sp<Client>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();

}

const uint32_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();

}

}

return buffer;//返回

}


不管怎样,此时跨进程的这个requestBuffer返回的GraphicBuffer,已经和一块物理存储绑定到一起了。所以dequeueBuffer顺利返回了它所需的东西。接下来则需调用lockBuffer。

(2)lockBuffer分析

lockBuffer的代码如下所示:


[—>Surface.cpp]

int Surface:lockBuffer(android_native_buffer_t*buffer)

{

sp<SurfaceComposerClient>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);

return err;

}

注意,给waitForCondition函数传递的是一个LockCondition类型的对象,前面所说的函数对象的作用将在这里见识到,先看waitForCondition函数,代码如下所示:

[—>SharedBufferStack.h]

template<typename T>//这是一个模板函数。

status_t SharedBufferBase:waitForCondition(T condition)

{

const SharedBufferStack&stack(*mSharedStack);

SharedClient&client(*mSharedClient);

const nsecs_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]

bool SharedBufferClient: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(SurfaceInfoother,RegiondirtyIn,bool blocking)

{

……

const sp<GraphicBuffer>&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中。后续的绘画只要更新脏区域就可以了,这会节约不少资源。

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;

return err;

}


来看queueBuffer调用,代码如下所示:


[—>Surface.cpp]

int Surface:queueBuffer(android_native_buffer_t*buffer)

{

sp<SurfaceComposerClient>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();

}

return err;

}


这里与读写控制有关的是queue函数,其代码如下所示:


[—>SharedBufferStack.cpp]

status_t SharedBufferClient:queue(int buf)

{

//QueueUpdate也是一个函数对象。

QueueUpdate update(this);

//调用updateCondition函数。

status_t err=updateCondition(update);

SharedBufferStack&stack(*mSharedStack);

const nsecs_t now=systemTime(SYSTEM_TIME_THREAD);

stack.stats.totalTime=ns2us(now-mDequeueTime[buf]);

return err;

}


这个updateCondition函数的代码如下所示:


[—>SharedBufferStack.h]

template<typename T>

status_t SharedBufferBase:updateCondition(T update){

SharedClient&client(*mSharedClient);

Mutex:Autolock_l(client.lock);

ssize_t result=update();//调用update对象的()函数。

client.cv.broadcast();//触发同步对象。

return result;

}


updateCondition函数和前面介绍的waitForCondition函数一样,使用的都是函数对象。queue操作使用的是QueueUpdate类,关于它的故事,将在本章拓展内容中讨论。

3.关于lockCanvas和unlockCanvasAndPost的总结

总结一下lockCanvas和unlockCanvasAndPost这两个函数的工作流程,用图8-20表示:

8.4.5 lockCanvas和unlockCanvasAndPost分析 - 图1

图 8-20 lockCanvas和unlockCanvasAndPost的流程总结