BitMap壓縮以及二次采樣
標簽: Android
首先我們來了解一下什么是oom简十?
1.什么是OOM?為什么會引起OOM撬腾?
out of Memory(內存溢出)我們都知道Android系統(tǒng)會被每個App分配一個獨立的空間勺远,或者說分配一個Dalvik虛擬機(Dalvik是Google公司自己設計用于Android平臺的虛擬機。Dalvik虛擬機是Google等廠商合作開發(fā)的Android移動設備平臺的核心組成部分之一时鸵。它可以支持已轉換為 .dex(即Dalvik Executable)格式的Java應用程序的運行胶逢,.dex格式是專為Dalvik設計的一種壓縮格式,適合內存和處理器速度有限的系統(tǒng)饰潜。Dalvik 經過優(yōu)化初坠,允許在有限的內存中同時運行多個虛擬機的實例甫男,并且[1]每一個Dalvik 應用作為一個獨立的Linux 進程執(zhí)行贮聂。獨立的進程可以防止在虛擬機崩潰的時候所有程序都被關閉乐尊。)這樣每個APP都可以獨立運行而不相互影響享潜!而Android對于每個 Dalvik虛擬機都會有一個最大內存限制朋譬,如果當前占用的內存加上我們申請的內存資源超過了這個限制 ,系統(tǒng)就會拋出OOM錯誤砂代!另外到忽,這里別和RAM混淆了,即時當前RAM中剩余的內存有1G多者填,但是OOM還是會發(fā)生浩村!別把RAM(物理內存)和OOM扯到一起!另外RAM不足的話占哟,就是殺應用了心墅,而不是僅僅是OOM了! 而這個Dalvik中的最大內存標準榨乎,不同的機型是不一樣的
這里可以獲取系統(tǒng)分給你App多少內存
ActivityManager activityManager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
Log.e("HEHE","最大內存:" + activityManager.getMemoryClass());
//或者
Runtime.getRuntime().maxMemory()來獲取
一怎燥、Bitmap:
Bitmap是Android系統(tǒng)中的圖像處理的最重要類之一。用它可以獲取圖像文件信息蜜暑,進行圖像剪切铐姚、旋轉、縮放等操作肛捍,并可以指定格式保存圖像文件谦屑。
常用方法:
public void recycle() // 回收位圖占用的內存空間,把位圖標記為Dead
public final boolean isRecycled() //判斷位圖內存是否已釋放
public final int getWidth() //獲取位圖的寬度
public final int getHeight() //獲取位圖的高度
public final boolean isMutable() //圖片是否可修改
public int getScaledWidth(Canvas canvas) //獲取指定密度轉換后的圖像的度
public int getScaledHeight(Canvas canvas) //獲取指定密度轉換后的圖像的度
public boolean compress(CompressFormat format, int quality, OutputStream stream) //按指定的圖片格式以及畫質篇梭,將圖片轉換為輸出流氢橙。
format:壓縮圖像的格式,如Bitmap.CompressFormat.PNG或 ·Bitmap.CompressFormat.JPEG
quality:畫質,0-100.0表示最低畫質壓縮恬偷,100以最高畫質壓縮悍手。對于PNG等無損格式的圖片,會忽略此項設置袍患。
stream: OutputStream中寫入壓縮數據坦康。
return: 是否成功壓縮到指定的流。
public static Bitmap createBitmap(Bitmap src) //以src為原圖生成不可變得新圖像
public static Bitmap createScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter) //以src為原圖诡延,創(chuàng)建新的圖像滞欠,指定新圖像的高寬以及是否可變。
public static Bitmap createBitmap(int width, int height, Config config) //創(chuàng)建指定格式肆良、大小的位圖
public static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height) //以source為原圖筛璧,創(chuàng)建新的圖片,指定起始坐標以及新圖像的高寬惹恃。
BitmapFactory.Option可設置參數
Option 參數類:
public boolean inJustDecodeBounds //如果設置為true夭谤,不獲取圖片,不分配 內存巫糙,但會返回圖片的高度寬度信息朗儒。
如果將這個值置為true,那么在解碼的時候將不會返回bitmap,只會返回這個bitmap 的尺寸醉锄。這個屬性的目的是乏悄,如果你只想知道一個bitmap的尺寸,但又不想將其加載到內存時恳不。這是一個非常有用的屬性檩小。
public int inSampleSize //圖片縮放的倍數
這個值是一個int,當它小于1的時候妆够,將會被當做1處理识啦,如果大于1负蚊,那么就會按照比例(1 / inSampleSize)縮小bitmap的寬和高神妹、降低分辨率,大于1時這個值將會被處置為2的倍數家妆。例如鸵荠,width=100,height=100伤极,inSampleSize=2蛹找,那么就會將bitmap處理為,width=50哨坪,height=50庸疾,寬高降為1 / 2,像素數降為1 / 4当编。
public int outWidth //獲取圖片的寬度值
public int outHeight //獲取圖片的高度值
表示這個Bitmap的寬和高届慈,一般和inJustDecodeBounds一起使用來獲得Bitmap的寬高,但是不加載到內存忿偷。
public int inDensity //用于位圖的像素壓縮比
public int inTargetDensity //用于目標位圖的像素壓縮比(要生成的位圖)
public byte[] inTempStorage //創(chuàng)建臨時文件金顿,將圖片存儲
public boolean inScaled //設置為true時進行圖片壓縮,從inDensity到inTargetDensity
public boolean inDither //如果為true,解碼器嘗試抖動解碼
public Bitmap.Config inPreferredConfig //設置解碼器
這個值是設置色彩模式鲤桥,默認值是ARGB_8888揍拆,在這個模式下,一個像素點占用4bytes空間茶凳,一般對透明度不做要求的話嫂拴,一般采用RGB_565模式,這個模式下一個像素點占用2bytes贮喧。
public String outMimeType //設置解碼圖像
public boolean inPurgeable //當存儲Pixel的內存空間在系統(tǒng)內存不足時是否可以被回收
public boolean inInputShareable //inPurgeable為true情況下才生效顷牌,是否可以共享一個InputStream
public boolean inPreferQualityOverSpeed //為true則優(yōu)先保證Bitmap質量其次是解碼速度
public boolean inMutable //配置Bitmap是否可以更改,比如:在Bitmap上隔幾個像素加一條線段
public int inScreenDensity //當前屏幕的像素密度
從資源中獲取位圖的方式有兩種:通過BitmapDrawable或者BitmapFactory
你可以創(chuàng)建一個構造一個BitmapDrawable對象塞淹,比如通過流構建BitmapDrawable:
BitmapDrawable bmpMeizi = new BitmapDrawable(getAssets().open("pic_meizi.jpg"));
Bitmap mBitmap = bmpMeizi.getBitmap();
img_bg.setImageBitmap(mBitmap);
工廠方法:
public static Bitmap decodeFile(String pathName, Options opts) //從文件讀取圖片
public static Bitmap decodeFile(String pathName)
public static Bitmap decodeStream(InputStream is) //從輸入流讀取圖片
public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts)
public static Bitmap decodeResource(Resources res, int id) //從資源文件讀取圖片
public static Bitmap decodeResource(Resources res, int id, Options opts)
public static Bitmap decodeByteArray(byte[] data, int offset, int length) //從數組讀取圖片
public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts)
public static Bitmap decodeFileDescriptor(FileDescriptor fd) //從文件讀取文件 與decodeFile不同的是這個直接調用JNI函數進行讀取 效率比較高
public static Bitmap decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts)
首先了解一下關于Bitmap的Config的理解
A:透明度 R:紅色 G:綠 B:藍
Bitmap.Config ARGB_4444:每個像素占四位窟蓝,即A=4,R=4,G=4运挫,B=4状共,那么一個像素點占4+4+4+4=16位
Bitmap.Config ARGB_8888:每個像素占四位,即A=8谁帕,R=8峡继,G=8,B=8匈挖,那么一個像素點占8+8+8+8=32位
Bitmap.Config RGB_565:每個像素占四位碾牌,即R=5,G=6儡循,B=5舶吗,沒有透明度,那么一個像素點占5+6+5=16位
Bitmap.Config ALPHA_8:每個像素占四位择膝,只有透明度誓琼,沒有顏色。
bitmap內存大小 = 圖片長度 x 圖片寬度 x 單位像素占用的字節(jié)數
起決定因素就是最后那個參數了肴捉,Bitmap'常見有2種編碼方式:ARGB_8888和RGB_565腹侣,ARGB_8888每個像素點4個byte,RGB_565是2個byte齿穗,一般都采用ARGB_8888這種傲隶。那么常見的1080*1920的圖片內存占用就是:
1920 x 1080 x 4 = 7.9M
常用的壓縮方法:
1.質量壓縮
private void compressQuality() {
Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.test);
mSrcSize = bm.getByteCount() + "byte";
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.JPEG, 100, bos);
byte[] bytes = bos.toByteArray();
mSrcBitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
}
質量壓縮不會減少圖片的像素,它是在保持像素的前提下改變圖片的位深及透明度窃页,來達到壓縮圖片的目的跺株,圖片的長,寬腮出,像素都不會改變帖鸦,那么bitmap所占內存大小是不會變的。
我們可以看到有個參數:quality胚嘲,可以調節(jié)你壓縮的比例作儿,但是還要注意一點就是,質量壓縮堆png格式這種圖片沒有作用馋劈,因為png是無損壓縮攻锰。
2.采樣率壓縮
private void compressSampling() {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;
mSrcBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test, options);
}
采樣率壓縮其原理其實也是縮放bitamp的尺寸,通過調節(jié)其inSampleSize參數妓雾,比如調節(jié)為2娶吞,寬高會為原來的1/2,內存變回原來的1/4.
3.放縮法壓縮
private void compressMatrix() {
Matrix matrix = new Matrix();
matrix.setScale(0.5f, 0.5f);
Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.test);
mSrcBitmap = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), matrix, true);
bm = null;
}
放縮法壓縮使用的是通過矩陣對圖片進行裁剪械姻,也是通過縮放圖片尺寸妒蛇,來達到壓縮圖片的效果,和采樣率的原理一樣
4.RGB_565壓縮
private void compressRGB565() {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
mSrcBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test, options);
}
這是通過壓縮像素占用的內存來達到壓縮的效果,一般不建議使用ARGB_4444绣夺,因為畫質實在是辣雞吏奸,如果對透明度沒有要求,建議可以改成RGB_565陶耍,相比ARGB_8888將節(jié)省一半的內存開銷奋蔚。
5.createScaledBitmap
private void compressScaleBitmap() {
Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.test);
mSrcBitmap = Bitmap.createScaledBitmap(bm, 600, 900, true);
bm = null;
}
將圖片的大小壓縮成用戶的期望大小,來減少占用內存烈钞。
為什么要二次采樣
默認情況下泊碑,bitmap每個像素點占用4個字節(jié)(ARGB_8888),比如一張3543×3503的圖片差不多在內存中占用47M
安卓系統(tǒng)給每個應用分配的內存都是有限的毯欣,可以使用Runtime.getRuntime().maxMemory()來獲取
內存有限空間馒过,默認情況下圖片存儲又需要大量的空間,于是就容易產生OOM(內存溢出)
private Bitmap getCompressBm(int id, int maxw, int maxh) {
Bitmap bm = null;
int iSamplesize = 1;
//第一次采樣
BitmapFactory.Options bitmapFactoryOptions = new BitmapFactory.Options();
//該屬性設置為true只會加載圖片的邊框進來仪媒,并不會加載圖片具體的像素點
bitmapFactoryOptions.inJustDecodeBounds = true;
bm = BitmapFactory.decodeResource(getResources(), id, bitmapFactoryOptions);
Log.e("Tag", "onActivityResult: 壓縮之前圖片的寬:" + bitmapFactoryOptions.outWidth + "--壓縮之前圖片的高:"
+ bitmapFactoryOptions.outHeight + "--壓縮之前圖片大小:" + bitmapFactoryOptions.outWidth * bitmapFactoryOptions.outHeight * 4 / 1024 + "kb");
int iWidth = bitmapFactoryOptions.outWidth;
int iHeight = bitmapFactoryOptions.outHeight;
//對縮放比例進行調整沉桌,直到寬和高符合我們要求為止
while (iWidth > maxw|| iHeight > maxh){
//如果寬高的任意一方的縮放比例沒有達到要求谢鹊,都繼續(xù)增大縮放比例
//sampleSize應該為2的n次冪算吩,如果給sampleSize設置的數字不是2的n次冪,那么系統(tǒng)會就近取
iSamplesize = iSamplesize*2;//寬高均為原圖的寬高的1/2 內存約為原來的1/4
iWidth = iWidth/iSamplesize;
iHeight = iHeight/iSamplesize;
}
//二次采樣開始
//二次采樣時我需要將圖片加載出來顯示佃扼,不能只加載圖片的框架偎巢,因此inJustDecodeBounds屬性要設置為false
bitmapFactoryOptions.inJustDecodeBounds = false;
bitmapFactoryOptions.inSampleSize = iSamplesize;
// 設置像素顏色信息
// bitmapFactoryOptions.inPreferredConfig = Bitmap.Config.RGB_565;
bm = BitmapFactory.decodeResource(getResources(),id, bitmapFactoryOptions);
//默認的圖片格式是Bitmap.Config.ARGB_8888
Log.e("Tag", "onActivityResult: 圖片的寬:" + bm.getWidth() + "--圖片的高:"
+ bm.getHeight() + "--圖片大小:" + bm.getWidth() * bm.getHeight() * 4 / 1024 + "kb");
return bm;//返回壓縮后的照片
}
再一次感謝您花費時間閱讀這份文章,祝您在這里記錄兼耀、閱讀压昼、分享愉快!
作者 @windrain_boy
2017年06月06日