他们之间的换算:屏幕的总 dp = px / (dpi/160)
以下是 java 层的源码
类:`BitmapFactory`.`decodeResource`(Resources res, int id)
-> 类:`BitmapFactory`.decodeResource(Resources res, int id, Options opts)
-> 类:`BitmapFactory`.decodeResourceStream(@Nullable Resources res, @Nullable TypedValue value,
@Nullable InputStream is, @Nullable Rect pad, @Nullable Options opts){
if (opts == null) {
opts = new Options();
}
if (opts.inDensity == 0 && value != null) {
final int density = value.density;
if (density == TypedValue.DENSITY_DEFAULT) {
opts.inDensity = DisplayMetrics.DENSITY_DEFAULT; // 根据放到哪个文件夹决定 density 的大小
} else if (density != TypedValue.DENSITY_NONE) {
opts.inDensity = density;
}
}
// 获取当前手机设备的 dpi
if (opts.inTargetDensity == 0 && res != null) {
opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
}
}
-> 类:`BitmapFactory`.decodeStream(@Nullable InputStream is, @Nullable Rect outPadding,
@Nullable Options opts)
-> 类:`BitmapFactory`.decodeStreamInternal(@NonNull InputStream is,
@Nullable Rect outPadding, @Nullable Options opts)
// 调用 native 层的方法解析图片
-> 类:`BitmapFactory`.nativeDecodeStream(InputStream is, byte[] storage,
Rect padding, Options opts, long inBitmapHandle, long colorSpaceHandle)
private static native Bitmap nativeDecodeStream(InputStream is, byte[] storage,
Rect padding, Options opts, long inBitmapHandle, long colorSpaceHandle);
opts.inDensity = DisplayMetrics.DENSITY_DEFAULT; // 根据放到哪个文件夹决定 density 的大小
以下是 native 层的源码
类 `BitmapFactory.cpp`.nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage,
jobject padding, jobject options, jlong inBitmapHandle, jlong colorSpaceHandle)
-> 类 `BitmapFactory.cpp`.doDecode(JNIEnv* env, std::unique_ptr stream,
jobject padding, jobject options, jlong inBitmapHandle,
jlong colorSpaceHandle)
// 分析下这个方法
static jobject doDecode(JNIEnv* env, std::unique_ptr stream,
jobject padding, jobject options, jlong inBitmapHandle,
jlong colorSpaceHandle) {
// Set default values for the options parameters.
int sampleSize = 1; // option里面要大小设置的参数,代表压缩几倍 BitmapFactory.Options. `inSampleSize`
bool onlyDecodeSize = false; // 是否只获取图片的大小 BitmapFactory.Options. `inJustDecodeBounds`
SkColorType prefColorType = kN32_SkColorType;
bool isHardware = false; // 是否硬件加速
bool isMutable = false; // 是否可以复用
float scale = 1.0f; // 缩放
bool requireUnpremultiplied = false;
jobject javaBitmap = NULL;
sk_sp prefColorSpace = GraphicsJNI::getNativeColorSpace(colorSpaceHandle);
// 获取 Options 里面的属性
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);
isHardware = GraphicsJNI::isHardwareConfig(env, jconfig);
isMutable = env->GetBooleanField(options, gOptions_mutableFieldID);
requireUnpremultiplied = !env->GetBooleanField(options, gOptions_premultipliedFieldID);
javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID);
if (env->GetBooleanField(options, gOptions_scaledField ID)) {
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 图片存放的文件夹不一样 scale 的值就不一样
scale = (float) targetDensity / density; // scale = 420/480 = 0.875
}
}
}
SkISize size = codec->getSampledDimensions(sampleSize);
int scaledWidth = size.width(); // 864
int scaledHeight = size.height(); // 582
bool willScale = false;
// Apply a fine scaling step if necessary. 如果 sampleSize != 1 的话就会进入到这个方法中 进行尺寸压缩
if (needsFineScale(codec->getInfo().dimensions(), size, sampleSize)) {
willScale = true;
scaledWidth = codec->getInfo().width() / sampleSize;
scaledHeight = codec->getInfo().height() / sampleSize;
}
// Scale is necessary due to density differences. scale = 420/480 = 0.875
if (scale != 1.0f) { // 0.833333
willScale = true;
// 0.5f 是很重要的
scaledWidth = static_cast(scaledWidth * scale + 0.5f); // 756
scaledHeight = static_cast(scaledHeight * scale + 0.5f); // 509
}
...
}
内存申请因为版本不同而有所区别:
Bitmap 的内存申请不同版本间有些许差异,在 3.0-7.0
的 bitmap 像素内存都是存放在 Java heap
中的,而 8.0
以后则是放在 Native heap
中的
看下面代码
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
logMemory();
Bitmap bitmap = Bitmap.createBitmap(1024, 1024 * 500, Bitmap.Config.ARGB_8888);
logMemory();
}
private void logMemory() {
ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
activityManager.getMemoryInfo(memoryInfo);
Log.e("TAG", "AvailMem :" + memoryInfo.availMem / 1024 / 1024);
Log.e("TAG", "lowMemory:" + memoryInfo.lowMemory);
Log.e("TAG", "NativeHeapAllocatedSize :" + Debug.getNativeHeapAllocatedSize() / 1024 / 1024);
}
上面我们创建了一张 2G 大小的 bitmap 我们在 8.0 以下的版本运行是会 OOM 的,而我们在 8.0 以上的版本运行是完全没问题,但 Native 内存多了 2G 的内存。
E/TAG: AvailMem :1654
E/TAG: lowMemory:false
E/TAG: NativeHeapAllocatedSize :4
E/TAG: AvailMem :1656
E/TAG: lowMemory:false
E/TAG: NativeHeapAllocatedSize :2052
通过之前的源码分析可知 bitmap 的内存创建都是通过 tryAllocPixels
方法来申请的
8.0 以上像素内存在 Native 内存中
auto wrapper = alloc(size, info, rowBytes, ctable);
看一下源码(8.0以上):
类 `BitmapFactory.cpp`.doDecode(JNIEnv* env, std::unique_ptr stream,
jobject padding, jobject options, jlong inBitmapHandle,
jlong colorSpaceHandle)
static jobject doDecode(JNIEnv* env, std::unique_ptr stream,
jobject padding, jobject options, jlong inBitmapHandle,
jlong colorSpaceHandle) {
// 原图
SkBitmap decodingBitmap;
if (!decodingBitmap.setInfo(bitmapInfo) ||
!decodingBitmap.tryAllocPixels(decodeAllocator)) { // tryAllocPixels 原图加载在了 native 层
// 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.");
}
...
// 返回的图片
SkBitmap outputBitmap;
if (willScale) { // 如果有压缩
// Set the allocator for the outputBitmap.
SkBitmap::Allocator* outputAllocator;
if (javaBitmap != nullptr) {
outputAllocator = &recyclingAllocator;
} else {
outputAllocator = &defaultAllocator;
}
SkColorType scaledColorType = decodingBitmap.colorType();
// FIXME: If the alphaType is kUnpremul and the image has alpha, the
// colors may not be correct, since Skia does not yet support drawing
// to/from unpremultiplied bitmaps.
outputBitmap.setInfo(
bitmapInfo.makeWH(scaledWidth, scaledHeight).makeColorType(scaledColorType));
if (!outputBitmap.tryAllocPixels(outputAllocator)) { // 输出的图片加载在 navtive 层
// This should only fail on OOM. The recyclingAllocator should have
// enough memory since we check this before decoding using the
// scaleCheckingAllocator.
return nullObjectReturn("allocation failed for scaled bitmap");
}
SkPaint paint;
// kSrc_Mode instructs us to overwrite the uninitialized pixels in
// outputBitmap. Otherwise we would blend by default, which is not
// what we want.
paint.setBlendMode(SkBlendMode::kSrc);
paint.setFilterQuality(kLow_SkFilterQuality); // bilinear filtering
SkCanvas canvas(outputBitmap, SkCanvas::ColorBehavior::kLegacy);
canvas.scale(scaleX, scaleY);
canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint);
} else {
outputBitmap.swap(decodingBitmap); // 否则返回原图的复制对象,也是 native 层
}
}
如果是 8.0
我们手动调用 recycle
方法,数据是会立即释放的
,因为像素数据本身就是在 Native 层开辟的。但如果是在 8.0 以下
,就算我们手动调用 recycle 方法,数据也是不会立即释放的
,
而是 DeleteWeakGlobalRef 交由 Java GC 来回收
。建议大家翻译一下 recycle 方法注释。注意:以上的所说的释放数据仅代表释放像素数据
,并未释放 Native 层的 Bitmap 对象。
总结:其实无论是 Android M 前还是之后,释放 Native 层的 Bitmap 对象的思想都是去监听 Java 层的 Bitmap 是否被释放,一旦当 Java 层的 Bitmap 对象被释放则立即去释放 Native 层的 Bitmap 。只不过 Android M 前是基于 Java 的 GC 机制,而 Android M 后是注册 native 的 Finalizer 方法。
内存复用好处:防止反复的去开辟和释放内存,内存抖动 -> GC -> 卡顿
Bitmap 绝对是我们 Android 开发中最容易引起 OOM 的对象之一,因为其占用的像素数据内存比较大,而加载图片又是很常见的操作。如果不断反复的去开辟和销毁 Bitmap 数据内存,势必可能会引起应用的内存抖动,因此 Google 的开发者也为我们想了一些办法,那就是允许 Bitmap 内存复用,具体如下:
必须为 Mutable(通过 BitmapFactory.Options 设置),然后 options.inBitmap = bitmap1,设置 inBitmap复用的Bitmap是谁
// 不复用的写法,消耗内存 32 M
logMemory();
Bitmap bitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.test2);
Bitmap bitmap2 = BitmapFactory.decodeResource(getResources(), R.drawable.test2);
logMemory();
// 复用的写法,消耗内存 16 M
logMemory();
BitmapFactory.Options options = new BitmapFactory.Options();
options.inMutable = true;
Bitmap bitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.test2, options);
options.inBitmap = bitmap1;
Bitmap bitmap2 = BitmapFactory.decodeResource(getResources(), R.drawable.test2, options);
logMemory();
做效果的时候,最好按比例去计算
首先 OOM
是可以 try catch
的,但是一般不这么做。
可以在 崩溃日志收集那儿 之前进行 dump 内存,怎么 dump 内存 可以参考 leakCanny