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流程总结
';