谈谈fresco的bitmap内存分配

博客探讨了Fresco库在处理Bitmap内存分配上的策略,特别是在Android系统中如何将像素数据分配到ashmem。内容讨论了在isPurgeable为true时,超过32k的bitmap如何在draw或getPixels时分配到ashmem,而Fresco通过主动pin操作来避免drop frames问题。同时,文章也提到了在Art模式下,如何优化避免stop-the-world的GC问题,展现了Fresco开发者对Bitmap解码和Android平台特性的深入理解。
摘要由CSDN通过智能技术生成
bitmap的内存分配主要有两个含义:
  1. 应用程序实现时针对bitmap的内存缓存;
  2. 从原始数据(byte[])经过decode生成bitmap过程中的内存分配问题;

其中第一个含义,在Fresco中实现对应的就是 interface MemoryCache,这里略有提及,不会大费章节,主要关注decode过程中的内存分配问题。

WHAT?
http://frescolib.org/关于memory的介绍可以知道,在4.x及以下的系统上,Fresco的bitmap decode会把bitmap的pixel data(像素数据)放到一个“ 特殊的内存区域’ ,这个特殊的内存区域其实就是ashmem,至于ashmem是什么,详细的可以参考罗升阳的博客( http://blog.csdn.net/luoshengyang/article/details/6651971 ),这里不详述。

WHY?
那为什么在4.x及以下系统需要这样做?原因是如果Bitmap数量很多时会占用大量的内存(这里内存特指Java Heap),必然就会更加频繁的触发虚拟机进行 GC GC 会导致stop the world,就会出现卡顿,而5.0之后采用了art模式,对GC进行了优化,情况比较乐观(为什么这么说?另说),而在4.x及以下的手机就比较糟糕了,所以,在4.x及以下系统上Fresco选择了ashmem作为pixel data的存储区域。(对于想深究 GC 的问题,可自行分析dalvik和art的相关代码)

另外,Java Heap的GC问题既然会导致freeze的问题,那么我们也可以采用native heap,这个不受GC的影响,而且内存很大(基本可以认为只受物理内存大小影响),需要的只是特别注意去回收内存。那为什么不用native heap,而去选择ashmem呢?这个问题后面会讲解。

HOW?
Bitmap在Fresco这个框架是在decode时,内存被分配到ashmem中呢?接下来从代码上,慢慢分析这个过程。

PlatformDecoder
Fresco 中,提供了PlatformDecoder这个接口用于处理bitmap的decode过程,下面包括了该接口的具体实现类; decoder-class-view.png

从类名就可以推测,Fresco根据系统版本不同,使用不同的decoder,从下面的代码也大致证实了这个推测(但有所差别):
    
    
    
  1. /** * * source : com/facebook/imagepipeline/core/ImagePipelineFactory.java * */
  2. /**
  3. * Provide the implementation of the PlatformDecoder for the current platform using the
  4. * provided PoolFactory
  5. *
  6. * @param poolFactory The PoolFactory
  7. * @return The PlatformDecoder implementation
  8. */
  9. public static PlatformDecoder buildPlatformDecoder(
  10. PoolFactory poolFactory,
  11. boolean directWebpDirectDecodingEnabled,
  12. BitmapCounterTracker bitmapCounterTracker) {
  13. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  14. int maxNumThreads = poolFactory.getFlexByteArrayPoolMaxNumThreads();
  15. return new ArtDecoder(
  16. poolFactory.getBitmapPool(),
  17. maxNumThreads,
  18. new Pools.SynchronizedPool<>(maxNumThreads));
  19. } else {
  20. if (directWebpDirectDecodingEnabled
  21. && Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
  22. return new GingerbreadPurgeableDecoder(bitmapCounterTracker);
  23. } else {
  24. return new KitKatPurgeableDecoder(
  25. poolFactory.getFlexByteArrayPool(), bitmapCounterTracker);
  26. }
  27. }
  28. }
1.1

分别是,5.0以上都使用ArtDecoder,4.4以下如果directWebpDirectDecodingEnabled为true,则使用GingerbreadPurgeableDecoder,不然其他都使 用KitKatPurgeableDecoder 这些decoder主要包含两个方法对原始数据进行decode:decodeFromEncodedImage和decodeJPEGFromEncodedImage。 由于在decode环节上,这两个方法并无二致,为了方便,所以就选择 decodeFromEncodedImage进行深入分析。

DalvikPurgeableDecoder
先看DalvikPurgeableDecoder的decodeFromEncodedImage方法:
   
   
   
  1. /** * * source : com/facebook/imagepipeline/platform/DalvikPurgeableDecoder.java * */
  2. @Override
  3. public CloseableReference<Bitmap> decodeFromEncodedImage(
  4. final EncodedImage encodedImage,
  5. Bitmap.Config bitmapConfig) {
  6. BitmapFactory.Options options = getBitmapFactoryOptions(
  7. encodedImage.getSampleSize(),
  8. bitmapConfig);
  9. CloseableReference<PooledByteBuffer> bytesRef = encodedImage.getByteBufferRef();
  10. Preconditions.checkNotNull(bytesRef);
  11. try {
  12. Bitmap bitmap = decodeByteArrayAsPurgeable(bytesRef, options);
  13. return pinBitmap(bitmap);
  14. } finally {
  15. CloseableReference.closeSafely(bytesRef);
  16. }
  17. }
    
    
    
  1. private static BitmapFactory.Options getBitmapFactoryOptions(
  2. int sampleSize,
  3. Bitmap.Config bitmapConfig) {
  4. BitmapFactory.Options options = new BitmapFactory.Options();
  5. options.inDither = true; // known to improve picture quality at low cost
  6. options.inPreferredConfig = bitmapConfig;
  7. // Decode the image into a 'purgeable' bitmap that lives on the ashmem heap
  8. options.inPurgeable = true;
  9. // Enable copy of of bitmap to enable purgeable decoding by filedescriptor
  10. options.inInputShareable = true;
  11. // Sample size should ONLY be different than 1 when downsampling is enabled in the pipeline
  12. options.inSampleSize = sampleSize;
  13. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
  14. options.inMutable = true; // no known perf difference; allows postprocessing to work
  15. }
  16. return options;
  17. }
2.1
着重关注和内存分配相关的两个设置:inPurgeable和inInputShareable。上面注释看, inPurgeable能使bitmap的内存分配到ashmem上,对于 通过filedescriptor去decode的方式,还要设置 inInputShareable为true,只能够使内存分配到ashmem上 。先关注 inPurgeable
    
    
    
  1. /**
  2. * If this is set to true, then the resulting bitmap will allocate its
  3. * pixels such that they can be purged if the system needs to reclaim
  4. * memory. In that instance, when the pixels need to be accessed again
  5. * (e.g. the bitmap is drawn, getPixels() is called), they will be
  6. * automatically re-decoded.
  7. *
  8. * <p>For the re-decode to happen, the bitmap must have access to the
  9. * encoded data, either by sharing a reference to the input
  10. * or by making a copy of it. This distinction is controlled by
  11. * inInputShareable. If this is true, then the bitmap may keep a shallow
  12. * reference to the input. If this is false, then the bitmap will
  13. * explicitly make a copy of the input data, and keep that. Even if
  14. * sharing is allowed, the implementation may still decide to make a
  15. * deep copy of the input data.</p>
  16. *
  17. * <p>While inPurgeable can help avoid big Dalvik heap allocations (from
  18. * API level 11 onward), it sacrifices performance predictability since any
  19. * image that the view system tries to draw may incur a decode delay which
  20. * can lead to dropped frames. Therefore, most apps should avoid using
  21. * inPurgeable to allow for a fast and fluid UI. To minimize Dalvik heap
  22. * allocations use the {@link #inBitmap} flag instead.</p>
  23. *
  24. * <p class="note"><strong>Note:</strong> This flag is ignored when used
  25. * with {@link #decodeResource(Resources, int,
  26. * android.graphics.BitmapFactory.Options)} or {@link #decodeFile(String,
  27. * android.graphics.BitmapFactory.Options)}.</p>
  28. */
  29. public boolean inPurgeable;
2.2
从注释看inPurgeable的作用是,在KITKAT及以下,使用该参数的话,当系统需要回收内存的时候,bitmap的pixels可以被清除。好在的是, 当pixels需要被重新访问的时候(例如bitmap draw或者调用getPixels()的时候),它们又可以重新被decode出来。要重新decode出来的话,自然需要encoded data,encdoded data可能来源于对原始那份encoded data的引用,或者是对原始数据的拷贝。具体是引用或者拷贝,就是根据 inInputShareable来决定的,如果是true那就是引用,不然就是deep copy,但是 inInputShareable 即使设置为true,不同的实现也可能是直接进行deep copy。而大的问题是, inPurgeable虽然能够避免在Java heap中分配内存,但是牺牲点了UI的流畅性,为什么?因为重新decode的过程是在UI线程进行的,这会导致一定的掉帧问题。

inPurgeable可以引出了两个问题:
  1. Decode过程是如何根据它来把pixels分配到ashmem?sdk文档中并没有提及到这一点,是不是真的如此?
  2. Decode为什么会导致drop frames,又该如何解决?

Pixels到ashmem过程
回到 decodeFromEncodedImage,它主要是调用decodeByteArrayAsPurgeable进行decode的过程,为了方便( 因为GingerbreadPurgeableDecoder的decode过程较为复杂,而且存在一定问题,留待后面解析 ),我们分析 KitKatPurgeableDecoder的该方法:
   
   
   
  1. /**
  2. * Decodes a byteArray into a purgeable bitmap
  3. *
  4. * @param bytesRef the byte buffer that contains the encoded bytes
  5. * @return
  6. */
  7. @Override
  8. protected Bitmap decodeByteArrayAsPurgeable(
  9. CloseableReference<PooledByteBuffer> bytesRef,
  10. BitmapFactory.Options options) {
  11. final PooledByteBuffer pooledByteBuffer = bytesRef.get();
  12. final int length = pooledByteBuffer.size();
  13. final CloseableReference<byte[]> encodedBytesArrayRef = mFlexByteArrayPool.get(length);
  14. try {
  15. final byte[] encodedBytesArray = encodedBytesArrayRef.get();
  16. pooledByteBuffer.read(0, encodedBytesArray, 0, length);
  17. Bitmap bitmap = BitmapFactory.decodeByteArray(
  18. encodedBytesArray,
  19. 0,
  20. length,
  21. options);
  22. return Preconditions.checkNotNull(bitmap, "BitmapFactory returned null");
  23. } finally {
  24. CloseableReference.closeSafely(encodedBytesArrayRef);
  25. }
  26. }
3.1
很简单,就是就是copy数据到byte[],然后调用BitmapFactory的decodeByteArray进行decode。BitmapFactory的decodeByteArray最终通过jni的方式,调用BitmapFactory.cpp的nativeDecodeByteArray方法:
   
   
   
  1. static jobject nativeDecodeByteArray(JNIEnv* env, jobject, jbyteArray byteArray,
  2. int offset, int length, jobject options) {
  3. /* If optionsShareable() we could decide to just wrap the java array and
  4. share it, but that means adding a globalref to the java array object
  5. and managing its lifetime. For now we just always copy the array's data
  6. if optionsPurgeable(), unless we're just decoding bounds.
  7. */
  8. bool purgeable = optionsPurgeable(env, options) && !optionsJustBounds(env, options);
  9. AutoJavaByteArray ar(env, byteArray);
  10. SkMemoryStream* stream = new SkMemoryStream(ar.ptr() + offset, length, purgeable);
  11. SkAutoUnref aur(stream);
  12. return doDecode(env, stream, NULL, options, purgeable);
  13. }
3.2
从上面代码看,除非BitmapOptions设置了inJustDecodeBounds为true,不然由于 inPurgeable为true,所以临时变量 purgeable都为true。 purgeable的值影响的是SkMemoryStream的区别:
   
   
   
  1. SkMemoryStream::SkMemoryStream(const void* src, size_t size, bool copyData) {
  2. fData = newFromParams(src, size, copyData);
  3. fOffset = 0;
  4. }
    
    
    
  1. static SkData* newFromParams(const void* src, size_t size, bool copyData) {
  2. if (copyData) {
  3. return SkData::NewWithCopy(src, size);
  4. } else {
  5. return SkData::NewWithProc(src, size, NULL, NULL);
  6. }
  7. }
     
     
     
  1. SkData* SkData::NewWithCopy(const void* data, size_t length) {
  2. if (0 == length) {
  3. return SkData::NewEmpty();
  4. }
  5. void* copy = sk_malloc_throw(length); // balanced in sk_free_releaseproc
  6. memcpy(copy, data, length);
  7. return new SkData(copy, length, sk_free_releaseproc, NULL);
  8. }
      
      
      
  1. SkData* SkData::NewWithProc(const void* data, size_t length,
  2. ReleaseProc proc, void* context) {
  3. return new SkData(data, length, proc, context);
  4. }
       
       
       
  1. SkData::SkData(const void* ptr, size_t size, ReleaseProc proc, void* context) {
  2. fPtr = ptr;
  3. fSize = size;
  4. fReleaseProc = proc;
  5. fReleaseProcContext = context;
  6. }
3.3
从上面代码看,区别就是如果是true,则copy一份数据,不然纯粹是引用。我们这里肯定是true,所以固定会copy一份数据,也就是copy一份encoded data。这内存的分配是在native heap上做的。
另外这个刚好反应上面说的,“ 但是 inInputShareable 即使设置为true,不同的实现也可能是直接进行deep copy

再看关键的doDecode方法,由于这个方法很长,只截取相关的一部分去看:
   
   
   
  1. // since we "may" create a purgeable imageref, we require the stream be ref'able
  2. // i.e. dynamically allocated, since its lifetime may exceed the current stack
  3. // frame.
  4. static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding,
  5. jobject options, bool allowPurgeable, bool forcePurgeable = false) {
  6. int sampleSize = 1;
  7. SkImageDecoder::Mode mode = SkImageDecoder::kDecodePixels_Mode;
  8. SkBitmap::Config prefConfig = SkBitmap::kARGB_8888_Config;
  9. bool doDither = true;
  10. bool isMutable = false;
  11. float scale = 1.0f;
  12. bool isPurgeable = forcePurgeable || (allowPurgeable && optionsPurgeable(env, options));
  13. bool preferQualityOverSpeed = false;
  14. bool requireUnpremultiplied = false;
  15. jobject javaBitmap = NULL;//其实就是BitmapOptions的inBitmap
  16. if (options != NULL) {
  17. sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
  18. if (optionsJustBounds(env, options)) {
  19. mode = SkImageDecoder::kDecodeBounds_Mode;
  20. }
  21. ........................................
  22. //根据部分参数计算scale的的值(我们这里scale设置为1)
  23. if (env->GetBooleanField(options, gOptions_scaledFieldID)) {
  24. const int density = env->GetIntField(options, gOptions_densityFieldID);
  25. const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID);
  26. const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID);
  27. if (density != 0 && targetDensity != 0 && density != screenDensity) {
  28. scale = (float) targetDensity / density;
  29. }
  30. }
  31. }
  32. const bool willScale = scale != 1.0f;
  33. isPurgeable &= !willScale;//如果bitmap需要scale那么isPurgeable必然为false
  34. SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
  35. if (decoder == NULL) {
  36. return nullObjectReturn("SkImageDecoder::Factory returned null");
  37. }
  38. ...........................................................
  39. SkAutoTDelete<SkBitmap> adb(outputBitmap == NULL ? new SkBitmap : NULL);
  40. if (outputBitmap == NULL) outputBitmap = adb.get();
  41. NinePatchPeeker peeker(decoder);
  42. decoder->setPeeker(&peeker);
  43. //如果isPurgeable,使用kDecodeBounds_Mode,用于先计算出图片的宽高等,而不是直接decode出pixels来
  44. SkImageDecoder::Mode decodeMode = isPurgeable ? SkImageDecoder::kDecodeBounds_Mode : mode;
  45. ...........................................................................
  46. // Only setup the decoder to be deleted after its stack-based, refcounted
  47. // components (allocators, peekers, etc) are declared. This prevents RefCnt
  48. // asserts from firing due to the order objects are deleted from the stack.
  49. SkAutoTDelete<SkImageDecoder> add(decoder);
  50. AutoDecoderCancel adc(options, decoder);
  51. // To fix the race condition in case "requestCancelDecode"
  52. // happens earlier than AutoDecoderCancel object is added
  53. // to the gAutoDecoderCancelMutex linked list.
  54. if (options != NULL && env->GetBooleanField(options, gOptions_mCancelID)) {
  55. return nullObjectReturn("gOptions_mCancelID");
  56. }
  57. SkBitmap decodingBitmap;
  58. //使用kDecodeBounds_Mode,先计算出图片的宽高等,而不是直接decode出pixels来
  59. if (!decoder->decode(stream, &decodingBitmap, prefConfig, decodeMode)) {
  60. return nullObjectReturn("decoder->decode returned false");
  61. }
  62. .............................................
  63. if (willScale) {
  64. .............
  65. .............
  66. } else {
  67. //把outputBitmap设置为刚才decode出的decodingBitmap
  68. outputBitmap->swap(decodingBitmap);
  69. }
  70. SkPixelRef* pr;
  71. if (isPurgeable) {
  72. pr = installPixelRef(outputBitmap, stream, sampleSize, doDither);
  73. } else {
  74. // if we get here, we're in kDecodePixels_Mode and will therefore
  75. // already have a pixelref installed.
  76. pr = outputBitmap->pixelRef();
  77. }
  78. if (pr == NULL) {
  79. return nullObjectReturn("Got null SkPixelRef");
  80. }
  81. if (!isMutable && javaBitmap == NULL) {
  82. // promise we will never change our pixels (great for sharing and pictures)
  83. pr->setImmutable();
  84. }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值