ImageLoader 相關(guān)知識(shí)點(diǎn)

BitmapFactory

我們不能夠通過(guò)構(gòu)造函數(shù)創(chuàng)建Bitmap對(duì)象聚蝶。如果需要將圖片轉(zhuǎn)成Bitmap對(duì)象加載到內(nèi)存中,就需要使用BitmapFactory類(lèi)姚建。BitmapFactory跟據(jù)圖片數(shù)據(jù)源的不同淑趾,提供了幾類(lèi)獲取Bitmap的方法。如下:

數(shù)據(jù)源類(lèi)型 方法
byte[] decodeByteArray(byte[] data, int offset, int length,BitmapFactory.Options opts)
byte[] decodeByteArray(byte[] data, int offset, int length)
File decodeFile(String pathName, BitmapFactory.Options opts)
File decodeFile(String pathName)
FileDescriptor decodeFileDescriptor(FileDescriptor fd)
FileDescriptor decodeFileDescriptor(FileDescriptor fd, BitmapFactory.Options opts)
Resource decodeResource(Resource res, int id)
Resource decodeResource(Resource res, int id, BitmapFactory.Options opts)
ResourceStream decodeResourceStream(Resource res, TypedValue value,InputStream is,Rect pad,BitmapFactory.Options opts)
Stream decodeStream(InputStream is)
Stream decodeStream(InputStream is, Rect outPadding, BitmapFactory.Options opts)

BitmapFactory.Options

從上面的表格可以看出攒霹,每一類(lèi)數(shù)據(jù)源的解碼方法都有兩個(gè)怯疤。其中一個(gè)都有一個(gè)BitmapFactory.Options參數(shù)。這個(gè)參數(shù)對(duì)解碼進(jìn)行了配置催束。
它的可選參數(shù)如下:

參數(shù) 作用
inBitmap : Bitmap 重用一個(gè)Bitmap對(duì)象
inDensity : int 這張圖片解碼使用的屏幕密度
inDither : boolean deprecated in api-24, 如果設(shè)置改選項(xiàng)集峦,那么解碼的時(shí)候會(huì)嘗試防抖動(dòng)處理
inInputShareable : boolean deprecated in api-21
inJustDecodeBounds : boolean 如果設(shè)置該選項(xiàng),返回值為null抠刺。但是可以從Options對(duì)象中獲取Bitmap的寬高
inMutable : boolean 如果設(shè)置塔淤,將會(huì)解碼出一個(gè)可更改的Bitmap對(duì)象,而不是不可更改的
inPreferQualityOverSpeed : boolean deprecated in api-24, 設(shè)置它會(huì)犧牲時(shí)間效率速妖,提升圖片的質(zhì)量
inPreferredConfig : Bitmap.Config 這里設(shè)置Bitmap的像素存儲(chǔ)格式高蜂,也就是Bitmap的config對(duì)象
inPremutiplied : boolean 默認(rèn)為true,與dither類(lèi)似是一種圖像處理的方式
inPurgeable : boolean deprecated in api-21
inSampleSize : int 如果值大于1罕容,那么生成一個(gè)縮略版的Bitmap
inScaled : boolean 如果設(shè)置為true备恤,并且inDensity和inTargetDensity不一致的時(shí)候稿饰,那么生成的bitmap會(huì)按照inTargetDensity的密度縮放,而不是系統(tǒng)提供的密度
inScreenDensity : int 屏幕的真實(shí)密度
inTargetDensity : int 這張bitmap繪制的屏幕密度
inTempStorage : byte[] 解碼用的臨時(shí)內(nèi)存區(qū)域
mCancel : boolean deprecated in api 24
outHeight : int Bitmap的高度
outWidth : int Bitmap的寬度
outMimeType: int 解碼圖片的MimeType

減少內(nèi)存占用:

原理:

解碼圖片設(shè)置縮放比例可以減少Bitmap對(duì)象的內(nèi)存占用露泊。關(guān)鍵的參數(shù)是inSampleSize參數(shù)喉镰。舉個(gè)例子:

一張2018 x 1536 的圖片如果完全解碼為(ARGB_8888)的Bitmap,那么他的內(nèi)存占用為 2048 * 1536 * 4=12M惭笑;
如果設(shè)置inSampleSize為4侣姆,那么最終Bitmap對(duì)象的尺寸為512 x 384。內(nèi)存占用為512 * 384 * 4 = 0.75M沉噩;

也就是inSampleSize = n的時(shí)候捺宗,內(nèi)存占用為1/(n * n) n=1, 2, 4, 8, 16 .....

使用方法:

  1. 計(jì)算圖片的尺寸
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
  1. 根據(jù)期望的imageview尺寸計(jì)算縮放的倍數(shù)
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
    // Raw height and width of image
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) {

        final int halfHeight = height / 2;
        final int halfWidth = width / 2;

        // Calculate the largest inSampleSize value that is a power of 2 and keeps both
        // height and width larger than the requested height and width.
        while ((halfHeight / inSampleSize) >= reqHeight
                && (halfWidth / inSampleSize) >= reqWidth) {
            inSampleSize *= 2;
        }
    }

    return inSampleSize;
}

這里的imSampleSize的結(jié)果都是2的冪。根據(jù)inSampleSize的文檔屁擅,如果傳進(jìn)去的inSampleSize非2的冪偿凭,那么會(huì)向下取2的冪為最終縮放比例。 例如:傳 15 最終為 8派歌;傳 7最終為4弯囊;

  1. 根據(jù)inSampleSize解碼圖片
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
        int reqWidth, int reqHeight) {

    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}

這里參考一些文章的思路,根據(jù)實(shí)驗(yàn)發(fā)現(xiàn)利用BitmapFactory.Options中的Density相關(guān)的設(shè)置也可以控制圖像的大薪汗:

如果單獨(dú)設(shè)置inDensity變量匾嘱,那么只會(huì)影響到生成的Bitmap的density的值。
如果想要更根據(jù)density縮放早抠,需要同時(shí)設(shè)置三個(gè)值:

變量
inDensity 圖片數(shù)據(jù)對(duì)應(yīng)的像素密度
inTargetDensity 生成的bitmap的像素密度
inScale 是否根據(jù)像素密度縮放霎烙,需要設(shè)置true

測(cè)試代碼:

        // 這里使用了一張大小960000B大小的圖片,放在assets目錄下
        // 這里的測(cè)試機(jī)默認(rèn)屏幕像素密度480

        AssetManager assetManager = getAssets();
        InputStream is = null;
        try {
            is = assetManager.open("img_fjords.jpg");
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inTargetDensity = DisplayMetrics.DENSITY_HIGH; // 240
            options.inDensity = DisplayMetrics.DENSITY_XXHIGH; // 480
            options.inScaled = true;
            Bitmap bitmap = BitmapFactory.decodeStream(is, new Rect(0, 0, 0, 0), options);
            Log.d(LOG_TAG, "default density: " + 480);
            Log.d(LOG_TAG, "default size: 960000");

            Log.d(LOG_TAG, "bitmap density: " + String.valueOf(bitmap.getDensity()));
            Log.d(LOG_TAG, "bitmap size: " + bitmap.getByteCount());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
/*
輸出結(jié)果:
08-20 03:03:06.528 7386-7386/com.example.densitytest D/MainActivity: default density: 480
08-20 03:03:06.528 7386-7386/com.example.densitytest D/MainActivity: default size: 960000
08-20 03:03:06.528 7386-7386/com.example.densitytest D/MainActivity: bitmap density: 240
08-20 03:03:06.528 7386-7386/com.example.densitytest D/MainActivity: bitmap size: 240000
*/

可以看出來(lái)蕊连,內(nèi)存大小確實(shí)變化了悬垃。內(nèi)存大小關(guān)系應(yīng)該是 finalSize = originSize*(inTargetDensity/inDensity)^2

不過(guò)需要注意的是tagetSize如果與屏幕像素密度不一致的時(shí)候展示的時(shí)候還是會(huì)縮放。所以甘苍,在使用這個(gè)方法控制的內(nèi)存的時(shí)候
通過(guò)inDensity來(lái)控制尝蠕,這樣就不需要額外修改bitmap的density。

Bitmap

這個(gè)類(lèi)就代表位圖载庭,它的一部分接口如下:

方法 解釋
compress(Bitmap.CompressFormat format, int quality, OutputStream stream) : boolean 把一個(gè)位圖寫(xiě)入流中
copy(Bitmap.Config config, boolean isMutable) : Bitmap 使用config配置復(fù)制一個(gè)Bitmap
copyPixelsFromBuffer(Buffer src) : void 從一個(gè)Buffer對(duì)象中復(fù)制出所有的像素
copyPixelsToBuffer(Buffer dst) : void 將Bitmap的所有像素都復(fù)制到Buffer中
static createBitmap(Bitmap source, int x, int y, int width, int height) : Bitmap 從已有的Bitmap對(duì)象中取一個(gè)子集
static createBitmap(int[] colors, int width, int height, Bitmap.Config config) : Bitmap 根據(jù)顏色矩陣生成一幅位圖
static createBitmap(DisplayMetrics display, int width, int height, Bitmap.Config config) 返回一個(gè)可更改的Bitmap
static createBitmap(int width, int height, Bitmap.Config config):Bitmap 返回一個(gè)可更改的Bitmap
static createScaledBitmap(Bitmap src, int dstWidth, int sdtHeight, boolean filter) 創(chuàng)建一個(gè)縮放到指定尺寸的Bitmap
describeContents() ...
eraseColor(int c) : void 將bitmap的所有像素都設(shè)置成同一顏色
extractAlpha(): Bitmap 生成一幅去掉Alpha值的Bitmap
extractAlpha(Paint paint, int[] offsetXY) .
getAllocationByteCount(): int 獲取bitmap的尺寸
getByteCount() : int 獲取存儲(chǔ)圖片最少需要的空間
getConfig(): Bitmap.Config 獲取圖片配置
getDensity() : int 獲取圖片密度
getGenerationId() : int 返回generationId
getHeight() 位圖高度
getNinePatchChunck() : byte[] 返回一個(gè)數(shù)組看彼,為.9.png使用
getPixel(int x, int y): int 獲取具體位置的顏色值
getRowBytes() 圖片中一行像素占多少空間
getScaledHeight(int targetDensity) : int, getScaledWidth(int targetDenisty) : int 特定目標(biāo)屏幕密度下的高度
hasAlpha() : boolean 如果每個(gè)像素都支持透明效果的話(huà)就返回true
hasMipMap(): boolean ...
isMutable() : boolean 是否可以更改
isPremultiplied() : boolean 像素點(diǎn)是否是premulitplied格式存儲(chǔ)
isRecycled() : boolean 圖片是否已經(jīng)被回收
prepareToDraw() 為繪制做緩存
reconfigure(int width, int height, Bitmap.Config config) : void 更改Bitmap的配置屬性,但是不會(huì)影響底層的存儲(chǔ)
recycle() : void 釋放native層的對(duì)象囚聚,并釋放對(duì)像素矩陣的引用
sameAs(Bitmap other) 如果另一個(gè)Bitmap擁有同樣的尺寸靖榕,配置,像素值就返回true
setConfig(Bitmap.Config config): void reconfig方法的一種捷徑
setDensity(int density) : void .
setHasAlpha(boolean hasAlpha) : void .
setHasMipMap(boolean hasMipMap) : void .
set Height(int height):void .
setPixel(int x, int y):void .
setPremultiplied(boolean premultiplied) : void .
setWidth(int width):void .
writeToParcel(Parcel p, int flags): void .

Bitmap.Config

這個(gè)類(lèi)是用來(lái)配置像素格式的顽铸。它決定了像素的大小茁计,圖像的質(zhì)量

變量名 大小(B) 補(bǔ)充說(shuō)明
ALPHA_8 1 只有黑白灰,就像黑白電視谓松,最節(jié)省空間
ARGB_4444 2 由于圖像質(zhì)量問(wèn)題簸淀,建議使用ARGB_8888瓶蝴。deprecated since api 14
ARGB_8888 4 最高畫(huà)質(zhì),建議使用租幕,空間使用最多
RGB_565 2 顏色相對(duì)豐富,適合不做透明處理的圖像

Bitmap.CompressFormat

變量名 說(shuō)明
JPEG 有損壓縮拧簸,畫(huà)質(zhì)不穩(wěn)定劲绪,存儲(chǔ)傳輸效率高
PNG 無(wú)損壓縮,畫(huà)質(zhì)很好盆赤,存儲(chǔ)傳輸效率低
WEBP api 14 以后才提供使用贾富,效果未知

這里對(duì)壓縮做一下說(shuō)明。compress方法有三個(gè)參數(shù):
第一個(gè)是格式,PNG格式是無(wú)損的牺六,所以后面的第二個(gè)參數(shù)對(duì)它沒(méi)有影響颤枪。另外兩種格式都有影響。
第二個(gè)是壓縮比淑际,取值在0~100之間畏纲。數(shù)字越大圖片質(zhì)量越高,體積越大春缕。100 代表不壓縮盗胀,0代表盡全力壓縮。
第三個(gè)是輸出流锄贼。

reconfigure方法票灰,它是不更改底層像素值的。調(diào)用這個(gè)方法之后只是“看起來(lái)”變了宅荤,不會(huì)影響內(nèi)存屑迂。

getAllocationByteCount()getByteCount()分別需要API-19和API-12,api版本相對(duì)較高冯键。事實(shí)上源碼的計(jì)算很簡(jiǎn)單惹盼,如果app使用的時(shí)候受到api限制的話(huà),完全可以自己計(jì)算:

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

LruCache

基于LinkedHashMap的一種經(jīng)典的內(nèi)存緩存模型琼了。它是用強(qiáng)引用控制的緩存逻锐。可以設(shè)置緩存的大小雕薪,個(gè)數(shù)昧诱。可以統(tǒng)計(jì)命中率所袁,讀寫(xiě)次數(shù)盏档。它是線程安全的。從做緩存的角度來(lái)說(shuō)燥爷,要比WeakHashMap要好很多蜈亩。

api 12 以上可以直接使用懦窘。api 12 以下可以通過(guò)support v4包 使用。

它的接口十分簡(jiǎn)單明了

具體的接口參見(jiàn):LruCache

DiskLruCache

DiskLruCache并不是谷歌官方的API稚配。它是推薦給開(kāi)發(fā)者使用的文件緩存的類(lèi)畅涂。從名稱(chēng)上很好理解,文件系統(tǒng)中的Lru緩存道川。它的源碼地址午衰。

它的原理 利用LinkedHashMap在內(nèi)存中記錄文件緩存的最近訪問(wèn)順序。磁盤(pán)中利用了journal文件作為日志文件冒萄,記錄文件讀寫(xiě)操作臊岸。每次創(chuàng)建DiskLruCache的時(shí)候都會(huì)通過(guò)journal日志重建LinkedHashMap的對(duì)象,這樣在每次重新創(chuàng)建的時(shí)候也可以保持之前LRU的效果尊流。源碼不長(zhǎng)帅戒,有興趣的同學(xué)自行研究。網(wǎng)絡(luò)上也有比較詳細(xì)的介紹崖技。
可以控制的變量:

  1. 緩存路徑逻住。建議選擇App的cache目錄下;
  2. cache版本响疚。cache版本升級(jí)的時(shí)候會(huì)把舊的緩存全部清除鄙信;
  3. cache大小。cache的大小要小于緩存路徑下的可有
  4. 日志條數(shù)忿晕。默認(rèn)2000條装诡。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市践盼,隨后出現(xiàn)的幾起案子鸦采,更是在濱河造成了極大的恐慌,老刑警劉巖咕幻,帶你破解...
    沈念sama閱讀 210,914評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件渔伯,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡肄程,警方通過(guò)查閱死者的電腦和手機(jī)锣吼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蓝厌,“玉大人玄叠,你說(shuō)我怎么就攤上這事⊥靥幔” “怎么了读恃?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,531評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我寺惫,道長(zhǎng)疹吃,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,309評(píng)論 1 282
  • 正文 為了忘掉前任西雀,我火速辦了婚禮萨驶,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘艇肴。我一直安慰自己篡撵,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布豆挽。 她就那樣靜靜地躺著,像睡著了一般券盅。 火紅的嫁衣襯著肌膚如雪帮哈。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,730評(píng)論 1 289
  • 那天锰镀,我揣著相機(jī)與錄音娘侍,去河邊找鬼。 笑死泳炉,一個(gè)胖子當(dāng)著我的面吹牛憾筏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播花鹅,決...
    沈念sama閱讀 38,882評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼氧腰,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了刨肃?” 一聲冷哼從身側(cè)響起古拴,我...
    開(kāi)封第一講書(shū)人閱讀 37,643評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎真友,沒(méi)想到半個(gè)月后黄痪,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,095評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡盔然,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評(píng)論 2 325
  • 正文 我和宋清朗相戀三年桅打,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片愈案。...
    茶點(diǎn)故事閱讀 38,566評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡挺尾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出刻帚,到底是詐尸還是另有隱情潦嘶,我是刑警寧澤,帶...
    沈念sama閱讀 34,253評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站掂僵,受9級(jí)特大地震影響航厚,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜锰蓬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評(píng)論 3 312
  • 文/蒙蒙 一幔睬、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧芹扭,春花似錦麻顶、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,715評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至轮锥,卻和暖如春矫钓,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背舍杜。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,945評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工新娜, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人既绩。 一個(gè)月前我還...
    沈念sama閱讀 46,248評(píng)論 2 360
  • 正文 我出身青樓概龄,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親饲握。 傳聞我的和親對(duì)象是個(gè)殘疾皇子私杜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評(píng)論 2 348

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