Android Bitmap 淺析

一、Bitmap 內(nèi)存回收

從3.0開(kāi)始秦效,Bitmap 像素?cái)?shù)據(jù)和 Bitmap 對(duì)象一起存放在 Dalvik 堆中,而在3.0之前,Bitmap 像素?cái)?shù)據(jù)存放在 Native 內(nèi)存中邓馒。
所以,在3.0之前蛾坯,Bitmap 像素?cái)?shù)據(jù)在 Nativie 內(nèi)存的釋放是不確定的光酣,容易內(nèi)存溢出而 Crash,官方強(qiáng)烈建議調(diào)用 recycle()(當(dāng)然是在確定不需要的時(shí)候)脉课;而在3.0之后救军,則是強(qiáng)調(diào)Bitmap的復(fù)用。
使用 LruCache 對(duì) Bitmap 對(duì)象進(jìn)行緩存倘零,當(dāng)再次使用到這個(gè) Bitmap 的時(shí)候直接獲取唱遭,而不用重走編碼流程。
Android3.0(API 11之后)引入了 BitmapFactory.Options.inBitmap 字段呈驶,設(shè)置此字段之后解碼方法會(huì)嘗試復(fù)用一張存在的 Bitmap 拷泽。這意味著 Bitmap 的內(nèi)存被復(fù)用,避免了內(nèi)存的回收及申請(qǐng)過(guò)程袖瞻,顯然性能表現(xiàn)更佳司致。不過(guò),使用這個(gè)字段有幾點(diǎn)限制:

  • 聲明可被復(fù)用的 Bitmap 必須設(shè)置 inMutable 為 true聋迎;
  • Android4.4(API 19)之前只有格式為 jpg脂矫、png,同等寬高(要求苛刻)霉晕,inSampleSize 為1的 Bitmap 才可以復(fù)用庭再;
  • Android4.4(API 19)之前被復(fù)用的 Bitmap 的 inPreferredConfig 會(huì)覆蓋待分配內(nèi)存的 Bitmap 設(shè)置的 inPreferredConfig;
  • Android4.4(API 19)之后被復(fù)用的 Bitmap 的內(nèi)存必須大于需要申請(qǐng)內(nèi)存的 Bitmap 的內(nèi)存缝彬;

二、Bitmap 內(nèi)存大小

Bitmap 類有兩個(gè)獲取存儲(chǔ) Bitmap 像素所占用內(nèi)存字節(jié)數(shù)的方法:

getByteCount

getByteCount() 方法是 API12 加入的哺眯,代表存儲(chǔ) Bitmap 的像素需要的最少內(nèi)存。

public final int getByteCount() {
        return getRowBytes() * getHeight();
}

getAllocationByteCount()

從 API19 開(kāi)始奶卓,加入了 getAllocationByteCount() 方法一疯,代表在內(nèi)存中為 Bitmap 分配的內(nèi)存大小夺姑,代替了 getByteCount() 方法废膘。

public final int getAllocationByteCount() {
        if (mBuffer == null) {
            //mBuffer 代表存儲(chǔ) Bitmap 像素?cái)?shù)據(jù)的字節(jié)數(shù)組丐黄。
            return getByteCount();
        }
        return mBuffer.length;
    }

在不復(fù)用 Bitmap 時(shí)艰争,getByteCount() 和 getAllocationByteCount 返回的結(jié)果是一樣的甩卓。
在通過(guò)復(fù)用 Bitmap 來(lái)解碼圖片時(shí)鹿寻,如果被復(fù)用的 Bitmap 的內(nèi)存比待分配內(nèi)存的 Bitmap 大,那么 getByteCount() 表示新解碼圖片占用內(nèi)存的大小(并非實(shí)際內(nèi)存大小,實(shí)際大小是復(fù)用的那個(gè)Bitmap的大小),getAllocationByteCount() 表示被復(fù)用 Bitmap真實(shí)占用的內(nèi)存大屑獗肌(即 mBuffer 的長(zhǎng)度)茴扁。

Bitmap 大小的計(jì)算

上面是獲取內(nèi)存大小的方法,下面是 Bitmap 占用內(nèi)存的計(jì)算公式:

Bitamp 占用內(nèi)存大小 = 寬度像素 x (inTargetDensity / inDensity) x 高度像素 x (inTargetDensity / inDensity)x 一個(gè)像素所占的內(nèi)存

上面公式中:

  • 寬度像素和高度像素就是 Bitmap 原始的寬度和高度的像素規(guī)格坯苹;
  • 一個(gè)像素所占的內(nèi)存與圖片的色彩模式有關(guān)粹湃,這個(gè)色彩模式在 Bitmap 類里面通過(guò)枚舉類 Config 標(biāo)識(shí):
 public enum Config {
        
        ALPHA_8     (1),

        RGB_565     (3),

        @Deprecated
        ARGB_4444   (4),

        ARGB_8888   (5);
    }
  • ARGB_8888:每個(gè)像素占四個(gè)字節(jié)为鳄,A偏形、R坠陈、G、B 分量各占8位乌昔,是 Android 的默認(rèn)設(shè)置溺蕉;
  • RGB_565:每個(gè)像素占兩個(gè)字節(jié),R分量占5位葫男,G分量占6位鸣剪,B分量占5位西傀;
  • ARGB_4444:每個(gè)像素占兩個(gè)字節(jié),A桶癣、R拥褂、G、B分量各占4位牙寞,成像效果比較差饺鹃;
  • Alpha_8: 只保存透明度,共8位间雀,1字節(jié)悔详;
  • 在 BitmapFactory 的內(nèi)部類 Options 有兩個(gè)成員變量 inDensity 和 inTargetDensity,其中 inDensity 就 Bitmap 的像素密度惹挟,也就是 Bitmap 的成員變量 mDensity茄螃,默認(rèn)是設(shè)備屏幕的像素密度,可以通過(guò) Bitmap#setDensity(int) 設(shè)置连锯,inTargetDensity 是圖片的目標(biāo)像素密度归苍,在加載圖片時(shí)就是 drawable 目錄的像素密度。
    在從資源目錄加載圖片時(shí)运怖,這個(gè)時(shí)候調(diào)用的是 BitmapFactory#decodeResource 方法拼弃,內(nèi)部調(diào)用的是 decodeResourceStream 方法:
public static Bitmap decodeResourceStream(Resources res, TypedValue value,
            InputStream is, Rect pad, 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;
            } 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);
    }

會(huì)根據(jù)設(shè)備屏幕像素密度到對(duì)應(yīng) drawable 目錄去尋找圖片,這個(gè)時(shí)候 inTargetDensity/inDensity = 1摇展,圖片不會(huì)做縮放吻氧,寬度和高度就是圖片原始的像素規(guī)格,如果沒(méi)有找到咏连,會(huì)到其他 drawable 目錄去找盯孙,這個(gè)時(shí)候 drawable 的屏幕像素密度就是 inTargetDensity,會(huì)根據(jù) inTargetDensity/inDensity 的比例對(duì)圖片的寬度和高度進(jìn)行縮放祟滴。

三镀梭、Bitmap 的創(chuàng)建

Bitmap 有一系列的 createXXX 方法用來(lái)創(chuàng)建 Bitmap 對(duì)象,但是我們一般都使用 BitmapFactory 的一系列 decodeXXX 方法來(lái)生成 Bitmap:

public static Bitmap decodeFile(String pathName, Options opts)
public static Bitmap decodeFile(String pathName)
public static Bitmap decodeResourceStream(Resources res, TypedValue value, InputStream is, Rect pad, Options opts)
public static Bitmap decodeResource(Resources res, int id, Options opts)
public static Bitmap decodeResource(Resources res, int id)
public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts)
public static Bitmap decodeByteArray(byte[] data, int offset, int length)
public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts)
public static Bitmap decodeStream(InputStream is)
public static Bitmap decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts)
public static Bitmap decodeFileDescriptor(FileDescriptor fd)

上面的方法大致可以分為四類:

  1. 從本地文件中解碼圖片:decodeFile踱启。從本地文件中解壓的原始圖片并不會(huì)對(duì)圖片進(jìn)行縮放报账;
  2. 從資源文件中解碼圖片:decodeResource。會(huì)根據(jù) inTargetDensity/inDensity 對(duì)圖片進(jìn)行縮放埠偿;
  3. 從輸入流中解碼圖片:decodeStream透罢。獲取網(wǎng)絡(luò)圖片的時(shí)候使用此方法,其實(shí)從本地文件和資源文件中解壓圖片冠蒋,最終調(diào)用的也是該方法羽圃;
  4. 從字節(jié)數(shù)組中解碼圖片:decodeByteArray。這個(gè)字節(jié)數(shù)組是輸入流傳化為的字節(jié)數(shù)組抖剿;
  5. decodeFileDescriptor朽寞。也是從本地文件中解碼圖片识窿,但是并不是通過(guò)流的方式解碼,比 decodeFile 方法省內(nèi)存脑融;

四喻频、Bitmap 壓縮

1.質(zhì)量壓縮

調(diào)用 Bitmap#compress 方法:

public boolean compress(CompressFormat format, int quality, OutputStream stream) {
        checkRecycled("Can't compress a recycled bitmap");
        // do explicit check before calling the native method
        if (stream == null) {
            throw new NullPointerException();
        }
        if (quality < 0 || quality > 100) {
            throw new IllegalArgumentException("quality must be 0..100");
        }
        Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "Bitmap.compress");
        boolean result = nativeCompress(mNativePtr, format.nativeInt,
                quality, stream, new byte[WORKING_COMPRESS_STORAGE]);
        Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
        return result;
    }

第一個(gè)參數(shù) format 標(biāo)識(shí)壓縮格式,CompressFormat 是一個(gè)枚舉類肘迎,有三個(gè)值甥温,是三種圖片格式 JPEG,PNG妓布,WEBP

public enum CompressFormat {
        JPEG    (0),
        PNG     (1),
        WEBP    (2);
    }
  • PNG是一種無(wú)損壓縮的圖像存儲(chǔ)格式姻蚓,相同像素寬高的圖像保存為PNG在文件大小上比JPEG往往要大的多,一般是JPEG大小的幾倍左右匣沼;
  • JPEG是一種有損壓縮的圖像存儲(chǔ)格式狰挡,不支持alpha通道,由于它具有高壓縮比释涛,在壓縮過(guò)程中把重復(fù)的數(shù)據(jù)和無(wú)關(guān)緊要的數(shù)據(jù)會(huì)選擇性的丟失圆兵,所以如果不需要用到alpha通道,那么大都圖片格式都用該格式枢贿;
  • Webp圖片格式是Google推出的一個(gè)支持alpha通道的有損壓縮格式殉农,據(jù)Google官方表明,同質(zhì)量情況下Webp圖像要比JPEG局荚、PNG圖像小25%~45%左右超凳;

第二個(gè)參數(shù) quality 表示壓縮質(zhì)量
這種方式是在保持像素的前提下改變圖片的位深及透明度等,來(lái)達(dá)到壓縮圖片的目的耀态,不會(huì)減少圖片的像素轮傍,它壓縮的是存儲(chǔ)大小,即你放到 disk 上的大小首装,但是解碼成 bitmap 后占的內(nèi)存是不變的创夜。

2.尺寸壓縮

前面的 decodeXXX 方法中有一個(gè) BitmapFactory.Options 類型的參數(shù),解碼圖片時(shí)仙逻,設(shè)置 BitmapFactory.Options 類的 inJustDecodeBounds 屬性為 true驰吓,可以在 Bitmap 不被加載到內(nèi)存的前提下,獲取 Bitmap 的原始寬高系奉。而設(shè)置 BitmapFactory.Options 的 inSampleSize 屬性可以真實(shí)的壓縮 Bitmap 占用的內(nèi)存檬贰,加載更小內(nèi)存的 Bitmap。
設(shè)置 inSampleSize 之后缺亮,Bitmap 的寬翁涤、高都會(huì)縮小 inSampleSize 倍。
inSampleSize 比1小的話會(huì)被當(dāng)做1,任何 inSampleSize 的值會(huì)被取接近2的冪值葵礼。

3.色彩模式壓縮

Bitmap 的色彩模式默認(rèn)為 Bitmap.Config.ARGB_8888号阿,可以通過(guò) BitmapFactory.Options.inPreferredConfig 屬性來(lái)修改解碼圖片的色彩模式,每個(gè)像素占用的字節(jié)減少了鸳粉,寬度和高度像素不變的情況下扔涧,占用的內(nèi)存大小也會(huì)減少;

4.Matrix

Matrix 矩陣變換赁严,可以對(duì) bitmap 進(jìn)行非常多的操作,其中一項(xiàng)是對(duì) bitmap 進(jìn)行等比縮放粉铐,這種方式可以精確的縮放到符合我們預(yù)期的 bitmap 大小疼约,奧代碼如下:

int bitmapWidth = bitmap.getWidth();
int bitmapHeight = bitmap.getHeight();
Matrix matrix = new Matrix();
float rate = computeScaleRate(bitmapWidth, bitmapHeight);
matrix.postScale(rate, rate);
Bitmap result = Bitmap.createBitmap(bitmap, 0, 0, bitmapWidth, bitmapHeight, matrix, true);

5.Bitmap#createScaledBitmap

通過(guò) public static Bitmap createScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter) 直接指定圖片壓縮后的寬度和高度

五、Bitmap 與 Drawable 的轉(zhuǎn)換

Bitmap-->Drawable

通過(guò) BitmapDrawable 的構(gòu)造方法:

@Deprecated
public BitmapDrawable(Bitmap bitmap)
public BitmapDrawable(Resources res, Bitmap bitmap)

Drawable-->Bitmap

根據(jù)已有的 Drawable 創(chuàng)建一個(gè)新的 Bitmap
public static Bitmap createBitmap(int width, int height, Config config)
BitmapDrawable#getBitmap() 方法
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蝙泼,一起剝皮案震驚了整個(gè)濱河市程剥,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌汤踏,老刑警劉巖织鲸,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異溪胶,居然都是意外死亡搂擦,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門哗脖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)瀑踢,“玉大人,你說(shuō)我怎么就攤上這事才避〕髫玻” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵桑逝,是天一觀的道長(zhǎng)棘劣。 經(jīng)常有香客問(wèn)我,道長(zhǎng)楞遏,這世上最難降的妖魔是什么茬暇? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮寡喝,結(jié)果婚禮上而钞,老公的妹妹穿的比我還像新娘。我一直安慰自己拘荡,他們只是感情好臼节,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般网缝。 火紅的嫁衣襯著肌膚如雪巨税。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天粉臊,我揣著相機(jī)與錄音草添,去河邊找鬼。 笑死扼仲,一個(gè)胖子當(dāng)著我的面吹牛远寸,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播屠凶,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼驰后,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了矗愧?” 一聲冷哼從身側(cè)響起灶芝,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎唉韭,沒(méi)想到半個(gè)月后夜涕,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡属愤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年女器,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片住诸。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡晓避,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出只壳,到底是詐尸還是另有隱情俏拱,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布吼句,位于F島的核電站锅必,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏惕艳。R本人自食惡果不足惜搞隐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望远搪。 院中可真熱鬧劣纲,春花似錦、人聲如沸谁鳍。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至绷柒,卻和暖如春志于,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背废睦。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工伺绽, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人嗜湃。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓奈应,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親购披。 傳聞我的和親對(duì)象是個(gè)殘疾皇子杖挣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

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