深入理解Android Bitmap
Bitmap(android.graphics.Bitmap)
Bitmap是Android系统中的图像处理的最重要类之一。用它可以获取图像文件信息,进行图像剪切、旋转、缩放等操作,并可以指定格式保存图像文件。
基于android-6.0.1_r80源代码分析
通过下面三个章节基本可以扫清Bitmap盲区。文章没有覆盖到的一方面是Bitmap用法,这部分建议阅读Glide库源代码。一些Color的概念,例如premultiplied/Dither,需要具备一定CG物理基础,不管怎样先读下去。
Bitmap对象创建
Bitmapjava层构造函数是通过native层jnicall过来的,逻辑在Bitmap_creator方法中。
///home/yuxiang/repo_aosp/android-6.0.1_r79/frameworks/base/core/jni/android/graphics/Bitmap.cpp
staticjobjectBitmap_creator(JNIEnv*env,jobject,jintArrayjColors,
jintoffset,jintstride,jintwidth,jintheight,
jintconfigHandle,jbooleanisMutable){
SkColorTypecolorType=GraphicsJNI::legacyBitmapConfigToColorType(configHandle);
if(NULL!=jColors){
size_tn=env->GetArrayLength(jColors);
if(n
legacyBitmapConfigToColorType将Bitmap.Config.ARGB_8888转成skia域的颜色类型kBGRA_8888_SkColorType,颜色类型定义在SkImageInfo.h中,kARGB_4444_SkColorType会强转成kN32_SkColorType,它就是kBGRA_8888_SkColorType,不必纠结。
///home/yuxiang/repo_aosp/android-6.0.1_r79/external/skia/include/core/SkImageInfo.h
enumSkColorType{
kUnknown_SkColorType,
kAlpha_8_SkColorType,
kRGB_565_SkColorType,
kARGB_4444_SkColorType,
kRGBA_8888_SkColorType,
kBGRA_8888_SkColorType,
kIndex_8_SkColorType,
kGray_8_SkColorType,
kLastEnum_SkColorType=kGray_8_SkColorType,
#ifSK_PMCOLOR_BYTE_ORDER(B,G,R,A)
kN32_SkColorType=kBGRA_8888_SkColorType,
#elifSK_PMCOLOR_BYTE_ORDER(R,G,B,A)
kN32_SkColorType=kRGBA_8888_SkColorType,
#else
#error"SK_*32_SHFITvaluesmustcorrespondtoBGRAorRGBAbyteorder"
#endif
};
接着,根据宽、高、颜色类型等创建SkBitmap,注意kPremul_SkAlphaType描述是alpha采用premultiplied处理的方式,CG处理alpha存在premultiplied和unpremultiplied两两种方式。
public:
SkImageInfo()
:fWidth(0)
,fHeight(0)
,fColorType(kUnknown_SkColorType)
,fAlphaType(kUnknown_SkAlphaType)
,fProfileType(kLinear_SkColorProfileType)
{}
staticSkImageInfoMake(intwidth,intheight,SkColorTypect,SkAlphaTypeat,
SkColorProfileTypept=kLinear_SkColorProfileType){
returnSkImageInfo(width,height,ct,at,pt);
}
Make创建SkImageInfo对象,fWidth的赋值是一个关键点,后面Java层通过getAllocationByteCount获取Bitmap内存占用中会用到它计算一行像素占用空间。allocateJavaPixelRef是通过JNI调用VMRuntime实例的newNonMovableArray方法分配内存。
intregister_android_graphics_Graphics(JNIEnv*env)
{
jmethodIDm;
jclassc;
...
gVMRuntime=env->NewGlobalRef(env->CallStaticObjectMethod(gVMRuntime_class,m));
gVMRuntime_newNonMovableArray=env->GetMethodID(gVMRuntime_class,"newNonMovableArray",
"(Ljava/lang/Class;I)Ljava/lang/Object;");
...
}
env->CallObjectMethod(gVMRuntime,gVMRuntime_newNonMovableArray,gByte_class,size)拿到虚拟机分配Heap对象,env->CallLongMethod(gVMRuntime,gVMRuntime_addressOf,arrayObj)拿到分配对象的地址,调用native层构造函数newandroid::Bitmap(env,arrayObj,(void*)addr,info,rowBytes,ctable)
///home/yuxiang/repo_aosp/android-6.0.1_r79/frameworks/base/core/jni/android/graphics/Bitmap.cpp
Bitmap::Bitmap(JNIEnv*env,jbyteArraystorageObj,void*address,
constSkImageInfo&info,size_trowBytes,SkColorTable*ctable)
:mPixelStorageType(PixelStorageType::Java){
env->GetJavaVM(&mPixelStorage.java.jvm);
mPixelStorage.java.jweakRef=env->NewWeakGlobalRef(storageObj);
mPixelStorage.java.jstrongRef=nullptr;
mPixelRef.reset(newWrappedPixelRef(this,address,info,rowBytes,ctable));
//Note:thiswilltriggeracalltoonStrongRefDestroyed(),but
//wewantthepixelreftohavearefcountof0atthispoint
mPixelRef->unref();
}
voidBitmap::getSkBitmap(SkBitmap*outBitmap){
assertValid();
android::AutoMutex_lock(mLock);
//SafebecausemPixelRefisaWrappedPixelReftype,otherwiserowBytes()
//wouldrequirelockingthepixelsfirst.
outBitmap->setInfo(mPixelRef->info(),mPixelRef->rowBytes());
outBitmap->setPixelRef(refPixelRefLocked())->unref();
outBitmap->setHasHardwareMipMap(hasHardwareMipMap());
}
voidBitmap::pinPixelsLocked(){
switch(mPixelStorageType){
casePixelStorageType::Invalid:
LOG_ALWAYS_FATAL("Cannotpininvalidpixels!");
break;
casePixelStorageType::External:
casePixelStorageType::Ashmem:
//Nothingtodo
break;
casePixelStorageType::Java:{
JNIEnv*env=jniEnv();
if(!mPixelStorage.java.jstrongRef){
mPixelStorage.java.jstrongRef=reinterpret_cast(
env->NewGlobalRef(mPixelStorage.java.jweakRef));
if(!mPixelStorage.java.jstrongRef){
LOG_ALWAYS_FATAL("Failedtoacquirestrongreferencetopixels");
}
}
break;
}
}
}
///home/yuxiang/repo_aosp/android-6.0.1_r79/frameworks/base/core/jni/android/graphics/Bitmap.h
std::unique_ptrmPixelRef;
PixelStorageTypemPixelStorageType;
union{
struct{
void*address;
void*context;
FreeFuncfreeFunc;
}external;
struct{
void*address;
intfd;
size_tsize;
}ashmem;
struct{
JavaVM*jvm;
jweakjweakRef;
jbyteArrayjstrongRef;
}java;
}mPixelStorage;
native层的Bitmap构造函数,mPixelStorage保存前面创建Heap对象的弱引用,mPixelRef指向WrappedPixelRef。outBitmap拿到mPixelRef强引用对象,这里理解为拿到SkBitmap对象。Bitmap*nativeBitmap=GraphicsJNI::allocateJavaPixelRef完成BitmapHeap分配,创建native层Bitmap,SkBitmap对象,最后自然是创建Java层Bitmap对象,把该包的包上。native层是通过JNI方法,在Java层创建一个数组对象的,这个数组是对应在Java层的Bitmap对象的buffer数组,所以pixels还是保存在Java堆。而在native层这里它是通过weak指针来引用的,在需要的时候会转换为strong指针,用完之后又去掉strong指针,这样这个数组对象还是能够被Java堆自动回收。里面jstrongRef一开始是赋值为null的,但是在bitmap的getSkBitmap方法会使用weakRef给他赋值。
///home/yuxiang/repo_aosp/android-6.0.1_r79/frameworks/base/core/jni/android/graphics/Bitmap.cpp
jobjectGraphicsJNI::createBitmap(JNIEnv*env,android::Bitmap*bitmap,
intbitmapCreateFlags,jbyteArrayninePatchChunk,jobjectninePatchInsets,
intdensity){
boolisMutable=bitmapCreateFlags&kBitmapCreateFlag_Mutable;
boolisPremultiplied=bitmapCreateFlags&kBitmapCreateFlag_Premultiplied;
//Thecallerneedstohavealreadysetthealphatypeproperly,sothe
//nativeSkBitmapstaysinsyncwiththeJavaBitmap.
assert_premultiplied(bitmap->info(),isPremultiplied);
jobjectobj=env->NewObject(gBitmap_class,gBitmap_constructorMethodID,
reinterpret_cast(bitmap),bitmap->javaByteArray(),
bitmap->width(),bitmap->height(),density,isMutable,isPremultiplied,
ninePatchChunk,ninePatchInsets);
hasException(env);//Forthesideeffectoflogging.
returnobj;
}
重点看下这里env->NewObject(gBitmap_class,gBitmap_constructorMethodID,...,参数中有一处bitmap->javaByteArray(),指向的是Heap对象。所以,实际的像素内存只有一份,被不同对象持有,Java层的Bitmap,native层的Btimap。
这里顺带说一下JNI生命周期。JNILocalReference的生命期是在nativemethod的执行期(从Java程序切换到nativecode环境时开始创建,或者在nativemethod执行时调用JNIfunction创建),在nativemethod执行完毕切换回Java程序时,所有JNILocalReference被删除,生命期结束(调用JNIfunction可以提前结束其生命期)。
JNI编程中明显的内存泄漏
NativeCode本身的内存泄漏
JNI编程首先是一门具体的编程语言,或者C语言,或者C++,或者汇编,或者其它native的编程语言。每门编程语言环境都实现了自身的内存管理机制。因此,JNI程序开发者要遵循native语言本身的内存管理机制,避免造成内存泄漏。以C语言为例,当用malloc()在进程堆中动态分配内存时,JNI程序在使用完后,应当调用free()将内存释放。总之,所有在native语言编程中应当注意的内存泄漏规则,在JNI编程中依然适应。
Native语言本身引入的内存泄漏会造成nativememory的内存,严重情况下会造成nativememory的outofmemory。
GlobalReference引入的内存泄漏
JNI编程还要同时遵循JNI的规范标准,JVM附加了JNI编程特有的内存管理机制。
JNI中的LocalReference只在nativemethod执行时存在,当nativemethod执行完后自动失效。这种自动失效,使得对LocalReference的使用相对简单,nativemethod执行完后,它们所引用的Java对象的referencecount会相应减1。不会造成JavaHeap中Java对象的内存泄漏。
而GlobalReference对Java对象的引用一直有效,因此它们引用的Java对象会一直存在JavaHeap中。程序员在使用GlobalReference时,需要仔细维护对GlobalReference的使用。如果一定要使用GlobalReference,务必确保在不用的时候删除。就像在C语言中,调用malloc()动态分配一块内存之后,调用free()释放一样。否则,GlobalReference引用的Java对象将永远停留在JavaHeap中,造成JavaHeap的内存泄漏。
更多JNI泄露,参考阅读JNI编程中潜在的内存泄漏——对LocalReference的深入理解
Bitmap对象释放
基于前文JNILocalReference和GlobalReference泄露,可以看到nativeRecycle实际调用native层Bitmap的freePixels方法,DeleteWeakGlobalRef释放Bitmapnative层Gloabl引用。逻辑还是很简单的。
///home/yuxiang/repo_aosp/android-6.0.1_r79/frameworks/base/core/jni/android/graphics/Bitmap.cpp
voidBitmap::freePixels(){
AutoMutex_lock(mLock);
if(mPinnedRefCount==0){
doFreePixels();
mPixelStorageType=PixelStorageType::Invalid;
}
}
voidBitmap::doFreePixels(){
switch(mPixelStorageType){
casePixelStorageType::Invalid:
//alreadyfree'd,nothingtodo
break;
casePixelStorageType::External:
mPixelStorage.external.freeFunc(mPixelStorage.external.address,
mPixelStorage.external.context);
break;
casePixelStorageType::Ashmem:
munmap(mPixelStorage.ashmem.address,mPixelStorage.ashmem.size);
close(mPixelStorage.ashmem.fd);
break;
casePixelStorageType::Java:
JNIEnv*env=jniEnv();
LOG_ALWAYS_FATAL_IF(mPixelStorage.java.jstrongRef,
"Deletingabitmapwrapperwhilethereareoutstandingstrong"
"references!mPinnedRefCount=%d",mPinnedRefCount);
env->DeleteWeakGlobalRef(mPixelStorage.java.jweakRef);
break;
}
if(android::uirenderer::Caches::hasInstance()){
android::uirenderer::Caches::getInstance().textureCache.releaseTexture(
mPixelRef->getStableID());
}
}
需要注意两点讯息,一是Java层主动callrecycle()方法或者Bitmap析构函数都会调用freePixels,移除Global对象引用,这个对象是Heap上存一堆像素的空间。GC时释放掉。二是,JNI不再持有GlobalReference,并native函数执行后释放掉,但Java层的Bitmap对象还在,只是它的mBuffer和mNativePtr是无效地址,没有像素Heap的Bitmap也就几乎不消耗内存了。至于Java层Bitmap对象什么时候释放,生命周期结束自然free掉了。
///home/yuxiang/repo_aosp/android-6.0.1_r79/art/runtime/jni_internal.cc
staticvoidDeleteWeakGlobalRef(JNIEnv*env,jweakobj){
JavaVMExt*vm=down_cast(env)->vm;
Thread*self=down_cast(env)->self;
vm->DeleteWeakGlobalRef(self,obj);
}
///home/yuxiang/repo_aosp/android-6.0.1_r79/art/runtime/java_vm_ext.cc
voidJavaVMExt::DeleteWeakGlobalRef(Thread*self,jweakobj){
if(obj==nullptr){
return;
}
MutexLockmu(self,weak_globals_lock_);
if(!weak_globals_.Remove(IRT_FIRST_SEGMENT,obj)){
LOG(WARNING)<<"JNIWARNING:DeleteWeakGlobalRef("<
通过BitmapFactory创建Bitmap
Bitmap工厂类提供了多种decodeXXX方法创建Bitmap对象,主要是兼容不同的数据源,包括byte数组、文件、FD、Resource对象、InputStream,最终去到native层方法,如下:
//BitmapFactory.java
privatestaticnativeBitmapnativeDecodeStream(InputStreamis,byte[]storage,Rectpadding,Optionsopts);
privatestaticnativeBitmapnativeDecodeFileDescriptor(FileDescriptorfd,Rectpadding,Optionsopts);
privatestaticnativeBitmapnativeDecodeAsset(longnativeAsset,Rectpadding,Optionsopts);
privatestaticnativeBitmapnativeDecodeByteArray(byte[]data,intoffset,intlength,Optionsopts);
privatestaticnativebooleannativeIsSeekable(FileDescriptorfd);
来看看nativeDecodeStream方法,该方法中先是创建了bufferedStream对象,接着doDecode返回Bitmap对象。SkStreamRewindable定义在skia库中继承SkStream,它声明了两个方法rewind和duplicate,写过网络库的同学一看命名便知是byte操作,前者功能是将文件内部的指针重新指向一个流的开头,后者是创建共享此缓冲区内容的新的字节缓冲区。
//BitmapFactory.cpp
staticjobjectnativeDecodeStream(JNIEnv*env,jobjectclazz,jobjectis,jbyteArraystorage,
jobjectpadding,jobjectoptions){
jobjectbitmap=NULL;
SkAutoTDeletestream(CreateJavaInputStreamAdaptor(env,is,storage));
if(stream.get()){
SkAutoTDeletebufferedStream(
SkFrontBufferedStream::Create(stream.detach(),BYTES_TO_BUFFER));
SkASSERT(bufferedStream.get()!=NULL);
bitmap=doDecode(env,bufferedStream,padding,options);
}
returnbitmap;
}
doDecode先是通过JNI拿到Java层Options对象里面的属性,outWidth、outHeight、inDensity、inTargetDensity这些。后两者用来计算Bitmap缩放比例,计算公式scale=(float)targetDensity/density。
//BitmapFactory.cpp
staticjobjectdoDecode(JNIEnv*env,SkStreamRewindable*stream,jobjectpadding,jobjectoptions){
intsampleSize=1;
SkImageDecoder::ModedecodeMode=SkImageDecoder::kDecodePixels_Mode;
SkColorTypeprefColorType=kN32_SkColorType;
booldoDither=true;
boolisMutable=false;
floatscale=1.0f;
boolpreferQualityOverSpeed=false;
boolrequireUnpremultiplied=false;
jobjectjavaBitmap=NULL;
if(options!=NULL){
sampleSize=env->GetIntField(options,gOptions_sampleSizeFieldID);
if(optionsJustBounds(env,options)){
decodeMode=SkImageDecoder::kDecodeBounds_Mode;
}
//initializethese,incasewefaillateron
env->SetIntField(options,gOptions_widthFieldID,-1);
env->SetIntField(options,gOptions_heightFieldID,-1);
env->SetObjectField(options,gOptions_mimeFieldID,0);
jobjectjconfig=env->GetObjectField(options,gOptions_configFieldID);
prefColorType=GraphicsJNI::getNativeBitmapColorType(env,jconfig);
isMutable=env->GetBooleanField(options,gOptions_mutableFieldID);
doDither=env->GetBooleanField(options,gOptions_ditherFieldID);
preferQualityOverSpeed=env->GetBooleanField(options,
gOptions_preferQualityOverSpeedFieldID);
requireUnpremultiplied=!env->GetBooleanField(options,gOptions_premultipliedFieldID);
javaBitmap=env->GetObjectField(options,gOptions_bitmapFieldID);
if(env->GetBooleanField(options,gOptions_scaledFieldID)){
constintdensity=env->GetIntField(options,gOptions_densityFieldID);
constinttargetDensity=env->GetIntField(options,gOptions_targetDensityFieldID);
constintscreenDensity=env->GetIntField(options,gOptions_screenDensityFieldID);
if(density!=0&&targetDensity!=0&&density!=screenDensity){
scale=(float)targetDensity/density;
}
}
...
}
这些参数是提供给图片解码器SkImageDecoder。图片资源无非是压缩格式,SkImageDecoder工厂类根据输入流同步拿到具体压缩格式并创建相应解码器。GetFormatName返回支持的图片格式。
SkImageDecoder实例将Options参数设置下去。如此解压出来的是根据实际尺寸裁剪后的图片。
//BitmapFactory.cpp
staticjobjectdoDecode(JNIEnv*env,SkStreamRewindable*stream,jobjectpadding,jobjectoptions){
...
SkImageDecoder*decoder=SkImageDecoder::Factory(stream);
if(decoder==NULL){
returnnullObjectReturn("SkImageDecoder::Factoryreturnednull");
}
decoder->setSampleSize(sampleSize);
decoder->setDitherImage(doDither);
decoder->setPreferQualityOverSpeed(preferQualityOverSpeed);
decoder->setRequireUnpremultipliedColors(requireUnpremultiplied)
...
}
//SkImageDecoder_FactoryDefault.cpp
SkImageDecoder*SkImageDecoder::Factory(SkStreamRewindable*stream){
returnimage_decoder_from_stream(stream);
}
//SkImageDecoder_FactoryRegistrar.cpp
SkImageDecoder*image_decoder_from_stream(SkStreamRewindable*stream){
SkImageDecoder*codec=NULL;
constSkImageDecoder_DecodeReg*curr=SkImageDecoder_DecodeReg::Head();
while(curr){
codec=curr->factory()(stream);
//werewindhere,becausewepromiselaterwhenwecall"decode",that
//thestreamwillbeatitsbeginning.
boolrewindSuceeded=stream->rewind();
//ourimagedecoder'srequirethatrewindissupportedsowefailearly
//ifwearegivenastreamthatdoesnotsupportrewinding.
if(!rewindSuceeded){
SkDEBUGF(("Unabletorewindtheimagestream."));
SkDELETE(codec);
returnNULL;
}
if(codec){
returncodec;
}
curr=curr->next();
}
returnNULL;
}
//SkImageDecoder.cpp
constchar*SkImageDecoder::GetFormatName(Formatformat){
switch(format){
casekUnknown_Format:
return"UnknownFormat";
casekBMP_Format:
return"BMP";
casekGIF_Format:
return"GIF";
casekICO_Format:
return"ICO";
casekPKM_Format:
return"PKM";
casekKTX_Format:
return"KTX";
casekASTC_Format:
return"ASTC";
casekJPEG_Format:
return"JPEG";
casekPNG_Format:
return"PNG";
casekWBMP_Format:
return"WBMP";
casekWEBP_Format:
return"WEBP";
default:
SkDEBUGFAIL("Invalidformattype!");
}
return"UnknownFormat";
}
解码仅仅完成数据的读取,图片是经过渲染才能呈现在最终屏幕上,这个步骤在canvas.drawBitmap方法中完成。
//BitmapFactory.cpp
staticjobjectdoDecode(JNIEnv*env,SkStreamRewindable*stream,jobjectpadding,jobjectoptions){
...
SkBitmapoutputBitmap;
if(willScale){
//Thisisweirdsoletmeexplain:wecouldusethescaleparameter
//directly,butforhistoricalreasonsthisishowthecorresponding
//Dalvikcodehasalwaysbehaved.Wesimplyrecreatethebehaviorhere.
//Theresultisslightlydifferentfromsimplyusingscalebecauseof
//the0.5froundingbiasappliedwhencomputingthetargetimagesize
constfloatsx=scaledWidth/float(decodingBitmap.width());
constfloatsy=scaledHeight/float(decodingBitmap.height());
//TODO:avoidcopyingwhenscaledsizeequalsdecodingBitmapsize
SkColorTypecolorType=colorTypeForScaledOutput(decodingBitmap.colorType());
//FIXME:IfthealphaTypeiskUnpremulandtheimagehasalpha,the
//colorsmaynotbecorrect,sinceSkiadoesnotyetsupportdrawing
//to/fromunpremultipliedbitmaps.
outputBitmap.setInfo(SkImageInfo::Make(scaledWidth,scaledHeight,
colorType,decodingBitmap.alphaType()));
if(!outputBitmap.tryAllocPixels(outputAllocator,NULL)){
returnnullObjectReturn("allocationfailedforscaledbitmap");
}
//IfoutputBitmap'spixelsarenewlyallocatedbyJava,thereisnoneed
//toeraseto0,sincethepixelswereinitializedto0.
if(outputAllocator!=&javaAllocator){
outputBitmap.eraseColor(0);
}
SkPaintpaint;
paint.setFilterQuality(kLow_SkFilterQuality);
SkCanvascanvas(outputBitmap);
canvas.scale(sx,sy);
canvas.drawARGB(0x00,0x00,0x00,0x00);
canvas.drawBitmap(decodingBitmap,0.0f,0.0f,&paint);
}
...
//nowcreatethejavabitmap
returnGraphicsJNI::createBitmap(env,javaAllocator.getStorageObjAndReset(),
bitmapCreateFlags,ninePatchChunk,ninePatchInsets,-1);
}
最终渲染后的图片数据包在了Bitmap对象中,这部分逻辑重回第一章节Bitmap对象创建。