對于安卓開發來說,記憶體溢出(oom)是安卓程式員不可繞過的坎,特别是對于大圖檔來說,加載時候的大記憶體更是常常讓人膽戰心驚。
很多安卓程式員都知道,避免圖檔加載大記憶體的最常用方法,那就是用BitmapFactory的options,設定這個options的inSampleSize來達到将圖檔按照實際顯示大小去縮小自己圖檔,來達到減少記憶體。
若是你去百度inSampleSize,那麼你會看到最多的用法是這樣的:
private Bitmap getimage(String srcPath) {
BitmapFactory.Options newOpts = new BitmapFactory.Options();
//開始讀入圖檔,此時把options.inJustDecodeBounds 設回true了
newOpts.inJustDecodeBounds = true;
Bitmap bitmap = BitmapFactory.decodeFile(srcPath,newOpts);//此時傳回bm為空
newOpts.inJustDecodeBounds = false;
int w = newOpts.outWidth;
int h = newOpts.outHeight;
//現在主流手機比較多是800*480分辨率,是以高和寬我們設定為
float hh = f;//這裡設定高度為800f
float ww = f;//這裡設定寬度為480f
//縮放比。由于是固定比例縮放,隻用高或者寬其中一個資料進行計算即可
int be = ;//be=1表示不縮放
if (w > h && w > ww) {//如果寬度大的話根據寬度固定大小縮放
be = (int) (newOpts.outWidth / ww);
} else if (w < h && h > hh) {//如果高度高的話根據寬度固定大小縮放
be = (int) (newOpts.outHeight / hh);
}
if (be <= )
be = ;
newOpts.inSampleSize = be;//設定縮放比例
//重新讀入圖檔,注意此時已經把options.inJustDecodeBounds 設回false了
bitmap = BitmapFactory.decodeFile(srcPath, newOpts);
return compressImage(bitmap);//壓縮好比例大小後再進行品質壓縮
}
看起來好像完全沒有問題,根據自己想要的的圖檔大小,然後去計算将原圖縮放的inSampleSize,然後指派給bitmap以達到縮放圖檔大小。
很多人看到這樣算法便喜不自勝急忙将它複制到自己工程,可是複制進去之後他們卻傻眼了,記憶體跟以前沒用時候差不多啊。
為什麼會這樣?
因為,inSampleSize隻能為整數啊。
我們舉個例子,假如有一張高度為700的圖檔,你想把它縮放為500,你要是用上面那個算法去算,你會發現,你算出來的inSampleSize其實為1!!!
而1,那就是圖檔不變,沒有壓縮
在這裡說一下,inSampleSize的值隻能為2的倍數,1,4,8,16……
1的話是不變,2的話是圖檔變為原來4分之一大小,8是變為原來64分之一大小,以此類推
是以到了最後,你會發現你想要壓縮的圖檔其實根本沒有被壓縮。
好吧,就算有變,但其實也很難,甚至幾乎不可能變到你想要的圖檔大小,就比如上面說的那個例子,無論你設定inSampleSize為什麼值,你都會發現,你最終得到的圖檔要麼太大要麼太小。
但是安卓怎麼可能那麼垃圾,
對的,安卓才不會這麼垃圾呢,
其實與inSampleSize相關的還有幾個參數,比如inScaled,inTargetDensity,inDensity。
這些參數其實非常有用,與inSampleSize一起用才會完美。
其中:
inScaled:将它設定為true,那麼代表這張圖檔可以縮放
inDensity:圖檔的原來密度,預設一般為160.
inTargetDensity:圖檔的目标密度,圖檔操作之後的密度
其實你設定一張圖檔的BitmapFactory的options,這張圖檔的輸出寬高是這樣決定的:
輸出圖檔的寬高= (原圖檔的寬高 * (inTargetDensity / inDensity)) / inSampleSize
是以你要是現将一張700的圖檔縮小為600,你可以計算一下圖檔的inTargetDensity,如下面方法
/**
* 擷取特定大小縮略圖
* @param imageid 圖檔資源id
* @param size 你想要擷取的圖檔大小尺寸
* @return
*/
public Bitmap getSampleBitmap2(int imageid,int size){
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds=true;
BitmapFactory.decodeResource(getResources(),imageid,options);
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
options.inSampleSize= calculateInSampleSize(options,size);
//設定圖檔可以縮小
options.inScaled = true;
int calsize=options.outHeight>options.outWidth?options.outWidth:options.outHeight;
/**
* 計算圖檔縮小的目标密度,在這裡說一下,有一條公式:
* 輸出圖檔的寬高= (原圖檔的寬高 * (inTargetDensity / inDensity)) / inSampleSize
* 一般來說,圖檔的options.inDensity預設為160
* 是以inTargetDensity計算公式為:(希望輸出的寬高*options.inDensity)/(原來圖檔的寬高/options.inSampleSize)
*/
options.inTargetDensity =(size*options.inDensity)/(calsize/options.inSampleSize);
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),imageid,options);
return bitmap;
}
//谷歌源碼裡面的計算simplesize方法,
public int calculateInSampleSize(BitmapFactory.Options options,
int size) {
int reqWidth,reqHeight;
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
if(options.outHeight>options.outWidth){
reqWidth=size;
reqHeight=size*options.outHeight/options.outWidth;
}else{
reqWidth=size*options.outWidth/options.outHeight;
reqHeight=size;
}
int inSampleSize = ;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / ;
final int halfWidth = width / ;
// 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 *= ;
}
}
return inSampleSize;
}
比如我将一張寬700圖檔縮小為寬450的圖檔,在沒設定inTargetDensity的時候
計算出來的inSampleSize為1,圖檔尺寸很大,沒有變
所占用記憶體:
差不多40M
而在設定了inTargetDensity 之後
計算出來的inSampleSize仍然為1,但是圖檔尺寸卻變為我想要擷取的大小450了!!!
而所占用的記憶體:
隻有14M左右!!!!!!!!!!
40?14!是不是感覺世界瞬間美好很多啦~
對了,後面順便說一下,若你将根據這個方法得到的圖檔指派給另一個bitmap的時候,可能會出現,這個被指派的bitmap圖檔雖然圖檔大小不變,但是顯示變了,那是因為這個inTargetDensity 屬性不會自動指派給新的bitmap,可以這樣做
//建立一張新的bitmap,跟傳入圖檔一樣寬的正方形bitmap,
Bitmap b=Bitmap.createBitmap(bitmapSize,bitmapSize, Bitmap.Config.ARGB_8888);
//将圖檔密度修改為上面那張的圖檔密度
b.setDensity(bitmap.getDensity());
這樣新得到的b圖檔就會跟原來那張一樣了