前文Android匿名共享內存(Ashmem)原理分析了匿名共享內存,它最主要的做用就是View視圖繪製,Android視圖是按照一幀一幀顯示到屏幕的,而每一幀都會佔用必定的存儲空間,經過Ashmem機制APP與SurfaceFlinger共享繪圖數據,提升圖形處理性能,本文就看Android是怎麼利用Ashmem分配及繪製的:android
前文Window添加流程中描述了:在添加窗口的時候,WMS會爲APP分配一個WindowState,以標識當前窗口並用於窗口管理,同時向SurfaceFlinger端請求分配Layer抽象圖層,在SurfaceFlinger分配Layer的時候建立了兩個比較關鍵的Binder對象,用於填充WMS端Surface,一個是sp handle:是每一個窗口標識的句柄,未來WMS同SurfaceFlinger通訊的時候方便找到對應的圖層。另外一個是sp gbp :共享內存分配的關鍵對象,同時兼具Binder通訊的功能,用來傳遞指令及共享內存的句柄,注意,這裏只是抽象建立了對象,並未真正分配每一幀的內存,內存的分配要等到真正繪製的時候纔會申請,首先看一下分配流程:canvas
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,bash
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端是app
class BpGraphicBufferProducer : public BpInterface<IGraphicBufferProducer>{}複製代碼
IGraphicBufferProducer Binder實體在SurfaceFlinger中建立後,打包到Surface對象,並經過binder通訊傳遞給APP端,APP段經過反序列化將其恢復出來,以下:async
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中的drawide
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;
}複製代碼
分配以後,會繼續利用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端就能夠應用這塊內存進行圖形繪製了。
關於內存的使用,咱們回到以前的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繪製內存的分配、傳遞、使用
僅供參考,歡迎指正