前言
在安卓中我們圖片相關(guān)的操作一定離不開Bitmap位圖站粟,可見Bitmap在圖像顯示中的位置是多么的重要阀湿,而且Bitmap操作不當(dāng)即會(huì)引發(fā)OOM伤塌,因此我詳細(xì)復(fù)習(xí)了一下Bitmap相關(guān)的知識(shí)點(diǎn)在此記錄一下皱碘。
Bitmap介紹
位圖(Bitmap),又稱柵格圖(英語(yǔ):Raster graphics)或點(diǎn)陣圖均唉,是使用像素陣列(Pixel-array/Dot-matrix點(diǎn)陣)來表示的圖像。自2005年Skia被Google收購(gòu)后肚菠,一直相當(dāng)神秘低調(diào)舔箭,直到2007年初,Skia GL相關(guān)的源代碼才被揭露蚊逢,作為Google Android平臺(tái)的圖形引擎层扶,稍后的Google Chrome瀏覽器也采用Skia引擎。隨著Android與Chrome (開放版本稱為"Chromium")兩大專案公布源代碼后烙荷,Skia也一并公開原始源代碼镜会,以Apache License v2發(fā)布(注意,這意味著與GPLv2授權(quán)不相容) 终抽,而Android與Chrome的源代碼庫(kù)中都有一份[Skia]的復(fù)制戳表,因需求不同,做了部份的修改昼伴,比方說Chrome專案底下的 [chrome/trunk/src/skia]匾旭,需要注意的是,Skia本身是不涉及底層環(huán)境圃郊,如Linux Framebuffer或Gtk+銜接的處理价涝,這也是何以Android (透過Linux Framebuffer)與Chrome (開發(fā)中的Linux版本使用Gtk+)需要提供一份修改,以便系統(tǒng)接軌持舆。 [1]
Bitmap創(chuàng)建過程 (Android N)
創(chuàng)建bitmap有很多的api色瘩,將這些api歸類下伪窖,大致分文三種創(chuàng)建形式。
-
根據(jù)現(xiàn)有的Bitmap創(chuàng)建新的Bitmap
/**
* 通過矩陣的方式居兆,返回原始 Bitmap 中的一個(gè)不可變子集覆山。新 Bitmap 可能返回的就是原始的 Bitmap,也可能還是復(fù)制出來的史辙。
* 新 Bitmap 與原始 Bitmap 具有相同的密度(density)和顏色空間;
*
* @param source 原始 Bitmap
* @param x 在原始 Bitmap 中 x方向的其起始坐標(biāo)(你可能只需要原始 Bitmap x方向上的一部分)
* @param y 在原始 Bitmap 中 y方向的其起始坐標(biāo)(你可能只需要原始 Bitmap y方向上的一部分)
* @param width 需要返回 Bitmap 的寬度(px)(如果超過原始Bitmap寬度會(huì)報(bào)錯(cuò))
* @param height 需要返回 Bitmap 的高度(px)(如果超過原始Bitmap高度會(huì)報(bào)錯(cuò))
* @param m Matrix類型汹买,表示需要做的變換操作
* @param filter 是否需要過濾,只有 matrix 變換不只有平移操作才有效
*/
public static Bitmap createBitmap(@NonNull Bitmap source, int x, int y, int width, int height,
@Nullable Matrix m, boolean filter)
-
根據(jù)顏色像素?cái)?shù)組創(chuàng)建空的Bitmap
/**
*
* 返回具有指定寬度和高度的不可變位圖聊倔,每個(gè)像素值設(shè)置為colors數(shù)組中的對(duì)應(yīng)值晦毙。
* 其初始密度由給定的確定DisplayMetrics。新創(chuàng)建的位圖位于sRGB 顏色空間中耙蔑。
* @param display 顯示將顯示此位圖的顯示的度量標(biāo)準(zhǔn)
* @param colors 用于初始化像素的sRGB數(shù)組
* @param offset 顏色數(shù)組中第一個(gè)顏色之前要跳過的值的數(shù)量
* @param stride 行之間數(shù)組中的顏色數(shù)(必須> = width或<= -width)
* @param width 位圖的寬度
* @param height 位圖的高度
* @param config 要?jiǎng)?chuàng)建的位圖配置见妒。如果配置不支持每像素alpha(例如RGB_565),
* 那么colors []中的alpha字節(jié)將被忽略(假設(shè)為FF)
*/
public static Bitmap createBitmap(@NonNull DisplayMetrics display,
@NonNull @ColorInt int[] colors, int offset, int stride,
int width, int height, @NonNull Config config)
-
對(duì)現(xiàn)有Bitmap進(jìn)行縮放處理
/**
* 對(duì)Bitmap進(jìn)行縮放甸陌,縮放成寬 dstWidth须揣、高 dstHeight 的新Bitmap
*/
public static Bitmap createScaledBitmap(@NonNull Bitmap src, int dstWidth, int dstHeight,boolean filter)
至此我們將這15種創(chuàng)建函數(shù)進(jìn)行了行為分類成這三種模式。經(jīng)過一輪追溯钱豁,我們看下這三個(gè)創(chuàng)建方式最終會(huì)調(diào)用到Bitmap#createBitmap()的方法之中耻卡。
public static Bitmap createBitmap(@Nullable DisplayMetrics display, int width, int height,
@NonNull Config config, boolean hasAlpha, @NonNull ColorSpace colorSpace) {
// 省略邏輯異常語(yǔ)句...
// 最終會(huì)調(diào)用到這個(gè)JNI方法中
Bitmap bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true,
colorSpace == null ? 0 : colorSpace.getNativeInstance());
if (display != null) {
bm.mDensity = display.densityDpi;
}
bm.setHasAlpha(hasAlpha);
if ((config == Config.ARGB_8888 || config == Config.RGBA_F16) && !hasAlpha) {
nativeErase(bm.mNativePtr, 0xff000000);
}
return bm;
}
到這里我們發(fā)現(xiàn)最終的Bitmap的創(chuàng)建是交由JNI調(diào)用Native方法進(jìn)行時(shí)機(jī)的處理。ok 我們分析下Native的部分牲尺。我們找下JNI引用橋代碼卵酪。
static const JNINativeMethod gBitmapMethods[] = {
{ "nativeCreate", "([IIIIIIZJ)Landroid/graphics/Bitmap;",
(void*)Bitmap_creator },
{ "nativeCopy", "(JIZ)Landroid/graphics/Bitmap;",
(void*)Bitmap_copy },
{ "nativeCopyAshmem", "(J)Landroid/graphics/Bitmap;",
(void*)Bitmap_copyAshmem },
{ "nativeCopyAshmemConfig", "(JI)Landroid/graphics/Bitmap;",
(void*)Bitmap_copyAshmemConfig },
{ "nativeGetNativeFinalizer", "()J", (void*)Bitmap_getNativeFinalizer },
{ "nativeRecycle", "(J)V", (void*)Bitmap_recycle },
{ "nativeReconfigure", "(JIIIZ)V", (void*)Bitmap_reconfigure },
{ "nativeCompress", "(JIILjava/io/OutputStream;[B)Z",
(void*)Bitmap_compress },
{ "nativeErase", "(JI)V", (void*)Bitmap_erase },
{ "nativeErase", "(JJJ)V", (void*)Bitmap_eraseLong },
{ "nativeRowBytes", "(J)I", (void*)Bitmap_rowBytes },
{ "nativeConfig", "(J)I", (void*)Bitmap_config },
{ "nativeHasAlpha", "(J)Z", (void*)Bitmap_hasAlpha },
{ "nativeIsPremultiplied", "(J)Z", (void*)Bitmap_isPremultiplied},
{ "nativeSetHasAlpha", "(JZZ)V", (void*)Bitmap_setHasAlpha},
{ "nativeSetPremultiplied", "(JZ)V", (void*)Bitmap_setPremultiplied},
{ "nativeHasMipMap", "(J)Z", (void*)Bitmap_hasMipMap },
{ "nativeSetHasMipMap", "(JZ)V", (void*)Bitmap_setHasMipMap },
{ "nativeCreateFromParcel",
"(Landroid/os/Parcel;)Landroid/graphics/Bitmap;",
(void*)Bitmap_createFromParcel },
{ "nativeWriteToParcel", "(JILandroid/os/Parcel;)Z",
(void*)Bitmap_writeToParcel },
{ "nativeExtractAlpha", "(JJ[I)Landroid/graphics/Bitmap;",
(void*)Bitmap_extractAlpha },
{ "nativeGenerationId", "(J)I", (void*)Bitmap_getGenerationId },
{ "nativeGetPixel", "(JII)I", (void*)Bitmap_getPixel },
{ "nativeGetColor", "(JII)J", (void*)Bitmap_getColor },
{ "nativeGetPixels", "(J[IIIIIII)V", (void*)Bitmap_getPixels },
{ "nativeSetPixel", "(JIII)V", (void*)Bitmap_setPixel },
{ "nativeSetPixels", "(J[IIIIIII)V", (void*)Bitmap_setPixels },
{ "nativeCopyPixelsToBuffer", "(JLjava/nio/Buffer;)V",
(void*)Bitmap_copyPixelsToBuffer },
{ "nativeCopyPixelsFromBuffer", "(JLjava/nio/Buffer;)V",
(void*)Bitmap_copyPixelsFromBuffer },
{ "nativeSameAs", "(JJ)Z", (void*)Bitmap_sameAs },
{ "nativePrepareToDraw", "(J)V", (void*)Bitmap_prepareToDraw },
{ "nativeGetAllocationByteCount", "(J)I", (void*)Bitmap_getAllocationByteCount },
{ "nativeCopyPreserveInternalConfig", "(J)Landroid/graphics/Bitmap;",
(void*)Bitmap_copyPreserveInternalConfig },
{ "nativeWrapHardwareBufferBitmap", "(Landroid/hardware/HardwareBuffer;J)Landroid/graphics/Bitmap;",
(void*) Bitmap_wrapHardwareBufferBitmap },
{ "nativeGetHardwareBuffer", "(J)Landroid/hardware/HardwareBuffer;",
(void*) Bitmap_getHardwareBuffer },
{ "nativeComputeColorSpace", "(J)Landroid/graphics/ColorSpace;", (void*)Bitmap_computeColorSpace },
{ "nativeSetColorSpace", "(JJ)V", (void*)Bitmap_setColorSpace },
{ "nativeIsSRGB", "(J)Z", (void*)Bitmap_isSRGB },
{ "nativeIsSRGBLinear", "(J)Z", (void*)Bitmap_isSRGBLinear},
{ "nativeSetImmutable", "(J)V", (void*)Bitmap_setImmutable},
// ------------ @CriticalNative ----------------
{ "nativeIsImmutable", "(J)Z", (void*)Bitmap_isImmutable}
};
Bitmap.cpp # Bitmap_creator()
static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors,
jint offset, jint stride, jint width, jint height,
jint configHandle, jboolean isMutable,
jlong colorSpacePtr) {
// 1
SkColorType colorType = GraphicsJNI::legacyBitmapConfigToColorType(configHandle);
if (NULL != jColors) {
size_t n = env->GetArrayLength(jColors);
if (n < SkAbs32(stride) * (size_t)height) {
doThrowAIOOBE(env);
return NULL;
}
}
// ARGB_4444 is a deprecated format, convert automatically to 8888
if (colorType == kARGB_4444_SkColorType) {
colorType = kN32_SkColorType;
}
sk_sp<SkColorSpace> colorSpace;
if (colorType == kAlpha_8_SkColorType) {
colorSpace = nullptr;
} else {
colorSpace = GraphicsJNI::getNativeColorSpace(colorSpacePtr);
}
SkBitmap bitmap;
// 2
bitmap.setInfo(SkImageInfo::Make(width, height, colorType, kPremul_SkAlphaType,
colorSpace));
// 3
Bitmap* nativeBitmap = GraphicsJNI::allocateJavaPixelRef(env, &bitmap, NULL);
if (!nativeBitmap) {
ALOGE("OOM allocating Bitmap with dimensions %i x %i", width, height);
doThrowOOME(env);
return NULL;
}
if (jColors != NULL) {
GraphicsJNI::SetPixels(env, jColors, offset, stride, 0, 0, width, height, &bitmap);
}
// 4
return GraphicsJNI::createBitmap(env, nativeBitmap,getPremulBitmapCreateFlags(isMutable));
代碼塊中的1處 將位圖格式轉(zhuǎn)換 RGB_565 ARGB_8888 等轉(zhuǎn)換為skia域的顏色類型kBGRA_8888_SkColorType,而ARGB_4444由于顯示質(zhì)量原因被標(biāo)記過時(shí)谤碳,在進(jìn)行Skia顏色轉(zhuǎn)換的時(shí)候被強(qiáng)制轉(zhuǎn)換為kBGRA_8888_SkColorType溃卡。
enum SkColorType {
kUnknown_SkColorType, //!< uninitialized
kAlpha_8_SkColorType, //!< pixel with alpha in 8-bit byte
kRGB_565_SkColorType, //!< pixel with 5 bits red, 6 bits green, 5 bits blue, in 16-bit word
kARGB_4444_SkColorType, //!< pixel with 4 bits for alpha, red, green, blue; in 16-bit word
kRGBA_8888_SkColorType, //!< pixel with 8 bits for red, green, blue, alpha; in 32-bit word
kRGB_888x_SkColorType, //!< pixel with 8 bits each for red, green, blue; in 32-bit word
kBGRA_8888_SkColorType, //!< pixel with 8 bits for blue, green, red, alpha; in 32-bit word
kRGBA_1010102_SkColorType, //!< 10 bits for red, green, blue; 2 bits for alpha; in 32-bit word
kRGB_101010x_SkColorType, //!< pixel with 10 bits each for red, green, blue; in 32-bit word
kGray_8_SkColorType, //!< pixel with grayscale level in 8-bit byte
kRGBA_F16_SkColorType, //!< pixel with half floats for red, green, blue, alpha; in 64-bit word
kRGBA_F32_SkColorType, //!< pixel using C float for red, green, blue, alpha; in 128-bit word
kLastEnum_SkColorType = kRGBA_F32_SkColorType,//!< last valid value
#if SK_PMCOLOR_BYTE_ORDER(B,G,R,A)
kN32_SkColorType = kBGRA_8888_SkColorType,//!< native ARGB 32-bit encoding
#elif SK_PMCOLOR_BYTE_ORDER(R,G,B,A)
kN32_SkColorType = kRGBA_8888_SkColorType,//!< native ARGB 32-bit encoding
#else
#error "SK_*32_SHIFT values must correspond to BGRA or RGBA byte order"
#endif
};
根據(jù)以上的枚舉類,我們知道所有的Skia圖片顯示格式的種類以及對(duì)應(yīng)的字節(jié)大小蜒简。這里將在后續(xù)計(jì)算Bitmap所占內(nèi)存大小的計(jì)算起到重中之重的角色瘸羡。之后回到代碼2處,SkImageInfo::Make()入?yún)⒅械膚idth搓茬、height犹赖、colorType為后續(xù)計(jì)算bitmap大小起到提供數(shù)據(jù)的作用,make()函數(shù)創(chuàng)建出來了SkImageInfo卷仑,這個(gè)對(duì)象存入了SkBitmap中冷尉。fWidth的賦值是一個(gè)關(guān)鍵點(diǎn),后面Java層通過getAllocationByteCount獲取Bitmap內(nèi)存占用中會(huì)用到它計(jì)算一行像素占用空間系枪,再用一行的所占用的控件乘以高度(行數(shù))就是對(duì)應(yīng)的總量雀哨。代碼3處為SkBitmap bitmap;變量進(jìn)行分配指定的地址空間,代碼4 調(diào)用JNI方法,并創(chuàng)建處Bitmap對(duì)象。我們代碼走一編3雾棺、4步驟膊夹。
int register_android_graphics_Graphics(JNIEnv* env)
{
jmethodID m;
jclass c;
gVMRuntime_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "dalvik/system/VMRuntime"));
m = env->GetStaticMethodID(gVMRuntime_class, "getRuntime", "()Ldalvik/system/VMRuntime;");
gVMRuntime = env->NewGlobalRef(env->CallStaticObjectMethod(gVMRuntime_class, m));
gVMRuntime_newNonMovableArray = GetMethodIDOrDie(env, gVMRuntime_class, "newNonMovableArray","(Ljava/lang/Class;I)Ljava/lang/Object;");
gVMRuntime_addressOf = GetMethodIDOrDie(env, gVMRuntime_class, "addressOf", "(Ljava/lang/Object;)J");
return 0;
}
第一個(gè)紅框調(diào)用了VMRuntime的newNonMovableArray方法,拿到虛擬機(jī)分配Heap對(duì)象捌浩,再調(diào)用VMRuntime的addressOf方法拿到其對(duì)應(yīng)的內(nèi)存地址放刨,這回再調(diào)用Native方法的Bitmap.cpp構(gòu)造方法,創(chuàng)建出Bitmap對(duì)象尸饺。而后又調(diào)用了getSkitmap(SkBitmap* outBitmap)方法进统。
空間分配以后,進(jìn)行調(diào)用Native層的Bitmap構(gòu)造方法浪听,new android::Bitmap(env, arrayObj, (void*) addr, info, rowBytes, ctable),這里能夠看到mPixelStorage保存之前分配后的Heap對(duì)象的弱引用,jstrongRef在構(gòu)造方法中先被初始化為null螟碎。
之后調(diào)用getSkBitmap,setPixelRef()方法中對(duì)強(qiáng)指針進(jìn)行了賦值
強(qiáng)指針被指向這個(gè)Heap對(duì)象〖Kǎ總結(jié)一下掉分,native層的Bitmap構(gòu)造函數(shù),mPixelStorage保存前面創(chuàng)建Heap對(duì)象的弱引用克伊,mPixelRef指向WrappedPixelRef酥郭。outBitmap拿到mPixelRef強(qiáng)引用對(duì)象,這里理解為拿到SkBitmap對(duì)象愿吹。Bitmap* nativeBitmap = GraphicsJNI::allocateJavaPixelRef完成Bitmap Heap分配不从,創(chuàng)建native層Bitmap,SkBitmap對(duì)象犁跪,最后自然是創(chuàng)建Java層Bitmap對(duì)象椿息,把該包的包上。native層是通過JNI方法耘拇,在Java層創(chuàng)建一個(gè)數(shù)組對(duì)象的撵颊,這個(gè)數(shù)組是對(duì)應(yīng)在Java層的Bitmap對(duì)象的buffer數(shù)組宇攻,所以pixels還是保存在Java堆惫叛。而在native層這里它是通過weak指針來引用的,在需要的時(shí)候會(huì)轉(zhuǎn)換為strong指針逞刷,用完之后又去掉strong指針嘉涌,這樣這個(gè)數(shù)組對(duì)象還是能夠被Java堆自動(dòng)回收。里面jstrongRef一開始是賦值為null的夸浅,但是在bitmap的getSkBitmap方法會(huì)使用weakRef給他賦值仑最。
因此證明了,Android N 版本Bitmap對(duì)象是分配在Dalvik堆上帆喇。
jobject GraphicsJNI::createBitmap(JNIEnv* env, android::Bitmap* bitmap,
int bitmapCreateFlags, jbyteArray ninePatchChunk, jobject ninePatchInsets,
int density) {
bool isMutable = bitmapCreateFlags & kBitmapCreateFlag_Mutable;
bool isPremultiplied = bitmapCreateFlags & kBitmapCreateFlag_Premultiplied;
// The caller needs to have already set the alpha type properly, so the
// native SkBitmap stays in sync with the Java Bitmap.
assert_premultiplied(bitmap->info(), isPremultiplied);
jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID,
reinterpret_cast<jlong>(bitmap), bitmap->javaByteArray(),
bitmap->width(), bitmap->height(), density, isMutable, isPremultiplied,
ninePatchChunk, ninePatchInsets);
hasException(env); // For the side effect of logging.
return obj;
}
之后將創(chuàng)建好的Bitmap對(duì)象返回警医。
Bitmap創(chuàng)建過程 (Android O)
流程和N差不多
sk_sp<Bitmap> Bitmap::allocateHeapBitmap(SkBitmap* bitmap) {
return allocateBitmap(bitmap, &android::allocateHeapBitmap);
}
static sk_sp<Bitmap> allocateBitmap(SkBitmap* bitmap, AllocPixelRef alloc) {
const SkImageInfo& info = bitmap->info();
if (info.colorType() == kUnknown_SkColorType) {
LOG_ALWAYS_FATAL("unknown bitmap configuration");
return nullptr;
}
size_t size;
// we must respect the rowBytes value already set on the bitmap instead of
// attempting to compute our own.
const size_t rowBytes = bitmap->rowBytes();
if (!computeAllocationSize(rowBytes, bitmap->height(), &size)) {
return nullptr;
}
// 進(jìn)行分配內(nèi)存
auto wrapper = alloc(size, info, rowBytes);
if (wrapper) {
wrapper->getSkBitmap(bitmap);
}
return wrapper;
}
這里調(diào)用alloc(size, info, rowBytes)來進(jìn)行內(nèi)存分配。alloc是通過參數(shù)傳遞進(jìn)來的,其實(shí)它是一個(gè)函數(shù)指針预皇,我們來看它的定義侈玄。
typedef sk_sp<Bitmap> (*AllocPixelRef)(size_t allocSize, const SkImageInfo& info, size_t rowBytes);
static sk_sp<Bitmap> allocateHeapBitmap(size_t size, const SkImageInfo& info, size_t rowBytes) {
// 真正的在Native層進(jìn)行內(nèi)存分配
void* addr = calloc(size, 1);
if (!addr) {
return nullptr;
}
return sk_sp<Bitmap>(new Bitmap(addr, size, info, rowBytes));
}
之后再回溯到主流程,之后正常調(diào)用createBitmap方法吟温,那么就與Android N 版本后半部分流程一致了序仙。
到這里我們就可以區(qū)分出來了Bitmap在不同Android版本下,對(duì)內(nèi)存分配的差異做了比較鲁豪。這里我們?cè)偬嵋幌屡说浚贑語(yǔ)言中的內(nèi)存分配函數(shù)比較。
C中分配內(nèi)存的函數(shù)主要有兩個(gè)爬橡,malloc()和calloc()治唤。
- 參數(shù)區(qū)別
void *__cdecl calloc(size_t _NumOfElements,size_t _SizeOfElements);
void *__cdecl malloc(size_t _Size);
malloc函數(shù):malloc(size_t size)函數(shù)有一個(gè)參數(shù),即要度分配的內(nèi)存空間的大小堤尾。
calloc函數(shù):calloc(size_t numElements,size_t sizeOfElement)有兩個(gè)參數(shù)肝劲,分別為元素的數(shù)目和每個(gè)元素的大小,這兩個(gè)參數(shù)的乘積就是要分配的內(nèi)存空間的大小郭宝。
初始化內(nèi)存空間上的區(qū)問別
malloc函數(shù):不能初始化所分配的內(nèi)存空間辞槐,在動(dòng)態(tài)分配完內(nèi)存后,里邊答數(shù)據(jù)是隨機(jī)的垃圾數(shù)據(jù)粘室。
calloc函數(shù):能初始化所分配的內(nèi)存空間榄檬,在動(dòng)態(tài)分配完內(nèi)存后,自動(dòng)初始化該內(nèi)存空間為零衔统。函數(shù)返回值上的區(qū)別
malloc函數(shù):函數(shù)返回值是一個(gè)對(duì)象鹿榜。
calloc函數(shù):函數(shù)返回值是一個(gè)數(shù)組。