深入理解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(nlegacyBitmapConfigToColorType将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_ptr mPixelRef; 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()){ SkAutoTDelete bufferedStream( 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对象创建。