출처 : http://luxtella.tistory.com/entry/Android-ViewgetDrawingCache-%EB%B6%84%EC%84%9D

getDrawingCache가 어떻게 그림을 복사하는지 내부 mechanism을 살펴보자

우선 사용예제는
 webView.setDrawingCacheEnabled(true);
 webView.setDrawingCacheBackgroundColor(Color.WHITE);
 webView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_AUTO);
 webView.getDrawingCache(true)
결론은 c단에서 skia bitmap이 실제로 그려지는 device의 actual pixel memory에 바로 붙어서 그 시점의 memory를 카피하는 것이라 생각된다. 확신을 못하는 이유는 skia쪽 분석을 하다 막혔기 때문이다. 추후에 더 완벽한 분석을 하여 보강하겠다.


frameworks/base/core/java/android/view/View.java 를 보면 getDrawingCache() 가 buildDrawingCache를 콜하면서 일을 한다. buildDrawingCache는 자기가 전에 저장해 놓은 bitmap이 갱신이 않되었으면 그것을 쓰고 아니면 다음을 수행한다.
bitmap = Bitmap.createBitmap(width, height, quality);
이 bitmap을 reference에 저장하여(?) getDrawingCache()로 불러쓴다.
mDrawingCache = new SoftReference<Bitmap>(bitmap);


진짜 일을 하는 놈이 Bitmap.createBitmap 인걸로 밝혀졌으니 그놈은 무엇을 하는지 알아보자
frameworks/base/graphics/java/android/graphics/Bitmap.java 를 살펴보면
public static Bitmap createBitmap(int width, int height, Config config) {
    Bitmap bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true);
    bm.eraseColor(0);    // start with black/transparent pixels
    return bm;
}
jni로 c 함수를 콜하는것을 알수 있다.
frameworks/base/core/jni/android/graphics/Bitmap.cpp를 살펴보자.
 static JNINativeMethod gBitmapMethods[] = {^M
     {   "nativeCreate",             "([IIIIIIZ)Landroid/graphics/Bitmap;",^M
         (void*)Bitmap_creator },^M
nativeCreate의 구현은 Bitmap_creator이다.
이런 안쪽 구현은 Skia로 해놨군.. 잘 모르니 이제부터 설명은 틀릴가능성이 농후하다....
 static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors,^M
                               int offset, int stride, int width, int height,^M
                               SkBitmap::Config config, jboolean isMutable) {^M
//위에서 jColors는 null, offset은 0으로 넘어왔다는것을 기억하자
     if (width <= 0 || height <= 0) {^M
         doThrowIAE(env, "width and height must be > 0");^M
         return NULL;^M
     }^M
 ^M
     if (NULL != jColors) {^M
         size_t n = env->GetArrayLength(jColors);^M
         if (n < SkAbs32(stride) * (size_t)height) {^M
             doThrowAIOOBE(env);^M
             return NULL;^M
         }^M
     }^M
 ^M
     SkBitmap bitmap;^M
 ^M
     bitmap.setConfig(config, width, height);^M
//가장 중요한 부분. bitmap이 actual pixel memory에 access할 수 있게 셋팅하는 부분이다. 부차적으로 bitmap에 메모리 alloc하고 pixel환경(?)을 잡아주는것 같다. VM이 그 메모리를 관리(?)할수 있도록 셋팅해주는것 같은데... 
     if (!GraphicsJNI::setJavaPixelRef(env, &bitmap, NULL, true)) {^M
         return NULL;^M
     }^M
 ^M
//pixel color(?)를 잡아주는것 같은데 우리는  null이 넘어왔으므로 pass 
     if (jColors != NULL) {^M
         GraphicsJNI::SetPixels(env, jColors, offset, stride,^M
                                0, 0, width, height, bitmap);^M
     }^M
 ^M
//결국 return에서 일을 하는 것이군.. SkBitmap는 메모리만 다시 잡아주는듯...
     return GraphicsJNI::createBitmap(env, new SkBitmap(bitmap), isMutable,^M
                                      NULL);^M
 }^M
이번글에서 가장 중요한 GraphicsJNI::setJavaPixelRef를 보자
frameworks/base/core/jni/android/graphics/Graphics.cpp
augments가 ctable=NULL, reportSizeToVM = true로 넘어왔음을 기억하자
bool GraphicsJNI::setJavaPixelRef(JNIEnv* env, SkBitmap* bitmap,
                                  SkColorTable* ctable, bool reportSizeToVM) {
    Sk64 size64 = bitmap->getSize64();
    if (size64.isNeg() || !size64.is32()) {
        doThrow(env, "java/lang/IllegalArgumentException",
                     "bitmap size exceeds 32bits");
        return false;
    }

    size_t size = size64.get32();
    jlong jsize = size;  // the VM wants longs for the size
    if (reportSizeToVM) {
        //    SkDebugf("-------------- inform VM we've allocated %d bytes\n", size);
//VM에 메모리 관련 뭔가를 하는것 같은데 이번글과 긴밀하게 관련 없는것 같으니 패스
        bool r = env->CallBooleanMethod(gVMRuntime_singleton,
                                    gVMRuntime_trackExternalAllocationMethodID,
                                    jsize);
        if (GraphicsJNI::hasException(env)) {
            return false;
        }
        if (!r) {
            LOGE("VM won't let us allocate %zd bytes\n", size);
            doThrowOOME(env, "bitmap size exceeds VM budget");
            return false;
        }
    }
    // call the version of malloc that returns null on failure
//actual pixel memory에서 bitmap을 복사할 storage 공간을 잡는다.    
void* addr = sk_malloc_flags(size, 0);
    if (NULL == addr) {
        if (reportSizeToVM) {
            //        SkDebugf("-------------- inform VM we're releasing %d bytes which we couldn't allocate\n", size);
            // we didn't actually allocate it, so inform the VM
            env->CallVoidMethod(gVMRuntime_singleton,
                                 gVMRuntime_trackExternalFreeMethodID,
                                 jsize);
            if (!GraphicsJNI::hasException(env)) {
                doThrowOOME(env, "bitmap size too large for malloc");
            }
        }
        return false;
    }
//SkMallocPixel로 이미지를 가져올 storage인 addr을 감싼다. 내부적으로 어떻게 하는지는 모르겠지만 화면에 그려질 pixel memory를 addr에 복사하는것 같다. reportSizeToVM이 true이므로 AndroidPixelRef실행. 하지만 AndroidPixelRef의 구현은 VM이 정상인지(?) 체크만 하고 다시 SKMallocPixelRef를 콜한다. SkMallocPixelRef:SkPixelRef 관계이고 void* fStorage를 추가적으로 관리한다. 아무래도 내부적으로 어떻게인지는 모르겠지만 화면에 그려질 pixel memory를 fStorage에 카피하는것 같다.
    SkPixelRef* pr = reportSizeToVM ?
                        new AndroidPixelRef(env, addr, size, ctable) :
                        new SkMallocPixelRef(addr, size, ctable);
//SkPixelRef 을 최종적으로 그림을 담을 bitmap의 fPixelRef에 pr을 설정하고 fPixels에는 pr.pixels()를 통해 PixelRef의 void* fPixels를 지정한다.
    bitmap->setPixelRef(pr)->unref();
    // since we're already allocated, we lockPixels right away
    // HeapAllocator behaves this way too
//하일라이트다. actual pixel memory를 카피한다(어떻게인지는 모르겠음). SkBitmap.lockPixel() -> SkPixelRef.lockPixel() -> SkPixelRef.onLockPixels -> SkMallocPixelRef.onLockPixels 를 연쇄적으로 콜한다. SkMallocPixelRef.fStorage가 SkPixelRef.fPixels에 카피되고 그것이 다시 SkBitmap.fPixels에 카피된다.
  bitmap->lockPixels();
     return true;
 }
결국 그림을 복사하는것은 SkPixelRef가 담당한다. SkPixelRef이 이 포스트에 결론이다. 근데 mechanism을 잘 모르겠다. http://skia.googlecode.com/svn/trunk/docs/html/index.html 를 보면 

This class is the smart container for pixel memory, and is used with SkBitmap. A pixelref is installed into a bitmap, and then the bitmap can access the actual pixel memory by calling lockPixels/unlockPixels.

This class can be shared/accessed between multiple threads.

라고 설명을 한다. 즉 lockPixels이 그림을 복사해 준다는 것인데..

external/skia/src/core/SkBitmap.cpp 를 보면 과연 SkMallocPixelRef는 addr을 감싸기만 한다는것 을 알 수 있다.
SkMallocPixelRef::SkMallocPixelRef(void* storage, size_t size,
                                   SkColorTable* ctable) {
    SkASSERT(storage);
    fStorage = storage;
    fSize = size;
    fCTable = ctable;
    ctable->safeRef();
}      
가장 중요한 lockPixels를 분석하자
external/skia/src/core/SkBitmap.cpp
void SkBitmap::lockPixels() const {
    if (NULL != fPixelRef && 1 == ++fPixelLockCount) {
//SkPixelRef의 lockPixel을 콜한다.
        fPixelRef->lockPixels();
        this->updatePixelsFromRef();
    }
    SkDEBUGCODE(this->validate();)
}  

SkPixelRef의 lockPixels을 다시 부른다.
void SkPixelRef::lockPixels() { 
    SkAutoMutexAcquire  ac(*fMutex);
         
    if (1 == ++fLockCount) {
        fPixels = this->onLockPixels(&fColorTable);
    }
}   
SkPixelRef의 onLockPixels는 virtual이므로 자식인 SkMallocPixelRef의 onLockPixels를 보면
void* SkMallocPixelRef::onLockPixels(SkColorTable** ct) {
    *ct = fCTable;
    return fStorage;
}  
단순히 fStorage를 반환한다... 이럴수가 SkMallocPixelRef, SkBitmap, SkPixelRef사이에서 변수를 주고받기만 하다 끝나버렸다.  어디서 화면에 그려질 pixel memory를 가져오는지 알수가 없다. 다만 최종 소스는 fStorage임을 알 수 있을 뿐이다...


그러므로 frameworks/base/core/jni/android/graphics/Graphics.cpp 로 고고씽
변수개수가 약간 다른데 왜 그런지는 모르겠고,  결과인 bitmap을 jni를 통해 다시 Java로 돌려준다.

gBitmap_class는 android/graphics/Bitmap Class이고 gBitmap_constructorMethodID 는 생성자이다.
jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, bool isMutable, jbyteArray ninepatch, int density)
{
    SkASSERT(bitmap != NULL);
    SkASSERT(NULL != bitmap->pixelRef());

    jobject obj = env->AllocObject(gBitmap_class);
    if (obj) {
        env->CallVoidMethod(obj, gBitmap_constructorMethodID,
                            (jint)bitmap, isMutable, ninepatch, density);
        if (hasException(env)) {
            obj = NULL;
        }
    }
    return obj;
}
한마디로 Bitmap(new SkBitmap(bitmap), isMutable=true, null, null); 을 콜한것인데... Java로 고고씽
 private Bitmap(int nativeBitmap, boolean isMutable, byte[] ninePatchChunk,
         int density) {
     if (nativeBitmap == 0) {
         throw new RuntimeException("internal error: native bitmap is 0");
     }
     // we delete this in our finalizer
     mNativeBitmap = nativeBitmap;
     mIsMutable = isMutable;
     mNinePatchChunk = ninePatchChunk;
     if (density >= 0) {
         mDensity = density;
     }
 }
c에서 jni를 통해 넘어온 bitmap을 wrapping해준다.

+ Recent posts