主要参照了这篇博客,https://www.jianshu.com/p/8e8ad414237e 但是原文写得不是很详细,做了些具体调用补充。

具体回收细节属于原创,原创不易,转载请注明出处https://blog.csdn.net/shihongyu12345/article/details/89681948,谢谢!

在 Android 2.3.3 之前开发者必须手动调用 recycle 方法去释放 Native 内存,因为那个时候管理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.

在 Android 2.3.3 以后不需要开发者主动调用 recycle 方法来回收内存了,但 Android K,L,M,N,O 版本上,都还能看到 recycle 方法,为什么没有干掉呢? 调用它会不会真正的释放内存呢?既然不需要手动释放 Native Bitmap ,那 Native 层的对象是怎么自动释放的?我们先来看下 7.0 和 8.0 中 recycle 的方法实现。

代码块

/**  * Free the native object associated with this bitmap, and clear the  * reference to the pixel data. This will not free the pixel data synchronously;  * it simply allows it to be garbage collected if there are no other references.  * The bitmap is marked as "dead", meaning it will throw an exception if  * getPixels() or setPixels() is called, and will draw nothing. This operation  * cannot be reversed, so it should only be called if you are sure there are no  * further uses for the bitmap. This is an advanced call, and normally need  * not be called, since the normal GC process will free up this memory when  * there are no more references to this bitmap.  */  public void recycle() {    if (!mRecycled && mNativePtr != 0) {      if (nativeRecycle(mNativePtr)) {        // return value indicates whether native pixel object was actually recycled.        // false indicates that it is still in use at the native level and these        // objects should not be collected now. They will be collected later when the        // Bitmap itself is collected.        mNinePatchChunk = null;      }      mRecycled = true;    }  }​  private static native boolean nativeRecycle(long nativeBitmap);

都是调用了native方法,下面看一下native方法

8.0 见:
/xref/frameworks/base/core/jni/android/graphics/Bitmap.cpp

代码块

static jboolean Bitmap_recycle(JNIEnv *env, jobject, jlong bitmapHandle) {    LocalScopedBitmap bitmap(bitmapHandle);    bitmap->freePixels();    return JNI_TRUE;}​void freePixels() {    mInfo = mBitmap->info();    mHasHardwareMipMap = mBitmap->hasHardwareMipMap();    mAllocationSize = mBitmap->getAllocationByteCount();    mRowBytes = mBitmap->rowBytes();    mGenerationId = mBitmap->getGenerationID();    mIsHardware = mBitmap->isHardware();    // 清空了数据    mBitmap.reset();}

7.0 见:
/xref/frameworks/base/core/jni/android/graphics/Bitmap.cpp

代码块

static jboolean Bitmap_recycle(JNIEnv *env, jobject, jlong bitmapHandle) {    LocalScopedBitmap bitmap(bitmapHandle);    bitmap->freePixels();    return JNI_TRUE;}
void Bitmap::doFreePixels() {    switch (mPixelStorageType) {        case PixelStorageType::Invalid:            // already free'd, nothing to do            break;        case PixelStorageType::External:            mPixelStorage.external.freeFunc(mPixelStorage.external.address,                                            183            mPixelStorage.external.context);            break;        case PixelStorageType::Ashmem:            munmap(mPixelStorage.ashmem.address, mPixelStorage.ashmem.size);            close(mPixelStorage.ashmem.fd);            break;        case PixelStorageType::Java:            // 只是释放了 Java 层之前创建的引用            JNIEnv *env = jniEnv();            LOG_ALWAYS_FATAL_IF(mPixelStorage.java.jstrongRef,                                192            "Deleting a bitmap wrapper while there are outstanding strong "                    "references! mPinnedRefCount = %d", mPinnedRefCount);            env->DeleteWeakGlobalRef(mPixelStorage.java.jweakRef);            break;    }​    if (android::uirenderer::Caches::hasInstance()) {        android::uirenderer::Caches::getInstance().textureCache.releaseTexture(                mPixelRef->getStableID());    }}

从上面的源码可以看出,如果是 8.0 我们手动调用 recycle 方法,数据是会立即释放的,因为像素数据本身就是在 Native 层开辟的。但如果是在 8.0 以下,就算我们手动调用 recycle 方法,数据也是不会立即释放的,而是 DeleteWeakGlobalRef 交由 Java GC 来回收。建议大家翻译一下 recycle 方法注释。注意:以上的所说的释放数据仅代表释放像素数据,并未释放 Native 层的 Bitmap 对象。

最后只剩下一个问题了,我们在开发的过程中一般情况下并不会手动去调用 recycle 方法,那 Native 层的 Bitmap 是怎么回收的呢?如果让我们来写这个代码,我们不妨思考一下该怎么下手?这里我就不卖关子了。在 new Bitmap 时,其实就已经指定了谁来控制 Bitmap 的内存回收。Android M 版本及以前的版本, Bitmap 的内存回收主要是通过 BitmapFinalizer 来完成的见:
/frameworks/base/graphics/java/android/graphics/Bitmap.java

代码块

Bitmap(long nativeBitmap, byte[] buffer, int width, int height, int density,            boolean isMutable, boolean requestPremultiplied,            byte[] ninePatchChunk, NinePatchInsetStruct ninePatchInsets) {        if (nativeBitmap == 0) {            throw new RuntimeException("internal error: native bitmap is 0");        }​        mWidth = width;        mHeight = height;        mIsMutable = isMutable;        mRequestPremultiplied = requestPremultiplied;        mBuffer = buffer;​        mNinePatchChunk = ninePatchChunk;        mNinePatchInsets = ninePatchInsets;        if (density >= 0) {            mDensity = density;        }​        mNativePtr = nativeBitmap;        // 这个对象对象来回收        mFinalizer = new BitmapFinalizer(nativeBitmap);        int nativeAllocationByteCount = (buffer == null ? getByteCount() : 0);        mFinalizer.setNativeAllocationByteCount(nativeAllocationByteCount);    }​    private static class BitmapFinalizer {        private long mNativeBitmap;​        // Native memory allocated for the duration of the Bitmap,        // if pixel data allocated into native memory, instead of java byte[]        private int mNativeAllocationByteCount;​        BitmapFinalizer(long nativeBitmap) {            mNativeBitmap = nativeBitmap;        }​        public void setNativeAllocationByteCount(int nativeByteCount) {            if (mNativeAllocationByteCount != 0) {                VMRuntime.getRuntime().registerNativeFree(mNativeAllocationByteCount);            }            mNativeAllocationByteCount = nativeByteCount;            if (mNativeAllocationByteCount != 0) {                VMRuntime.getRuntime().registerNativeAllocation(mNativeAllocationByteCount);            }        }​        @Override        public void finalize() {            try {                super.finalize();            } catch (Throwable t) {                // Ignore            } finally {                // finalize 这里是 GC 回收该对象时会调用                setNativeAllocationByteCount(0);                nativeDestructor(mNativeBitmap);                mNativeBitmap = 0;            }        }    }​    private static native void nativeDestructor(long nativeBitmap);

看到这里,可能有些人还是不知道怎么触发回收的。

这里要说一下libcore/libart/src/main/java/java/lang/Daemons.java中的FinalizerDaemon

FinalizerDaemon:析构守护线程。对于重写了成员函数finalize的对象,它们被GC决定回收时,并没有马上被回收,而是被放入到一个队列中,等待FinalizerDaemon守护线程去调用它们的成员函数finalize,然后再被回收。(引申一下,与此文无关 函数里可能会用到很多java对象。这也是为什么如果对象实现了finalize函数,不仅会使其生命周期至少延长一个GC过程,而且也会延长其所引用到的对象的生命周期,从而给内存造成了不必要的压力)

由于BitmapFinalizer实现了finalize()方法,当bitmap对象变成GC root不可达时,会触发回收BitmapFinalizer,放到延时回收队列中,调用它的finalize函数,进行bitmap native内存回收。

为什么bitmap对象不直接实现finalize()方法呢?因为bitmap2.3-7.0版本,主要内存(如像素点)在java堆中,如果直接实现finalize()方法会导致bitmap对象被延时回收,造成内存压力。

在 Android N 和 Android O 上做了些改动,没有了 BitmapFinalizer 类,但在 new Bitmap 时会注册 native 的 Finalizer 方法见: /frameworks/base/graphics/java/android/graphics/Bitmap.java

代码块

/**     * Private constructor that must received an already allocated native bitmap     * int (pointer).     */    // called from JNI    Bitmap(long nativeBitmap, int width, int height, int density,            boolean isMutable, boolean requestPremultiplied,            byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets) {        if (nativeBitmap == 0) {            throw new RuntimeException("internal error: native bitmap is 0");        }​        mWidth = width;        mHeight = height;        mIsMutable = isMutable;        mRequestPremultiplied = requestPremultiplied;​        mNinePatchChunk = ninePatchChunk;        mNinePatchInsets = ninePatchInsets;        if (density >= 0) {            mDensity = density;        }​        mNativePtr = nativeBitmap;        long nativeSize = NATIVE_ALLOCATION_SIZE + getAllocationByteCount();        NativeAllocationRegistry registry = new NativeAllocationRegistry(                Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), nativeSize);        registry.registerNativeAllocation(this, nativeBitmap);    }

NativeAllocationRegistry 见:
/libcore/luni/src/main/java/libcore/util/NativeAllocationRegistry.java

代码块

public class NativeAllocationRegistry {​        private final ClassLoader classLoader;        private final long freeFunction;        private final long size;​        /**         * Constructs a NativeAllocationRegistry for a particular kind of native         * allocation.         * The address of a native function that can be used to free this kind         * native allocation should be provided using the         * freeFunction argument. The native function should have the         * type:         * 
         *    void f(void* nativePtr);         * 
*

* The classLoader argument should be the class loader used * to load the native library that freeFunction belongs to. This is needed * to ensure the native library doesn't get unloaded before freeFunction * is called. *

* The size should be an estimate of the total number of * native bytes this kind of native allocation takes up. Different * NativeAllocationRegistrys must be used to register native allocations * with different estimated sizes, even if they use the same * freeFunction. * * @param classLoader ClassLoader that was used to load the native * library freeFunction belongs to. * @param freeFunction address of a native function used to free this * kind of native allocation * @param size estimated size in bytes of this kind of native * allocation * @throws IllegalArgumentException If size is negative */ public NativeAllocationRegistry(ClassLoader classLoader, long freeFunction, long size) { if (size < 0) { throw new IllegalArgumentException("Invalid native allocation size: " + size); }​ this.classLoader = classLoader; this.freeFunction = freeFunction; this.size = size; }​ /** * Registers a new native allocation and associated Java object with the * runtime. * This NativeAllocationRegistry's freeFunction will * automatically be called with nativePtr as its sole * argument when referent becomes unreachable. If you * maintain copies of nativePtr outside * referent, you must not access these after * referent becomes unreachable, because they may be dangling * pointers. *

* The returned Runnable can be used to free the native allocation before * referent becomes unreachable. The runnable will have no * effect if the native allocation has already been freed by the runtime * or by using the runnable. * * @param referent java object to associate the native allocation with * @param nativePtr address of the native allocation * @return runnable to explicitly free native allocation * @throws IllegalArgumentException if either referent or nativePtr is null. * @throws OutOfMemoryError if there is not enough space on the Java heap * in which to register the allocation. In this * case, freeFunction will be * called with nativePtr as its * argument before the OutOfMemoryError is * thrown. */ public Runnable registerNativeAllocation(Object referent, long nativePtr) { if (referent == null) { throw new IllegalArgumentException("referent is null"); } if (nativePtr == 0) { throw new IllegalArgumentException("nativePtr is null"); }​ try { registerNativeAllocation(this.size); } catch (OutOfMemoryError oome) { applyFreeFunction(freeFunction, nativePtr); throw oome; }​ Cleaner cleaner = Cleaner.create(referent, new CleanerThunk(nativePtr)); return new CleanerRunner(cleaner); }​​ private class CleanerThunk implements Runnable { private long nativePtr;​ public CleanerThunk() { this.nativePtr = 0; }​ public CleanerThunk(long nativePtr) { this.nativePtr = nativePtr; }​ public void run() { if (nativePtr != 0) { applyFreeFunction(freeFunction, nativePtr); } registerNativeFree(size); }​ public void setNativePtr(long nativePtr) { this.nativePtr = nativePtr; } }​ private static class CleanerRunner implements Runnable { private final Cleaner cleaner;​ public CleanerRunner(Cleaner cleaner) { this.cleaner = cleaner; }​ public void run() { cleaner.clean(); } }​ /** * Calls freeFunction(nativePtr). * Provided as a convenience in the case where you wish to manually free a * native allocation using a freeFunction without using a * NativeAllocationRegistry. */ public static native void applyFreeFunction(long freeFunction, long nativePtr); }

核心代码NativeAllocationRegistry里面的registerNativeAllocation方法:

代码块

try {                registerNativeAllocation(this.size);            } catch (OutOfMemoryError oome) {                applyFreeFunction(freeFunction, nativePtr);                throw oome;            }​            Cleaner cleaner = Cleaner.create(referent, new CleanerThunk(nativePtr));            return new CleanerRunner(cleaner);

我们具体分析一下各行的作用:

registerNativeAllocation(this.size);调用了

VMRuntime.getRuntime().registerNativeAllocation(int size)

http://androidxref.com/8.0.0_r4/xref/libcore/libart/src/main/java/dalvik/system/VMRuntime.java#316

看一下方法注释:

代码块

/**309     * Registers a native allocation so that the heap knows about it and performs GC as required.310     * If the number of native allocated bytes exceeds the native allocation watermark, the311     * function requests a concurrent GC. If the native bytes allocated exceeds a second higher312     * watermark, it is determined that the application is registering native allocations at an313     * unusually high rate and a GC is performed inside of the function to prevent memory usage314     * from excessively increasing.315     */

通过匿名内存申请了mSize这么多native内存,向JVM坦白了偷内存的犯罪事实,仅仅是一个声明的作用,可能申请失败,抛出oom,此时进行释放;

接下来看一下

Cleaner cleaner = Cleaner.create(referent, new CleanerThunk(nativePtr));

通过sun.misc.Cleaner创建了一个对象。这个Cleaner来头可不小,它专门用于监控无法被JVM释放的内存。构造函数传入两个参数,一个是监控对象,这里是FileDescriptor对应的内存区域。Cleaner利用虚引用(PhantomReference)和ReferenceQueue来监控一个对象是否存在强引用。虚引用不影响对象任何的生命周期,当这个对象不具有强引用的时候,JVM会将这个对象加入与之关联的ReferenceQueue。此时Cleaner将会调用构造函数的第二个参数——一个Closer对象——实际上是一个Runnable来执行内存回收操作。

这里Runnable是CleanerThunk(nativePtr),用来释放native内存;

cleaner源码:http://androidxref.com/8.0.0_r4/xref/libcore/ojluni/src/main/java/sun/misc/Cleaner.java

 

好了,到这里,我们就清楚了Bitmap在各个Android版本的回收机制;

具体回收细节属于原创,原创不易,转载请注明出处https://blog.csdn.net/shihongyu12345/article/details/89681948,谢谢!

更多相关文章

  1. Android中图片的异步加载
  2. Android(安卓)重学系列 Ashmem匿名共享内存
  3. ORM 框架之greenDAO
  4. Android(安卓)Handler和内部类的正确用法
  5. android 0x10000
  6. android launcher的结构
  7. Android系列之Intent传递对象的几种实例方法
  8. [转]五大布局对象---FrameLayout,LinearLayout ,AbsoluteLayout,
  9. [置顶] android 耳机按钮深层理解

随机推荐

  1. android运行模拟器脚本(批处理)
  2. 在Eclipse中进行Android单元测试
  3. android电池信息简介
  4. Android开发之拖动条/滑动条控件、星级评
  5. 笔记!
  6. Android 左右滑屏效果
  7. android adb 命令大全
  8. API 23 widget.Space——属性分析
  9. 在android创建bitmap避免低记忆法
  10. Android日志框架SLF4J Android