解析Bitmap的density

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)

界面


demo

輸出:

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è)屬性 (inDensityinTargetDensity)咽白,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 的 inDensityinTargetDensity 是用作 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ù)資源文件獲得 inDensityinTargetDensity 就是當(dāng)前設(shè)備的 density骚腥,圖片解碼后在通過 canvas 縮放 inTargetDensity / inDensity 個(gè)倍數(shù)敦间,就獲得了縮放尺寸后的 Bitmap

原文鏈接

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市束铭,隨后出現(xiàn)的幾起案子廓块,更是在濱河造成了極大的恐慌,老刑警劉巖契沫,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件带猴,死亡現(xiàn)場離奇詭異,居然都是意外死亡懈万,警方通過查閱死者的電腦和手機(jī)拴清,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來钞速,“玉大人贷掖,你說我怎么就攤上這事嫡秕】视铮” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵昆咽,是天一觀的道長驾凶。 經(jīng)常有香客問我牙甫,道長,這世上最難降的妖魔是什么调违? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任窟哺,我火速辦了婚禮,結(jié)果婚禮上技肩,老公的妹妹穿的比我還像新娘且轨。我一直安慰自己,他們只是感情好虚婿,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布旋奢。 她就那樣靜靜地躺著,像睡著了一般然痊。 火紅的嫁衣襯著肌膚如雪至朗。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天剧浸,我揣著相機(jī)與錄音锹引,去河邊找鬼。 笑死唆香,一個(gè)胖子當(dāng)著我的面吹牛嫌变,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播躬它,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼初澎,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了虑凛?” 一聲冷哼從身側(cè)響起碑宴,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎桑谍,沒想到半個(gè)月后延柠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡锣披,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年贞间,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片雹仿。...
    茶點(diǎn)故事閱讀 39,779評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡增热,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出胧辽,到底是詐尸還是另有隱情峻仇,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布邑商,位于F島的核電站摄咆,受9級特大地震影響凡蚜,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜吭从,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一朝蜘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧涩金,春花似錦谱醇、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至辆床,卻和暖如春佳晶,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背讼载。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工轿秧, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人咨堤。 一個(gè)月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓菇篡,卻偏偏與公主長得像,于是被迫代替她去往敵國和親一喘。 傳聞我的和親對象是個(gè)殘疾皇子驱还,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容