源码在线查看: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像素。
打印:17941080 480 图片宽高:864*582 。
更换手机:Letv x600
xxhdpi/bg_person.png 864582像素。
打印:19201080 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 的内存开辟与销毁
- 从file文件读,inputStream,把文件读过来,去开辟内存;
如果需要缩放,用的是 SKBitmap::HeapAllocator 开辟。
不需要缩放,用的是 HeapAllocator defaultAllocator;
- 从file文件读,inputStream,把文件读过来,去开辟内存;
- 去读写像素值(加载的原图 864*582)
需要缩放,还要创建一个Bitmap,用于最终返回。
SKCanvas画,最后拿到Bitmap对象,返回给 Java层。调用了java的构造函数。
bitmap::createBitmap(env, defaultAllocator.getStorageObjAndReset(), bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1);
- 去读写像素值(加载的原图 864*582)
- 不需要缩放,直接调用swap方法,直接复制
outputBitmap.swap(decodingBitmap);
- 不需要缩放,直接调用swap方法,直接复制
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,如何处理?
资源文件应该放那个文件夹?