Bitmap 是內(nèi)存優(yōu)化逃不了的一個(gè)東西,本文探討下,Bitmap 中的 density 到底是什么東西嘁捷,它是如何影響到內(nèi)存的使用的
先看下 density 的文檔注釋
/**
* <p>Returns the density for this bitmap.</p>
*
* <p>The default density is the same density as the current display,
* unless the current application does not support different screen
* densities in which case it is
* {@link android.util.DisplayMetrics#DENSITY_DEFAULT}. Note that
* compatibility mode is determined by the application that was initially
* loaded into a process -- applications that share the same process should
* all have the same compatibility, or ensure they explicitly set the
* density of their bitmaps appropriately.</p>
*
* @return A scaling factor of the default density or {@link #DENSITY_NONE}
* if the scaling factor is unknown.
*
*/
簡單來說 density 是用來繪制縮放用的绰上,默認(rèn)情況下的 density 就是屏幕的 density(resources.displayMetrics.densityDpi
),假如我修改了一張 Bitmap 的 density若治,那么圖片的顯示應(yīng)該會發(fā)生縮放,寫個(gè)簡單的 demo 驗(yàn)證下
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.male_xhdpi)
Log.i("Bitmap", "display's density: ${resources.displayMetrics.densityDpi}")
Log.i("Bitmap", "source bitmap's density: ${bitmap.density}")
Log.i("Bitmap", "source bitmap width: ${bitmap.width}, height: ${bitmap.height}")
img_src.setImageBitmap(bitmap)
val smallBitmap = bitmap.copy(bitmap.config, bitmap.isMutable)
Log.i("Bitmap", "smallBitmap width: ${smallBitmap.width}, height: ${smallBitmap.height}")
smallBitmap.density = bitmap.density * 2
img_small.setImageBitmap(smallBitmap)
val bigBitmap = bitmap.copy(bitmap.config, bitmap.isMutable)
Log.i("Bitmap", "bigBitmap width: ${bigBitmap.width}, height: ${bigBitmap.height}")
bigBitmap.density = bitmap.density / 2
img_big.setImageBitmap(bigBitmap)
界面
輸出:
display's density: 420
source bitmap's density: 420
source bitmap width: 360, height: 360
smallBitmap width: 360, height: 360
bigBitmap width: 360, height: 360
從輸出可以看出,Bitmap 的 density 只是會影響到顯示而已往弓,并不會影響到 Bitmap 本身的大小,所以這個(gè)屬性不會影響到內(nèi)存占用過多的問題
界面中可以看出蓄氧,density 導(dǎo)致圖像的縮小一倍和放大一倍
那么影響內(nèi)存的是什么呢函似,我們知道把一張 xxhdpi 的圖片放到 xhdpi 中是不行的,這樣會導(dǎo)致圖片擴(kuò)大喉童,這里的擴(kuò)大是指圖片本身內(nèi)存占用的擴(kuò)大撇寞,而不是顯示上面的,跟蹤下 BitmapFactory.decodeResource(resources, R.drawable.male_xhdpi)
的代碼調(diào)用堂氯,跟蹤到 decodeResourceStream()
函數(shù)的時(shí)候蔑担,發(fā)現(xiàn)了 density 的身影
public static Bitmap decodeResourceStream(Resources res, TypedValue value,
InputStream is, Rect pad, Options opts) {
validate(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;
} else if (density != TypedValue.DENSITY_NONE) {
opts.inDensity = density;
}
}
if (opts.inTargetDensity == 0 && res != null) {
opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
}
return decodeStream(is, pad, opts);
}
發(fā)現(xiàn) Options 里面有兩個(gè)屬性 (inDensity
,inTargetDensity
)咽白,inTargetDensity
被賦值成了當(dāng)前設(shè)備的像素密度啤握,那么 inDensity
被賦值成啥了呢?代碼中看是賦值成了參數(shù) value 的 density晶框,回溯函數(shù)看看 value.density 是哪里來的
public static Bitmap decodeResource(Resources res, int id, Options opts) {
// ...
try {
final TypedValue value = new TypedValue();
is = res.openRawResource(id, value);
}
// ...
}
TypeValue 的值只有在這里有寫入現(xiàn)象排抬,猜測是根據(jù) resource 的等級來賦值的懂从,比如 xhdpi 就是 320dpi,xxhdpi 就是 480dpi(這些數(shù)值可以在官網(wǎng)查看)蹲蒲,寫段代碼測試下
val xhdpiValue = TypedValue()
resources.openRawResource(R.drawable.male_xhdpi, xhdpiValue)
Log.i("Bitmap", "xhdpi's density: ${xhdpiValue.density}")
val xxhdpiValue = TypedValue()
resources.openRawResource(R.drawable.male_xxhdpi, xxhdpiValue)
Log.i("Bitmap", "xhdpi's density: ${xxhdpiValue.density}")
輸出
xhdpi's density: 320
xxhdpi's density: 480
恩莫绣,看來這個(gè)推測很準(zhǔn)確
既然知道了這兩個(gè)數(shù)值是什么意義,那么繼續(xù)跟蹤代碼悠鞍,跟蹤后發(fā)現(xiàn)最后調(diào)用的是一個(gè) native 函數(shù)
private static native Bitmap nativeDecodeStream(InputStream is, byte[] storage,
Rect padding, Options opts);
去 androidXRef 看下這部分的代碼
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);
// 在這里進(jìn)行解碼
bitmap = doDecode(env, bufferedStream.release(), padding, options);
}
return bitmap;
}
// 解碼函數(shù)
static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream
, jobject padding, jobject options) {
// ... 忽略部分代碼
if (env->GetBooleanField(options, gOptions_scaledFieldID)) {
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) {
// 這里通過 density 計(jì)算縮放數(shù)值
scale = (float) targetDensity / density;
}
}
// ...
// Scale is necessary due to density differences.
if (scale != 1.0f) {
willScale = true;
// 計(jì)算出縮放后的 width 和 height
scaledWidth = static_cast<int>(scaledWidth * scale + 0.5f);
scaledHeight = static_cast<int>(scaledHeight * scale + 0.5f);
}
// ...
if (willScale) {
// 計(jì)算需要縮放的比例
const float sx = scaledWidth / float(decodingBitmap.width());
const float sy = scaledHeight / float(decodingBitmap.height());
// ...
// 可以看出把解碼的 Bitmap 通過 canvas 縮放后繪制到 outBitmap对室,用于返回
SkCanvas canvas(outputBitmap);
canvas.scale(sx, sy);
canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint);
}
}
忽略了多余的代碼,分析部分看中文注釋
可以在代碼中看到咖祭,Options 的 inDensity
和 inTargetDensity
是用作 Bitmap 的縮放用的掩宜,此縮放并不是視覺上的縮放,而是縮放了 Bitmap 的真正尺寸么翰,那么這里就需要注意內(nèi)存上的消耗了牺汤,不要把 xxhdpi 的圖片放到 xhdpi 下面
對于上面的
inScreenDensity
,在decodeResource
的流程里面并沒有發(fā)現(xiàn)它的賦值過程浩嫌,那么它肯定是初始值 0檐迟,查看了下注釋,他表示當(dāng)前 display 設(shè)備的 density码耐,如果inScreenDensity == density
就不會對圖片進(jìn)行縮放
總結(jié)下過程:
通過 resource 獲取 Bitmap 的時(shí)候追迟,先根據(jù)資源文件獲得 inDensity
,inTargetDensity
就是當(dāng)前設(shè)備的 density骚腥,圖片解碼后在通過 canvas 縮放 inTargetDensity / inDensity
個(gè)倍數(shù)敦间,就獲得了縮放尺寸后的 Bitmap