# 61.Bitmap全面解析-ndk层源码

源码在线查看:http://androidxref.com/

当Android应用加载DPI资源时,会根据当前设备的DPI选择最匹配的资源进行加载。如果没有对应DPI的资源,则会向上选择最接近的资源进行缩放。这种缩放会增加内存消耗,并且可能导致图像失真。
优化建议

为了减少加载不同DPI资源带来的内存消耗,我们可以采取一些优化措施:
优化资源:尽量提供与当前设备DPI匹配的资源,避免缩放操作。
分模块加载:根据不同DPI设备,分模块加载资源,避免一次性加载所有资源。
延迟加载:仅在需要时才加载资源,避免不必要的内存消耗。
内存管理:及时释放不再使用的资源,避免内存泄漏和占用。

BitmapFactory.Options options = new BitmapFactory.Options();
options.inDensity = DisplayMetrics.DENSITY_HIGH; // 设置资源的DPI
options.inTargetDensity = getResources().getDisplayMetrics().densityDpi; // 设置当前设备的DPI
options.inScaled = true; // 开启资源缩放

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image, options);

61.Bitmap全面解析-ndk层源码。

线上用户oom,如何dump内存-然后app重启,让应用内存重启。

1.Bitmap 的大小计算

根据手机机型分辨率、存放的文件夹 = Bitmap.width Bitmap.height4.

图片尺寸 865582的 bg_person.png图片,存放在xxhdpi目录下。手机5.5英寸,像素19201080,占用多大内存。
一个像素占用4个字节。8655824 = 20111392字节。

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true; // 只获取原图宽高,不是Bitmap的最终宽高。
// options.inScaled=false; // 不压缩图片
// options.inSampleSize =2; // 图片的宽高都÷2

Bitmap bitmap = BitmapFactory.decodeResource(getResource(), R.drawable.bg_person, options);
Log.e("tag", "bitmap size= " + bitmap.getByteCount()); // 实际输出:1539216
Log.e("tag", "bitmap width= " + bitmap.getWidth()); // 756
Log.e("tag", "bitmap height= " + bitmap.getHeight()); // 509

1.1 代码执行时序。

BitmapFactory.decodeResource()
--> decodeResource(res, id,null)
--> bm = decodeResourceStream(res,value, is,null,opt);
opts.inTargetDensity = res.getDisplayMetrics().densityDpi(); //获取手机dpi
--> decodeStream(is, pad, opts)
--> bm = decodeStreamInternal(is, outPadding, opts);
--> return nativeDecodeStream(is,tempStorage,outPadding,opts);
BitmapFactory:: nativeDecodeStream 真正解析bitmap

Native 层方法:
手机屏幕像素密度dip = 1英寸所占的像素 = 开根号(10801080 +19201920)/5.24 = 420 【Letv x600】
420/160 = 2.5 像素。1dp在不同手机上,显示的像素值不一样。
scale = (float) targetDensity / density; // 计算压缩比例
= 420/480 = 0.875
SkISize size = codec->getSampledDimensions(sampleSize); // 拿到原图size:864*582

if (needsFineScale(codec->getInfo().dimensions(), size, sampleSize)) { // 是否需要scale
    willScale = true;
    scaledWidth = codec->getInfo().width() / sampleSize;
    scaledHeight = codec->getInfo().height() / sampleSize;
}
// sampleSize=2; 长宽都除2.
if (onlyDecodeSize) { // 仅解析宽高:options.inJustDecodeBounds=true; 
      return nullptr;
}
if (scale != 1.0f) { // scale=0.875
    willScale = true;
    scaledWidth = static_cast<int>(scaledWidth * scale + 0.5f); // 864*0.875=756
    scaledHeight = static_cast<int>(scaledHeight * scale + 0.5f); // 582*0.875=509
} // 0.5f 是比较重要的,向上取整数。
// InputStream: is输入流
// opts: inDensity=480; inTargetDensity=手机的dip= 手机屏幕像素密度 = 420
private static native Bitmap nativeDecodeStream(InputStream is,byte[] storage, Rect padding,Options opts);
//  /frameworks/base/core/jni/android/graphics/BitmapFactory.cpp

// android_graphics_BitmapFactory_nativeDecodeStream() // 静态注册
// 动态注册,一一匹配。直接写方法名。匹配nativeDecodeStream()
static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage,
        jobject padding, jobject options) {

    jobject bitmap = NULL;
    std::unique_ptr<SkStream> stream(CreateJavaInputStreamAdaptor(env, is, storage));

    if (stream.get()) {
        std::unique_ptr<SkStreamRewindable> bufferedStream(
                SkFrontBufferedStream::Create(stream.release(), SkCodec::MinBufferedBytesNeeded()));
        SkASSERT(bufferedStream.get() != NULL);
        bitmap = doDecode(env, bufferedStream.release(), padding, options); // 重要方法
    }
    return bitmap;
}
static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding, jobject options) {
    // This function takes ownership of the input stream.  Since the SkAndroidCodec
    // will take ownership of the stream, we don't necessarily need to take ownership
    // here.  This is a precaution - if we were to return before creating the codec,
    // we need to make sure that we delete the stream.
    std::unique_ptr<SkStreamRewindable> streamDeleter(stream);

    // Set default values for the options parameters.
    int sampleSize = 1;
    bool onlyDecodeSize = false; // 只解析宽高
    SkColorType prefColorType = kN32_SkColorType;
    bool isHardware = false;
    bool isMutable = false;
    float scale = 1.0f;
    bool requireUnpremultiplied = false;
    jobject javaBitmap = NULL;
    sk_sp<SkColorSpace> prefColorSpace = nullptr;

    // Update with options supplied by the client.
    if (options != NULL) {
        sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
        // Correct a non-positive sampleSize.  sampleSize defaults to zero within the
        // options object, which is strange.
        if (sampleSize <= 0) {
            sampleSize = 1;
        }

        if (env->GetBooleanField(options, gOptions_justBoundsFieldID)) {
            onlyDecodeSize = true; // 是否只解析尺寸
        }
        // initialize these, in case we fail later on
        env->SetIntField(options, gOptions_widthFieldID, -1);
        env->SetIntField(options, gOptions_heightFieldID, -1);
        env->SetObjectField(options, gOptions_mimeFieldID, 0);
        env->SetObjectField(options, gOptions_outConfigFieldID, 0);
        env->SetObjectField(options, gOptions_outColorSpaceFieldID, 0);

        jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
        prefColorType = GraphicsJNI::getNativeBitmapColorType(env, jconfig);
        jobject jcolorSpace = env->GetObjectField(options, gOptions_colorSpaceFieldID);
        prefColorSpace = GraphicsJNI::getNativeColorSpace(env, jcolorSpace);
        isHardware = GraphicsJNI::isHardwareConfig(env, jconfig);
        isMutable = env->GetBooleanField(options, gOptions_mutableFieldID); 
        requireUnpremultiplied = !env->GetBooleanField(options, gOptions_premultipliedFieldID);
        javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID); // java层bitmap用来服用

        if (env->GetBooleanField(options, gOptions_scaledFieldID)) { // inScaleed=false,加载的图片就是png图原来的大小。
            const int density = env->GetIntField(options, gOptions_densityFieldID);
            const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID);
            const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID);
            if (density != 0 && targetDensity != 0 && density != screenDensity) {
                scale = (float) targetDensity / density; // 1.计算压缩比例
           }
        }
    }

    if (isMutable && isHardware) {
        doThrowIAE(env, "Bitmaps with Config.HARWARE are always immutable");
        return nullObjectReturn("Cannot create mutable hardware bitmap");
    }

    // Create the codec.
    NinePatchPeeker peeker;
    std::unique_ptr<SkAndroidCodec> codec(SkAndroidCodec::NewFromStream(
            streamDeleter.release(), &peeker));
    if (!codec.get()) {
        return nullObjectReturn("SkAndroidCodec::NewFromStream returned null");
    }

    // Do not allow ninepatch decodes to 565.  In the past, decodes to 565
    // would dither, and we do not want to pre-dither ninepatches, since we
    // know that they will be stretched.  We no longer dither 565 decodes,
    // but we continue to prevent ninepatches from decoding to 565, in order
    // to maintain the old behavior.
    if (peeker.mPatch && kRGB_565_SkColorType == prefColorType) {
        prefColorType = kN32_SkColorType;
    }

    // Determine the output size.
    SkISize size = codec->getSampledDimensions(sampleSize); // 2.拿到原图size

    int scaledWidth = size.width();
    int scaledHeight = size.height();
    bool willScale = false;

    // Apply a fine scaling step if necessary.
    if (needsFineScale(codec->getInfo().dimensions(), size, sampleSize)) { // 是否需要scale
        willScale = true;
        scaledWidth = codec->getInfo().width() / sampleSize;
        scaledHeight = codec->getInfo().height() / sampleSize;
    }

    // Set the decode colorType
    SkColorType decodeColorType = codec->computeOutputColorType(prefColorType);
    sk_sp<SkColorSpace> decodeColorSpace = codec->computeOutputColorSpace(
            decodeColorType, prefColorSpace);

    // Set the options and return if the client only wants the size.
    if (options != NULL) {
        jstring mimeType = encodedFormatToString(
                env, (SkEncodedImageFormat)codec->getEncodedFormat());
        if (env->ExceptionCheck()) {
            return nullObjectReturn("OOM in encodedFormatToString()");
        }
        env->SetIntField(options, gOptions_widthFieldID, scaledWidth); // 设置图片大小.
        env->SetIntField(options, gOptions_heightFieldID, scaledHeight);
        env->SetObjectField(options, gOptions_mimeFieldID, mimeType);

        jint configID = GraphicsJNI::colorTypeToLegacyBitmapConfig(decodeColorType);
        if (isHardware) {
            configID = GraphicsJNI::kHardware_LegacyBitmapConfig;
        }
        jobject config = env->CallStaticObjectMethod(gBitmapConfig_class,
                gBitmapConfig_nativeToConfigMethodID, configID);
        env->SetObjectField(options, gOptions_outConfigFieldID, config);

        env->SetObjectField(options, gOptions_outColorSpaceFieldID,
                GraphicsJNI::getColorSpace(env, decodeColorSpace, decodeColorType));

        if (onlyDecodeSize) { 
            return nullptr;
        }
    }

    // Scale is necessary due to density differences.
    if (scale != 1.0f) {
        willScale = true;
        scaledWidth = static_cast<int>(scaledWidth * scale + 0.5f);
        scaledHeight = static_cast<int>(scaledHeight * scale + 0.5f);
    }
    android::Bitmap* reuseBitmap = nullptr;
    unsigned int existingBufferSize = 0;
    if (javaBitmap != NULL) {
        reuseBitmap = &bitmap::toBitmap(env, javaBitmap);
        if (reuseBitmap->isImmutable()) {
            ALOGW("Unable to reuse an immutable bitmap as an image decoder target.");
            javaBitmap = NULL;
            reuseBitmap = nullptr;
        } else {
            existingBufferSize = bitmap::getBitmapAllocationByteCount(env, javaBitmap);
        }
    }

    HeapAllocator defaultAllocator;
    RecyclingPixelAllocator recyclingAllocator(reuseBitmap, existingBufferSize); // 开辟内存
    ScaleCheckingAllocator scaleCheckingAllocator(scale, existingBufferSize);
    SkBitmap::HeapAllocator heapAllocator;
    SkBitmap::Allocator* decodeAllocator;
    if (javaBitmap != nullptr && willScale) {
        // This will allocate pixels using a HeapAllocator, since there will be an extra
        // scaling step that copies these pixels into Java memory.  This allocator
        // also checks that the recycled javaBitmap is large enough.
        decodeAllocator = &scaleCheckingAllocator;
    } else if (javaBitmap != nullptr) {
        decodeAllocator = &recyclingAllocator;
    } else if (willScale || isHardware) { // 硬件加速
        // This will allocate pixels using a HeapAllocator,
        // for scale case: there will be an extra scaling step.
        // for hardware case: there will be extra swizzling & upload to gralloc step.
        decodeAllocator = &heapAllocator;
    } else {
        decodeAllocator = &defaultAllocator;
    }

    SkAlphaType alphaType = codec->computeOutputAlphaType(requireUnpremultiplied);
    const SkImageInfo decodeInfo = SkImageInfo::Make(size.width(), size.height(),
            decodeColorType, alphaType, decodeColorSpace);

    // For wide gamut images, we will leave the color space on the SkBitmap.  Otherwise,
    // use the default.
    SkImageInfo bitmapInfo = decodeInfo;
    if (decodeInfo.colorSpace() && decodeInfo.colorSpace()->isSRGB()) {
        bitmapInfo = bitmapInfo.makeColorSpace(GraphicsJNI::colorSpaceForType(decodeColorType));
    }

    if (decodeColorType == kGray_8_SkColorType) {
        // The legacy implementation of BitmapFactory used kAlpha8 for
        // grayscale images (before kGray8 existed).  While the codec
        // recognizes kGray8, we need to decode into a kAlpha8 bitmap
        // in order to avoid a behavior change.
        bitmapInfo =
                bitmapInfo.makeColorType(kAlpha_8_SkColorType).makeAlphaType(kPremul_SkAlphaType);
    }
    SkBitmap decodingBitmap; // decodingBitmap是原图, tryAllocPixels 去开辟内存,decodeAllocator 需要缩放用的是SKBitmap::HepAllocator开辟内存
    if (!decodingBitmap.setInfo(bitmapInfo) ||
            !decodingBitmap.tryAllocPixels(decodeAllocator)) {
        // SkAndroidCodec should recommend a valid SkImageInfo, so setInfo()
        // should only only fail if the calculated value for rowBytes is too
        // large.
        // tryAllocPixels() can fail due to OOM on the Java heap, OOM on the
        // native heap, or the recycled javaBitmap being too small to reuse.
        return nullptr;
    }

    // Use SkAndroidCodec to perform the decode.
    SkAndroidCodec::AndroidOptions codecOptions;
    codecOptions.fZeroInitialized = decodeAllocator == &defaultAllocator ?
            SkCodec::kYes_ZeroInitialized : SkCodec::kNo_ZeroInitialized;
    codecOptions.fSampleSize = sampleSize;
    SkCodec::Result result = codec->getAndroidPixels(decodeInfo, decodingBitmap.getPixels(),
            decodingBitmap.rowBytes(), &codecOptions); // 获取像素
    switch (result) {
        case SkCodec::kSuccess:
        case SkCodec::kIncompleteInput:
            break;
        default:
            return nullObjectReturn("codec->getAndroidPixels() failed.");
    }
    jbyteArray ninePatchChunk = NULL;
    if (peeker.mPatch != NULL) {
        if (willScale) {
            scaleNinePatchChunk(peeker.mPatch, scale, scaledWidth, scaledHeight);
        }

        size_t ninePatchArraySize = peeker.mPatch->serializedSize();
        ninePatchChunk = env->NewByteArray(ninePatchArraySize);
        if (ninePatchChunk == NULL) {
            return nullObjectReturn("ninePatchChunk == null");
        }

        jbyte* array = (jbyte*) env->GetPrimitiveArrayCritical(ninePatchChunk, NULL);
        if (array == NULL) {
            return nullObjectReturn("primitive array == null");
        }

        memcpy(array, peeker.mPatch, peeker.mPatchSize);
        env->ReleasePrimitiveArrayCritical(ninePatchChunk, array, 0);
    }
451
452    jobject ninePatchInsets = NULL;
453    if (peeker.mHasInsets) {
454        ninePatchInsets = env->NewObject(gInsetStruct_class, gInsetStruct_constructorMethodID,
455                peeker.mOpticalInsets[0], peeker.mOpticalInsets[1],
456                peeker.mOpticalInsets[2], peeker.mOpticalInsets[3],
457                peeker.mOutlineInsets[0], peeker.mOutlineInsets[1],
458                peeker.mOutlineInsets[2], peeker.mOutlineInsets[3],
459                peeker.mOutlineRadius, peeker.mOutlineAlpha, scale);
460        if (ninePatchInsets == NULL) {
461            return nullObjectReturn("nine patch insets == null");
462        }
463        if (javaBitmap != NULL) {
464            env->SetObjectField(javaBitmap, gBitmap_ninePatchInsetsFieldID, ninePatchInsets);
465        }
466    }
467
468    SkBitmap outputBitmap; // 真正最后返回的图片
469    if (willScale) { // 需要压缩
470        // This is weird so let me explain: we could use the scale parameter
471        // directly, but for historical reasons this is how the corresponding
472        // Dalvik code has always behaved. We simply recreate the behavior here.
473        // The result is slightly different from simply using scale because of
474        // the 0.5f rounding bias applied when computing the target image size
475        const float sx = scaledWidth / float(decodingBitmap.width());
476        const float sy = scaledHeight / float(decodingBitmap.height());
477
478        // Set the allocator for the outputBitmap.
479        SkBitmap::Allocator* outputAllocator;
480        if (javaBitmap != nullptr) {
481            outputAllocator = &recyclingAllocator;
482        } else {
483            outputAllocator = &defaultAllocator;
484        }
485
486        SkColorType scaledColorType = decodingBitmap.colorType();
487        // FIXME: If the alphaType is kUnpremul and the image has alpha, the
488        // colors may not be correct, since Skia does not yet support drawing
489        // to/from unpremultiplied bitmaps. 设置信息
490        outputBitmap.setInfo(
491                bitmapInfo.makeWH(scaledWidth, scaledHeight).makeColorType(scaledColorType));
492        if (!outputBitmap.tryAllocPixels(outputAllocator)) {
493            // This should only fail on OOM.  The recyclingAllocator should have
494            // enough memory since we check this before decoding using the
495            // scaleCheckingAllocator.
496            return nullObjectReturn("allocation failed for scaled bitmap");
497        }
498
499        SkPaint paint; // 画笔绘制缩放的图片。
500        // kSrc_Mode instructs us to overwrite the uninitialized pixels in
501        // outputBitmap.  Otherwise we would blend by default, which is not
502        // what we want.
503        paint.setBlendMode(SkBlendMode::kSrc);
504        paint.setFilterQuality(kLow_SkFilterQuality); // bilinear filtering
505
506        SkCanvas canvas(outputBitmap, SkCanvas::ColorBehavior::kLegacy);
507        canvas.scale(sx, sy);
508        canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint);
509    } else { 
            // 直接复制像素过去
510        outputBitmap.swap(decodingBitmap);
511    }
512
513    if (padding) {
514        if (peeker.mPatch != NULL) {
515            GraphicsJNI::set_jrect(env, padding,
516                    peeker.mPatch->paddingLeft, peeker.mPatch->paddingTop,
517                    peeker.mPatch->paddingRight, peeker.mPatch->paddingBottom);
518        } else {
519            GraphicsJNI::set_jrect(env, padding, -1, -1, -1, -1);
520        }
521    }
522
523    // If we get here, the outputBitmap should have an installed pixelref.
524    if (outputBitmap.pixelRef() == NULL) {
525        return nullObjectReturn("Got null SkPixelRef");
526    }
527
528    if (!isMutable && javaBitmap == NULL) {
529        // promise we will never change our pixels (great for sharing and pictures)
530        outputBitmap.setImmutable();
531    }
532
533    bool isPremultiplied = !requireUnpremultiplied;
534    if (javaBitmap != nullptr) {
535        bitmap::reinitBitmap(env, javaBitmap, outputBitmap.info(), isPremultiplied);
536        outputBitmap.notifyPixelsChanged();
537        // If a java bitmap was passed in for reuse, pass it back
538        return javaBitmap; // 直接使用他
539    }
540
541    int bitmapCreateFlags = 0x0;
542    if (isMutable) bitmapCreateFlags |= android::bitmap::kBitmapCreateFlag_Mutable;
543    if (isPremultiplied) bitmapCreateFlags |= android::bitmap::kBitmapCreateFlag_Premultiplied;
544
545    if (isHardware) {
546        sk_sp<Bitmap> hardwareBitmap = Bitmap::allocateHardwareBitmap(outputBitmap);
547        if (!hardwareBitmap.get()) {
548            return nullObjectReturn("Failed to allocate a hardware bitmap");
549        }
550        return bitmap::createBitmap(env, hardwareBitmap.release(), bitmapCreateFlags,
551                ninePatchChunk, ninePatchInsets, -1);
552    }
553
554    // now create the java bitmap,返回java的bitmap
555    return bitmap::createBitmap(env, defaultAllocator.getStorageObjAndReset(),
556            bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1);
557}

更换手机:HUAWEI PRA-AL00X(标准手机),
xxhdpi/bg_person.png 864582像素。
打印:1794
1080 480 图片宽高:864*582 。


更换手机:Letv x600
xxhdpi/bg_person.png 864582像素。
打印:1920
1080 420 图片宽高:756*509

总结:图片大小与 sampleSize压缩有关; 与手机像素+屏幕尺寸有关;与放的文件夹有关。
存放xhdpi文件夹中,宽高增大
打印:19201080 420 图片宽高:1134764 。

Log.e("tag", "bitmap size= " + bitmap.getByteCount()); // 实际输出:1539216
Log.e("tag", "bitmap width= " + bitmap.getWidth()); // 756
Log.e("tag", "bitmap height= " + bitmap.getHeight()); // 509

2.Bitmap 的内存开辟与销毁

    1. 从file文件读,inputStream,把文件读过来,去开辟内存;
      如果需要缩放,用的是 SKBitmap::HeapAllocator 开辟。
      不需要缩放,用的是 HeapAllocator defaultAllocator;
    1. 去读写像素值(加载的原图 864*582)
      需要缩放,还要创建一个Bitmap,用于最终返回。
      SKCanvas画,最后拿到Bitmap对象,返回给 Java层。调用了java的构造函数。
      bitmap::createBitmap(env, defaultAllocator.getStorageObjAndReset(), bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1);
    1. 不需要缩放,直接调用swap方法,直接复制
      outputBitmap.swap(decodingBitmap);

Bitmap 内存开辟总结:

8.0系统以下,放在Java内存中;8.0以上系统在native内存中。

logIM();
Bitmap bitmap = Bitmap.createBitmap(1024,1024, Bitmap.Config.ARGB_8888); // 4M的图片
Log.e("tag", "size MB = " + bitmap.getByteCount()/1024/1024); // 4
logIM(); 

private void logIM() {
    Runtime runtime= Runtime.getRuntime();
    Log.e("tag", "totalSize MB = " + runtime.maxMemory() /1024/1024); // 不同手机java层,最大内存不一样。
    Log.e("tag", "totalMemory MB = " + runtime.totalMemory() /1024/1024);
    Log.e("tag", "nativeSize MB = " + Debug.getNativeHeapAllocatedSize() /1024/1024);
}
// 第一遍:totalSize=384(256), nativeSize=4;
// 第二遍:totalSize=384(256), nativeSize=1028;

bitmap内存创建都是通过tryAllocPixels 方法来申请的。
/frameworks/base/core/jni/android/graphics/Bitmap.cpp
tryAllocPixels()
--> allocPixelRef(this, ctable)
--> android::Bitmap *GraphicsJNI:: allocateJavaPixelRef()

8.0- 以下在java内存中,8.0以后在native内存中。
auto wrapper = alloc(size, info, rowBytes, ctable); // native 申请内存

/external/skia/core/SKBitmap.cpp
原图:原图必须要开辟。native层有2G内存空间。

  • 如果需要缩放,内存开辟在native中。
  • 如果不需要缩放,8.0-开辟在java内存中;8.0以后开辟在native中。

Bitmap销毁:

7.0, 8.0都需要去销毁数据:Java层Bitmap、native层Bitmap、tryAlloc开辟的像素数组(8.0 Native, 7.0 Java层-VM)
java层,GC回收;
bitmap.recycle() 回收的是像素数据。bitmap还没有销毁,但是不能使用了。
8.0+ 回收像素数据有效;但是7.0以下 不管用(java内存),内存还是不降低,数据怎么回收?因为是GC回收的。
---- 调用对象的 finalize()

回收是6.0与  7.0的区别。
6.0通过监听对象 GC的 finalize()方法,去释放对象。
6.0+ 通过注册的观察者,applyFreeFunction() 拿到释放方法,freeFunction是C native传过来的。
nativeGetNativeFinalizer() 去回收。

// 6.0 源码

public static class BitmapFinalizer {
    // ...
    public void finalize() { // 析构函数
        super.finalize();    
        setNativeAllocationByteCount(0);
        nativeDestructor(mNativeBitmap);
        mNativeBitmap=0;
    }
}

3.Bitmap 复用(isMutable)

防止反复的去开辟和释放内存,可能会导致内存抖动,导致GC,导致卡顿。
加载的图片下面还要用bitmap1,

BitmapFactory.Options options = new BitmapFactory.Options();
options.isMutable = true; // 下次可能会拿来复用。告知系统可以复用bitmap。放置反复开辟释放内存。
Bitmap bitmap1 = BitmapFactory.decodeResource(getResource(),R.drawable.person, options);

options.inBitmap = bitmap1;
Bitmap bitmap2 = BitmapFactory.decodeResource(getResource(),R.drawable.person, options);
// bitmap1,bitmap2, 两次返回的 是同一个对象。
// 如果以后还要用bitmap1,显示到别的地方。bitmap1就不能做复用。bitmap1最好不要用来复用。

4.本地图片资源优化

xxh,xh,xxxh 图片怎么放?看ndk层的源代码。

5. 总结:

Bitmap内存开辟: 7.0系统及以下,放在Java内存中;8.0以上系统在native内存中。
回收是6.0与 7.0的区别: 6.0通过监听对象 GC的 finalize方法,释放对象。
6.0+ 通过注册的观察者,applyFreeFunction() 拿到释放方法,freeFunction是native传过来的。
佳宁对象的GC回收,来达到回收效果。

线上用户手机oom,如何处理?
资源文件应该放那个文件夹?

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,718评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,683评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,207评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,755评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,862评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,050评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,136评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,882评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,330评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,651评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,789评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,477评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,135评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,864评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,099评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,598评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,697评论 2 351

推荐阅读更多精彩内容