Android内存优化(二)之Bitmap的内存申请与回收(Android N和O的对比)

在Android O上大面积的爆了大量native Bitmap相关的泄漏问题,最大能达到几十MB,开始怀疑是出现了native内存泄漏问题,但经分析后发现是Android N和Android O在处理Bitmap的内存存储不同导致的问题,并不是内存泄漏~
以下是整体分析记录:

内存申请

由于Bitmap构造方法不是public的,使用Bitmap时,一般都是使用BitmapFactory的decode操作,以BitmapFactory.decodeFile接口来看下Bitmap申请内存的差异性,看执行流的差异

Android O

BitmapFactory.decodeFile->BitmapFactory.decodeStream->BitmapFactory.decodeStreamInternal->BitmapFactory.nativeDecodeStream->BitmapFactory.cpp nativeDecodeStream->BitmapFactory.cpp doDecode
在doDecode方法中有两段与申请内存相关,一个是默认的内存分配器为HeapAllocator,一个是调用方法tryAllocPixels继续申请内存,见xref: /frameworks/base/core/jni/android/graphics/BitmapFactory.cpp

384    HeapAllocator defaultAllocator;
385    RecyclingPixelAllocator recyclingAllocator(reuseBitmap, existingBufferSize);
386    ScaleCheckingAllocator scaleCheckingAllocator(scale, existingBufferSize);
387    SkBitmap::HeapAllocator heapAllocator;
388    SkBitmap::Allocator* decodeAllocator;
389    if (javaBitmap != nullptr && willScale) {
390        // This will allocate pixels using a HeapAllocator, since there will be an extra
391        // scaling step that copies these pixels into Java memory. This allocator
392        // also checks that the recycled javaBitmap is large enough.
393        decodeAllocator = &scaleCheckingAllocator;
394    } else if (javaBitmap != nullptr) {
395        decodeAllocator = &recyclingAllocator;
396    } else if (willScale || isHardware) {
397        // This will allocate pixels using a HeapAllocator,
398        // for scale case: there will be an extra scaling step.
399        // for hardware case: there will be extra swizzling & upload to gralloc step.
400        decodeAllocator = &heapAllocator;
401    } else {
402        decodeAllocator = &defaultAllocator;
403    }
404
405    // Construct a color table for the decode if necessary
406    sk_sp<SkColorTable> colorTable(nullptr);
407    SkPMColor* colorPtr = nullptr;
408    int* colorCount = nullptr;
409    int maxColors = 256;
410    SkPMColor colors[256];
411    if (kIndex_8_SkColorType == decodeColorType) {
412        colorTable.reset(new SkColorTable(colors, maxColors));
413
414        // SkColorTable expects us to initialize all of the colors before creating an
415        // SkColorTable. However, we are using SkBitmap with an Allocator to allocate
416        // memory for the decode, so we need to create the SkColorTable before decoding.
417        // It is safe for SkAndroidCodec to modify the colors because this SkBitmap is
418        // not being used elsewhere.
419        colorPtr = const_cast<SkPMColor*>(colorTable->readColors());
420        colorCount = &maxColors;
421    }
422
423    SkAlphaType alphaType = codec->computeOutputAlphaType(requireUnpremultiplied);
424
425    const SkImageInfo decodeInfo = SkImageInfo::Make(size.width(), size.height(),
426            decodeColorType, alphaType, decodeColorSpace);
427
428    // For wide gamut images, we will leave the color space on the SkBitmap. Otherwise,
429    // use the default.
430    SkImageInfo bitmapInfo = decodeInfo;
431    if (decodeInfo.colorSpace() && decodeInfo.colorSpace()->isSRGB()) {
432        bitmapInfo = bitmapInfo.makeColorSpace(GraphicsJNI::colorSpaceForType(decodeColorType));
433    }
434
435    if (decodeColorType == kGray_8_SkColorType) {
436        // The legacy implementation of BitmapFactory used kAlpha8 for
437        // grayscale images (before kGray8 existed). While the codec
438        // recognizes kGray8, we need to decode into a kAlpha8 bitmap
439        // in order to avoid a behavior change.
440        bitmapInfo =
441                bitmapInfo.makeColorType(kAlpha_8_SkColorType).makeAlphaType(kPremul_SkAlphaType);
442    }
443    SkBitmap decodingBitmap;
444    if (!decodingBitmap.setInfo(bitmapInfo) ||
445            !decodingBitmap.tryAllocPixels(decodeAllocator, colorTable.get())) {
446        // SkAndroidCodec should recommend a valid SkImageInfo, so setInfo()
447        // should only only fail if the calculated value for rowBytes is too
448        // large.
449        // tryAllocPixels() can fail due to OOM on the Java heap, OOM on the
450        // native heap, or the recycled javaBitmap being too small to reuse.
451        return nullptr;
452    }

tryAllocPixels方法会调用默认内存分配器的allocPixelRef方法,见xref: /external/skia/src/core/SkBitmap.cpp

313 bool SkBitmap::tryAllocPixels(Allocator* allocator, SkColorTable* ctable) {
314    HeapAllocator stdalloc;
315
316    if (nullptr == allocator) {
317        allocator = &stdalloc;
318    }
319    return allocator->allocPixelRef(this, ctable);
320}

allocPixelRef方法会继续调用allocateHeapBitmap分配内存,见xref: /frameworks/base/core/jni/android/graphics/Graphics.cpp

616 bool HeapAllocator::allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) {
617    mStorage = android::Bitmap::allocateHeapBitmap(bitmap, ctable);
618    return !!mStorage;
619}

allocateHeapBitmap方法会最终new Bitmap,分配内存 ,见xref: /frameworks/base/libs/hwui/hwui/Bitmap.cpp

86 static sk_sp<Bitmap> allocateHeapBitmap(size_t size, const SkImageInfo& info, size_t rowBytes,
87        SkColorTable* ctable) {
88    void* addr = calloc(size, 1);
89    if (!addr) {
90        return nullptr;
91    }
92    return sk_sp<Bitmap>(new Bitmap(addr, size, info, rowBytes, ctable));
93}

Android N

开始的执行流不变:
BitmapFactory.decodeFile->BitmapFactory.decodeStream->BitmapFactory.decodeStreamInternal->BitmapFactory.nativeDecodeStream->BitmapFactory.cpp nativeDecodeStream->BitmapFactory.cpp doDecode
差别主要是体现在BitmapFactory的代码变化,见:xref: /frameworks/base/core/jni/android/graphics/BitmapFactory.cpp

343    JavaPixelAllocator javaAllocator(env);
344    RecyclingPixelAllocator recyclingAllocator(reuseBitmap, existingBufferSize);
345    ScaleCheckingAllocator scaleCheckingAllocator(scale, existingBufferSize);
346    SkBitmap::HeapAllocator heapAllocator;
347    SkBitmap::Allocator* decodeAllocator;
348    if (javaBitmap != nullptr && willScale) {
349        // This will allocate pixels using a HeapAllocator, since there will be an extra
350        // scaling step that copies these pixels into Java memory. This allocator
351        // also checks that the recycled javaBitmap is large enough.
352        decodeAllocator = &scaleCheckingAllocator;
353    } else if (javaBitmap != nullptr) {
354        decodeAllocator = &recyclingAllocator;
355    } else if (willScale) {
356        // This will allocate pixels using a HeapAllocator, since there will be an extra
357        // scaling step that copies these pixels into Java memory.
358        decodeAllocator = &heapAllocator;
359    } else {
360        decodeAllocator = &javaAllocator;
361    }
362
363    // Set the decode colorType. This is necessary because we can't always support
364    // the requested colorType.
365    SkColorType decodeColorType = codec->computeOutputColorType(prefColorType);
366
367    // Construct a color table for the decode if necessary
368    SkAutoTUnref<SkColorTable> colorTable(nullptr);
369    SkPMColor* colorPtr = nullptr;
370    int* colorCount = nullptr;
371    int maxColors = 256;
372    SkPMColor colors[256];
373    if (kIndex_8_SkColorType == decodeColorType) {
374        colorTable.reset(new SkColorTable(colors, maxColors));
375
376        // SkColorTable expects us to initialize all of the colors before creating an
377        // SkColorTable. However, we are using SkBitmap with an Allocator to allocate
378        // memory for the decode, so we need to create the SkColorTable before decoding.
379        // It is safe for SkAndroidCodec to modify the colors because this SkBitmap is
380        // not being used elsewhere.
381        colorPtr = const_cast<SkPMColor*>(colorTable->readColors());
382        colorCount = &maxColors;
383    }
384
385    // Set the alpha type for the decode.
386    SkAlphaType alphaType = codec->computeOutputAlphaType(requireUnpremultiplied);
387
388    const SkImageInfo decodeInfo = SkImageInfo::Make(size.width(), size.height(), decodeColorType,
389            alphaType);
390    SkImageInfo bitmapInfo = decodeInfo;
391    if (decodeColorType == kGray_8_SkColorType) {
392        // The legacy implementation of BitmapFactory used kAlpha8 for
393        // grayscale images (before kGray8 existed). While the codec
394        // recognizes kGray8, we need to decode into a kAlpha8 bitmap
395        // in order to avoid a behavior change.
396        bitmapInfo = SkImageInfo::MakeA8(size.width(), size.height());
397    }
398    SkBitmap decodingBitmap;
399    if (!decodingBitmap.setInfo(bitmapInfo) ||
400            !decodingBitmap.tryAllocPixels(decodeAllocator, colorTable)) {
401        // SkAndroidCodec should recommend a valid SkImageInfo, so setInfo()
402        // should only only fail if the calculated value for rowBytes is too
403        // large.
404        // tryAllocPixels() can fail due to OOM on the Java heap, OOM on the
405        // native heap, or the recycled javaBitmap being too small to reuse.
406        return nullptr;
407    }
408

tryAllocPixels方法依然会调用默认内存分配器的allocPixelRef方法,不过不再是HeapAllicator,而是JavaPixelAllocator,官方解释: Allocator which allocates the backing buffer in the Java heap.
意思就是在Java Heap上分配内存,见xref: /external/skia/src/core/SkBitmap.cpp

278bool SkBitmap::tryAllocPixels(Allocator* allocator, SkColorTable* ctable) {
279    HeapAllocator stdalloc;
280
281    if (nullptr == allocator) {
282        allocator = &stdalloc;
283    }
284    return allocator->allocPixelRef(this, ctable);
285}

现在都已经进入native世界了,那又是怎么在Java Heap上分配内存的呢?有过JNI编程经验的同学可能不难想到,JNIEnv就是干这个的~接着往下看代码,见xref: /frameworks/base/core/jni/android/graphics/Graphics.cpp

672 bool JavaPixelAllocator::allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) {
673    JNIEnv* env = vm2env(mJavaVM);
674
675    mStorage = GraphicsJNI::allocateJavaPixelRef(env, bitmap, ctable);
676    return mStorage != nullptr;
677}

继续看GraphicsJNI::allocateJavaPixelRef方法,见xref: /frameworks/base/core/jni/android/graphics/Graphics.cpp

486android::Bitmap* GraphicsJNI::allocateJavaPixelRef(JNIEnv* env, SkBitmap* bitmap,
487                                             SkColorTable* ctable) {
488    const SkImageInfo& info = bitmap->info();
489    if (info.colorType() == kUnknown_SkColorType) {
490        doThrowIAE(env, "unknown bitmap configuration");
491        return NULL;
492    }
493
494    size_t size;
495    if (!computeAllocationSize(*bitmap, &size)) {
496        return NULL;
497    }
498
499    // we must respect the rowBytes value already set on the bitmap instead of
500    // attempting to compute our own.
501    const size_t rowBytes = bitmap->rowBytes();
502
503    jbyteArray arrayObj = (jbyteArray) env->CallObjectMethod(gVMRuntime,
504                                                             gVMRuntime_newNonMovableArray,
505                                                             gByte_class, size);
506    if (env->ExceptionCheck() != 0) {
507        return NULL;
508    }
509    SkASSERT(arrayObj);
510    jbyte* addr = (jbyte*) env->CallLongMethod(gVMRuntime, gVMRuntime_addressOf, arrayObj);
511    if (env->ExceptionCheck() != 0) {
512        return NULL;
513    }
514    SkASSERT(addr);
515    android::Bitmap* wrapper = new android::Bitmap(env, arrayObj, (void*) addr,
516            info, rowBytes, ctable);
517    wrapper->getSkBitmap(bitmap);
518    // since we're already allocated, we lockPixels right away
519    // HeapAllocator behaves this way too
520    bitmap->lockPixels();
521
522    return wrapper;
523}

从上面就可以看到,new android::Bitmap,见xref: /frameworks/base/core/jni/android/graphics/Bitmap.cpp

129Bitmap::Bitmap(JNIEnv* env, jbyteArray storageObj, void* address,
130            const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable)
131        : mPixelStorageType(PixelStorageType::Java) {
132    env->GetJavaVM(&mPixelStorage.java.jvm);
133    mPixelStorage.java.jweakRef = env->NewWeakGlobalRef(storageObj);
134    mPixelStorage.java.jstrongRef = nullptr;
135    mPixelRef.reset(new WrappedPixelRef(this, address, info, rowBytes, ctable));
136    // Note: this will trigger a call to onStrongRefDestroyed(), but
137    // we want the pixel ref to have a ref count of 0 at this point
138    mPixelRef->unref();
139}

从哪个地址上new呢?参数中有addr,这个addr是通过env调用gVMRuntime_addressOf获取的arrayObj的地址,而arrayObj是通过env调用gVMRuntime_newNonMovableArray创建的array,通过JNI方法进入Java世界创建的对象必然在Java Heap上~就像Zygote进入Java世界是通过JNI调用com.android.internal.os.ZygoteInit类的main函数是一个道理~
具体看下gVMRuntime_newNonMovableArray的实现吧,看看直接现场,见xref: /frameworks/base/core/jni/android/graphics/Graphics.cpp

855    gVMRuntime_class = make_globalref(env, "dalvik/system/VMRuntime");
856    m = env->GetStaticMethodID(gVMRuntime_class, "getRuntime", "()Ldalvik/system/VMRuntime;");
857    gVMRuntime = env->NewGlobalRef(env->CallStaticObjectMethod(gVMRuntime_class, m));
858    gVMRuntime_newNonMovableArray = env->GetMethodID(gVMRuntime_class, "newNonMovableArray",
859                                                     "(Ljava/lang/Class;I)Ljava/lang/Object;");
860    gVMRuntime_addressOf = env->GetMethodID(gVMRuntime_class, "addressOf", "(Ljava/lang/Object;)J");

gVMRuntime_newNonMovableArray是dalvik/system/VMRuntime的newNonMovableArray方法,而newNonMovableArray是个native方法,从注释可以看出是在Java heap上分配内存,看下具体是怎么分配的吧,见
xref: /libcore/libart/src/main/java/dalvik/system/VMRuntime.java

253    /**
254 * Returns an array allocated in an area of the Java heap where it will never be moved.
255 * This is used to implement native allocations on the Java heap, such as DirectByteBuffers
256 * and Bitmaps.
257 */
258    public native Object newNonMovableArray(Class<?> componentType, int length);

后面会调到VMRuntime_newNonMovableArray,见xref: /art/runtime/native/dalvik_system_VMRuntime.cc

69 static jobject VMRuntime_newNonMovableArray(JNIEnv* env, jobject, jclass javaElementClass,
70                                            jint length) {
71  ScopedFastNativeObjectAccess soa(env);
72  if (UNLIKELY(length < 0)) {
73    ThrowNegativeArraySizeException(length);
74    return nullptr;
75  }
76  mirror::Class* element_class = soa.Decode<mirror::Class*>(javaElementClass);
77  if (UNLIKELY(element_class == nullptr)) {
78    ThrowNullPointerException("element class == null");
79    return nullptr;
80  }
81  Runtime* runtime = Runtime::Current();
82  mirror::Class* array_class =
83      runtime->GetClassLinker()->FindArrayClass(soa.Self(), &element_class);
84  if (UNLIKELY(array_class == nullptr)) {
85    return nullptr;
86  }
87  gc::AllocatorType allocator = runtime->GetHeap()->GetCurrentNonMovingAllocator();
88  mirror::Array* result = mirror::Array::Alloc<true>(soa.Self(), array_class, length,
89                                                     array_class->GetComponentSizeShift(),
90                                                     allocator);
91  return soa.AddLocalReference<jobject>(result);
92}

其意思就是runtime->GetHeap()上分配内存~及Java Heap

总结

  Android N 上Bitmap的像素点数据与bitmap对象都是分配到dalvik heap,而Android O 上Bitmap的像素点数据是分配在native heap中,因此在Android O加载大量的Bitmap并不会导致应用OOM(我拿一个4GB的手机,native memory会增长搭到将近3GB,还是很恐怖的),但并不意味着就可以任意的使用native内存。所以在需要加载大量Bitmap的时候,该优化还是要优化,该缓存还是要缓存。只是对于某些将Bitmap通过JNI方式直接在native请求空间的优化方案来说,就失去意义了(之前阿里分享过一个偷native内存给Bitmap 的方案就失去意义了)。

内存回收

首先纠正一个观点,Bitmap.java的recycle方法并不是一定要调用的,这是很老的观点了~具体原因如下:
在Android 2.3.3之前,Bitmap像素数据和Bitmap对象是分开存储的,像素数据是存储在native memory中,对象存储在Dalvik heap中,native memory中的像素数据不是以一种可预见的方式释放,可能导致应用程序暂时超过其内存限制和崩溃,所以是需要通过recycle方法回收内存的,那个时候管理Bitmap内存比较复杂,需要手动维护引用计数器,在官网上有如下一段解释:

On Android 2.3.3 (API level 10) and lower, using recycle() is recommended. If you're displaying large amounts of bitmap data in your app, you're likely to run into OutOfMemoryError errors. The recycle()method allows an app to reclaim memory as soon as possible.
Caution: You should use recycle() only when you are sure that the bitmap is no longer being used. If you call recycle() and later attempt to draw the bitmap, you will get the error: "Canvas: trying to use a recycled bitmap".
The following code snippet gives an example of calling recycle(). It uses reference counting (in the variables mDisplayRefCount and mCacheRefCount) to track whether a bitmap is currently being displayed or in the cache. The code recycles the bitmap when these conditions are met:
The reference count for both mDisplayRefCount and mCacheRefCount is 0.
The bitmap is not null, and it hasn't been recycled yet.
private int mCacheRefCount = 0;
private int mDisplayRefCount = 0;
...
// Notify the drawable that the displayed state has changed.
// Keep a count to determine when the drawable is no longer displayed.
public void setIsDisplayed(boolean isDisplayed) {
    synchronized (this) {
        if (isDisplayed) {
            mDisplayRefCount++;
            mHasBeenDisplayed = true;
        } else {
            mDisplayRefCount--;
        }
    }
    // Check to see if recycle() can be called.
    checkState();
}
// Notify the drawable that the cache state has changed.
// Keep a count to determine when the drawable is no longer being cached.
public void setIsCached(boolean isCached) {
    synchronized (this) {
        if (isCached) {
            mCacheRefCount++;
        } else {
            mCacheRefCount--;
        }
    }
    // Check to see if recycle() can be called.
    checkState();
}
private synchronized void checkState() {
    // If the drawable cache and display ref counts = 0, and this drawable
    // has been displayed, then recycle.
    if (mCacheRefCount <= 0 && mDisplayRefCount <= 0 && mHasBeenDisplayed
            && hasValidBitmap()) {
        getBitmap().recycle();
    }
}
private synchronized boolean hasValidBitmap() {
    Bitmap bitmap = getBitmap();
    return bitmap != null && !bitmap.isRecycled();
}

在Android 3.0以后,Bitmap的像素数据和Bitmap对象一起存储在Dalvik heap中,因此不需要手动调用recycle释放Bitmap对象,内存的释放交给垃圾回收器来做~
但我看了下,在Android KK,L,M,N,O版本上,都能看到recycle接口,为什么没有干掉呢?
从上面的分析能知道,不管是在N上还是O上,都需要JNI方法创建Bitmap,简单来说,Bitmap加载至内存中以后,其实是有两部分内存,一个是Java部分,一个是Native部分,Java内存有虚拟机垃圾回收控制就好了,但C部分的内存还是需要recycle方法来释放~recycle接口的注释也说明了这一点~

339    /** 
340 * Free the native object associated with this bitmap, and clear the 
341 * reference to the pixel data. This will not free the pixel data synchronously; 
342 * it simply allows it to be garbage collected if there are no other references. 
343 * The bitmap is marked as "dead", meaning it will throw an exception if 
344 * getPixels() or setPixels() is called, and will draw nothing. This operation 
345 * cannot be reversed, so it should only be called if you are sure there are no 
346 * further uses for the bitmap. This is an advanced call, and normally need 
347 * not be called, since the normal GC process will free up this memory when 
348 * there are no more references to this bitmap. 
349 */
350    public void recycle() {
351        if (!mRecycled && mNativePtr != 0) {
352            if (nativeRecycle(mNativePtr)) {
353                // return value indicates whether native pixel object was actually recycled.
354                // false indicates that it is still in use at the native level and these
355                // objects should not be collected now. They will be collected later when the
356                // Bitmap itself is collected.
357                mBuffer = null;
358                mNinePatchChunk = null;
359            }
360            mRecycled = true;
361        }
362    }

注释的意思就是:释放与此位图相关的native对象,清除像素数据的引用~这将不会同步释放像素数据,只是允许在没有其他引用的情况下能被垃圾回收.位图被标记为“死”,意味着它将抛出一个异常getPixels()或setPixels被调用,什么也不画。此操作不能逆向,所以只有确定位图没有其他用途时方可调用它.这是一个高级调用,通常不需要调用,因为正常的GC进程将在没有对此位图的引用时释放该内存~什么时候没有此位图的引用呢,很容易啊想到Activity onDestroy的时候~后面会根据执行的log,验证这一点
那么如果不手动调用recycle方法,Bitmap 的内存又是如何回收的呢?在new Bitmap时,其实就已经指定了谁来控制Bitmap 的内存回收~Android M版本及以前的版本,Bitmap的内存回收主要是通过BitmapFinalizer来完成的,见xref: /frameworks/base/graphics/java/android/graphics/Bitmap.java

112    /**
113 * Private constructor that must received an already allocated native bitmap
114 * int (pointer).
115 */
116    // called from JNI
117    Bitmap(long nativeBitmap, byte[] buffer, int width, int height, int density,
118            boolean isMutable, boolean requestPremultiplied,
119            byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets) {
120        if (nativeBitmap == 0) {
121            throw new RuntimeException("internal error: native bitmap is 0");
122        }
123
124        mWidth = width;
125        mHeight = height;
126        mIsMutable = isMutable;
127        mRequestPremultiplied = requestPremultiplied;
128        mBuffer = buffer;
129
130        mNinePatchChunk = ninePatchChunk;
131        mNinePatchInsets = ninePatchInsets;
132        if (density >= 0) {
133            mDensity = density;
134        }
135
136        mNativePtr = nativeBitmap;
137        mFinalizer = new BitmapFinalizer(nativeBitmap);
138        int nativeAllocationByteCount = (buffer == null ? getByteCount() : 0);
139        mFinalizer.setNativeAllocationByteCount(nativeAllocationByteCount);
140
141        // MIUI ADD:
142        miui.util.DumpBitmapInfoUtils.putBitmap(this, null);
143    }

简单看下BitmapFinalizer,见xref: /frameworks/base/graphics/java/android/graphics/Bitmap.java

1650    private static class BitmapFinalizer {
1651        private long mNativeBitmap;
1652
1653        // Native memory allocated for the duration of the Bitmap,
1654        // if pixel data allocated into native memory, instead of java byte[]
1655        private int mNativeAllocationByteCount;
1656
1657        BitmapFinalizer(long nativeBitmap) {
1658            mNativeBitmap = nativeBitmap;
1659        }
1660
1661        public void setNativeAllocationByteCount(int nativeByteCount) {
1662            if (mNativeAllocationByteCount != 0) {
1663                VMRuntime.getRuntime().registerNativeFree(mNativeAllocationByteCount);
1664            }
1665            mNativeAllocationByteCount = nativeByteCount;
1666            if (mNativeAllocationByteCount != 0) {
1667                VMRuntime.getRuntime().registerNativeAllocation(mNativeAllocationByteCount);
1668            }
1669        }

Bitmap自身没有Finalize方法,但为它包装了BitmapFinalizer类来完成内存回收,为什么要这样做?这是因为有Finalize方法的对象在第一次GC来临时并不会立即回收内存,需等到下一次GC才会真正回收内存(系统的许多工具类实现了object的finalize方法,这些类在创建时会新建FinalizerRefernece,封装override finalize方法的对象,是个强引用关系,GC时会将FinalizerReference放入静态链表ReferenceQueue(双向链表),FinalizerDaemon线程维护此链表,当线程获得执行资源时,从队列中弹出里面的FinalizerReference对象,并执行封装的object的finalize方法,而FinalizerWatchdogDeamon线程会监控 FinalizerDaemon线程的执行object.finalize()方法的快慢问题,如果ReferenceQueue是空的,FinalizerWatchdogDeamon线程会睡去,直到ReferenceQueue不为空,FinalizeDaemon线程会去唤醒FinalizerWatchdogDeamon,告诉它有工作来了,这是大概流程).所以Bitmap的finalize实现是为了让占用内存大的部分,可以早些释放内存,BitmapFinalizer内部类仅持有一个NativeBitmap指针,通过finalize去释放native内存。这样最有效的达到既提前释放主要内存又能通过finalize释放native内存的目的。
在Android N和Android O上做了些改动,但改动不大~虽然没有了BitmapFinalizer类,但在new Bitmap时会注册native的Finalizer方法,见xref: /frameworks/base/graphics/java/android/graphics/Bitmap.java

115    /**
116 * Private constructor that must received an already allocated native bitmap
117 * int (pointer).
118 */
119    // called from JNI
120    Bitmap(long nativeBitmap, byte[] buffer, int width, int height, int density,
121            boolean isMutable, boolean requestPremultiplied,
122            byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets) {
123        if (nativeBitmap == 0) {
124            throw new RuntimeException("internal error: native bitmap is 0");
125        }
126
127        mWidth = width;
128        mHeight = height;
129        mIsMutable = isMutable;
130        mRequestPremultiplied = requestPremultiplied;
131        mBuffer = buffer;
132
133        mNinePatchChunk = ninePatchChunk;
134        mNinePatchInsets = ninePatchInsets;
135        if (density >= 0) {
136            mDensity = density;
137        }
138
139        mNativePtr = nativeBitmap;
140        // MIUI ADD:
141        miui.util.DumpBitmapInfoUtils.putBitmap(this, null);
142        long nativeSize = NATIVE_ALLOCATION_SIZE;
143        if (buffer == null) {
144            nativeSize += getByteCount();
145        }
146        NativeAllocationRegistry registry = new NativeAllocationRegistry(
147            Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), nativeSize);
148        registry.registerNativeAllocation(this, nativeBitmap);
149    }
150

看下NativeAllocationRegistry的构造方法以及registerNativeAllocation方法,见xref: /libcore/luni/src/main/java/libcore/util/NativeAllocationRegistry.java

76    public NativeAllocationRegistry(ClassLoader classLoader, long freeFunction, long size) {
77        if (size < 0) {
78            throw new IllegalArgumentException("Invalid native allocation size: " + size);
79        }
80
81        this.classLoader = classLoader;
82        this.freeFunction = freeFunction;
83        this.size = size;
84    }
86    /**
87 * Registers a new native allocation and associated Java object with the
88 * runtime.
89 * This NativeAllocationRegistry's <code>freeFunction</code> will
90 * automatically be called with <code>nativePtr</code> as its sole
91 * argument when <code>referent</code> becomes unreachable. If you
92 * maintain copies of <code>nativePtr</code> outside
93 * <code>referent</code>, you must not access these after
94 * <code>referent</code> becomes unreachable, because they may be dangling
95 * pointers.
96 * <p>
97 * The returned Runnable can be used to free the native allocation before
98 * <code>referent</code> becomes unreachable. The runnable will have no
99 * effect if the native allocation has already been freed by the runtime
100 * or by using the runnable.
101 * 
113    public Runnable registerNativeAllocation(Object referent, long nativePtr) {
114        if (referent == null) {
115            throw new IllegalArgumentException("referent is null");
116        }
117        if (nativePtr == 0) {
118            throw new IllegalArgumentException("nativePtr is null");
119        }
120
121        try {
122            registerNativeAllocation(this.size);
123        } catch (OutOfMemoryError oome) {
124            applyFreeFunction(freeFunction, nativePtr);
125            throw oome;
126        }
127
128        Cleaner cleaner = Cleaner.create(referent, new CleanerThunk(nativePtr));
129        return new CleanerRunner(cleaner);
130    }

意思就是注册一个native内存回收器,此回收器和运行时的Java Bitmap对象相关联,当Java Bitmap对象不可达时内存回收方法(freeFunction)会自动被调用,其实就是呼应了上述在recycle方法中所说的
since the normal GC process will free up this memory when there are no more references to this bitmap.
在这个地方再插几句:Java层的Bitmap的构造方法中有如下标注:
// called from JNI
那有人会有疑问,Bitmap的构造方法又是什么时候执行的呢?
在Android N上createBitmap时会创建,见xref: /frameworks/base/core/jni/android/graphics/Graphics.cpp

409jobject GraphicsJNI::createBitmap(JNIEnv* env, android::Bitmap* bitmap,
410        int bitmapCreateFlags, jbyteArray ninePatchChunk, jobject ninePatchInsets,
411        int density) {
412    bool isMutable = bitmapCreateFlags & kBitmapCreateFlag_Mutable;
413    bool isPremultiplied = bitmapCreateFlags & kBitmapCreateFlag_Premultiplied;
414    // The caller needs to have already set the alpha type properly, so the
415    // native SkBitmap stays in sync with the Java Bitmap.
416    assert_premultiplied(bitmap->info(), isPremultiplied);
417
418    jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID,
419            reinterpret_cast<jlong>(bitmap), bitmap->javaByteArray(),
420            bitmap->width(), bitmap->height(), density, isMutable, isPremultiplied,
421            ninePatchChunk, ninePatchInsets);
422    hasException(env); // For the side effect of logging.
423    return obj;
424}

即在BitmapFactory doDecode时得到执行,见xref: /frameworks/base/core/jni/android/graphics/BitmapFactory.cpp

538    // now create the java bitmap
539    return GraphicsJNI::createBitmap(env, javaAllocator.getStorageObjAndReset(),
540            bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1);
541}

在Android O上稍微有点差别,但其基本一致~见xref: /frameworks/base/core/jni/android/graphics/Bitmap.cpp

199jobject createBitmap(JNIEnv* env, Bitmap* bitmap,
200        int bitmapCreateFlags, jbyteArray ninePatchChunk, jobject ninePatchInsets,
201        int density) {
202    bool isMutable = bitmapCreateFlags & kBitmapCreateFlag_Mutable;
203    bool isPremultiplied = bitmapCreateFlags & kBitmapCreateFlag_Premultiplied;
204    // The caller needs to have already set the alpha type properly, so the
205    // native SkBitmap stays in sync with the Java Bitmap.
206    assert_premultiplied(bitmap->info(), isPremultiplied);
207    BitmapWrapper* bitmapWrapper = new BitmapWrapper(bitmap);
208    jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID,
209            reinterpret_cast<jlong>(bitmapWrapper), bitmap->width(), bitmap->height(), density,
210            isMutable, isPremultiplied, ninePatchChunk, ninePatchInsets);
211
212    if (env->ExceptionCheck() != 0) {
213        ALOGE("*** Uncaught exception returned from Java call!\n");
214        env->ExceptionDescribe();
215    }
216    return obj;
217}

根据不同的Bitmap 类型在不同的地方调用~

总结

从上面的分析可以得知,Android N上Bitmap的内存主要在Java heap,内存回收基本就是GC机制,而Android O上一部分在Java heap,一部分在Native heap,Java heap内存回收跟Android N上一致,Native heap的内存又是如何回收的呢?Android N上有没有需要回收的native内存呢?
其实,不管是Android N和Android O,或者其他版本,Bitmap的内存在native heap上都会有部分内存,整个内存分配过程主要动过JNI调用,这也是Bitmap.java的recycle的意义所在~而Android O上的native heap的内存释放,其实也是类似于Java的GC机制-引用计数,当没有引用指向其native heap数据时,回收内存~

验证

我在Bitmap申请内存时和回收内存时打印了部分log,简单看下实际运行的log,上面所说的就很明了了:
02-26 18:04:58.652 4602 4602 D BitmapFactory: lwl doDecode
02-26 18:04:58.652 4602 4602 D OpenGLRenderer: lwl allocateHeapBitmap 19872432
02-26 18:04:58.652 4602 4602 D OpenGLRenderer: lwl contruct Bitmap Heap
02-26 18:04:58.824 4602 4602 D Bitmap : lwl Bitmap.cpp createBitmap
点击back键后:
02-26 18:05:02.037 4602 4602 W OpenGLRenderer: lwl destroy Bitmap Heap
02-26 18:05:02.037 4602 4602 D OpenGLRenderer: lwl RenderProxy::onBitmapDestroyed 608
02-26 18:05:02.037 4602 4632 D OpenGLRenderer: lwl RenderState::onBitmapDestroyed 608
从log看,decode bitmap大小为19872432B,约19MB,lwl contruct Bitmap Heap 这句log是在Bitmap的构造方法中添加的,Heap代表的是Heap类型(共有Heap,External,Ashmem,Hardware四种类型),lwl Bitmap.cpp createBitmap标识在native层创建Bitmap过程,其中户执行Bitmap.java中的构造方法~
@@ -365,6 +366,7 @@ Bitmap::Bitmap(void* address, size_t size, const SkImageInfo& info, size_t rowBy
mPixelStorage.heap.address = address;
mPixelStorage.heap.size = size;
reconfigure(info, rowBytes, ctable);
+ ALOGD(“lwl contruct Bitmap Heap”);
}
而在析构Bitmap数据时,执行完free(mPixelStorage.heap.address);后再执行其他的清理操作~
Bitmap::~Bitmap() {
switch (mPixelStorageType) {
case PixelStorageType::External:
mPixelStorage.external.freeFunc(mPixelStorage.external.address,
mPixelStorage.external.context);
ALOGW(“lwl destroy Bitmap External”);
break;
case PixelStorageType::Ashmem:
munmap(mPixelStorage.ashmem.address, mPixelStorage.ashmem.size);
close(mPixelStorage.ashmem.fd);
ALOGW(“lwl destroy Bitmap Ashmem”);
break;
case PixelStorageType::Heap:
free(mPixelStorage.heap.address);
ALOGW(“lwl destroy Bitmap Heap”);
break;
case PixelStorageType::Hardware:
auto buffer = mPixelStorage.hardware.buffer;
buffer->decStrong(buffer);
mPixelStorage.hardware.buffer = nullptr;
ALOGW(“lwl destroy Bitmap Hardware”);
break;

}

展开阅读全文

没有更多推荐了,返回首页