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 .....
使用方法:
- 計(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;
- 根據(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弯囊;
- 根據(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ì)的介紹崖技。
可以控制的變量:
- 緩存路徑逻住。建議選擇App的cache目錄下;
- cache版本响疚。cache版本升級(jí)的時(shí)候會(huì)把舊的緩存全部清除鄙信;
- cache大小。cache的大小要小于緩存路徑下的可有
- 日志條數(shù)忿晕。默認(rèn)2000條装诡。