前幾天我寫了一篇經過壓縮Bitmap,減小OOM的文章,那篇文章的目的是按照imageview的大小來壓縮bitmap,讓bitmap的大小正好是imageview。可是那種算法的通用性比較差,僅僅能適合fit_xy的狀況。對此我進一步分析了下這個問題,而且參考了Volley的源碼,最終得出告終論:若是你要讓這個壓縮後的bitmap徹底適合多種imageview拉伸模式,你就必須重寫拉伸模式的算法,但這過於小題大作了。討巧一點的辦法就是讓這個imageview不徹底按照imageview的長寬進行壓縮,而僅僅按照imageview的長或寬按比例縮小,獲得的是一張和原圖比率同樣的小圖,讓imageview加載這個小圖就好了。世上沒有十全十美的事情,你這個雖然討巧了,但問題也就來了,在某些模式下可能會有一部分圖片沒有顯示在屏幕上,浪費了一點點內存,在cent模式下,原圖的顯示效果和小圖的顯示效果徹底不同。html
總結:考慮到多種因素,我仍是決定使用比較討巧的作法,由於它通用性比較高,浪費內存的狀況有,但浪費的內存不多(幾kb),通常狀況下咱們不用center模式進行圖片的顯示,因此咱們徹底能夠考慮這個方式。android
工具類:算法
我參考了volley的代碼,從新構建了工具類,下面直接貼出工具類的代碼:數組
package com.kale.bitmaptest; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; public class BitUtils { private static int mDesiredWidth; private static int mDesiredHeight; /** * @description 從Resources中加載圖片 * * @param res * @param resId * @param reqWidth * @param reqHeight * @return */ public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { BitmapFactory.Options options = new BitmapFactory.Options(); // 設置成了true,不佔用內存,只獲取bitmap寬高 options.inJustDecodeBounds = true; // 初始化options對象 BitmapFactory.decodeResource(res, resId, options); // 獲得計算好的options,目標寬、目標高 options = getBestOptions(options, reqWidth, reqHeight); Bitmap src = BitmapFactory.decodeResource(res, resId, options); // 載入一個稍大的縮略圖 return createScaleBitmap(src, mDesiredWidth, mDesiredHeight); // 進一步獲得目標大小的縮略圖 } /** * @description 從SD卡上加載圖片 * * @param pathName * @param reqWidth * @param reqHeight * @return */ public static Bitmap decodeSampledBitmapFromFile(String pathName, int reqWidth, int reqHeight) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFile(pathName, options); options = getBestOptions(options, reqWidth, reqHeight); Bitmap src = BitmapFactory.decodeFile(pathName, options); return createScaleBitmap(src, mDesiredWidth, mDesiredHeight); } /** * @description 計算目標寬度,目標高度,inSampleSize * * @param options * @param reqWidth * @param reqHeight * @return BitmapFactory.Options對象 */ private static BitmapFactory.Options getBestOptions(BitmapFactory.Options options, int reqWidth, int reqHeight) { // 讀取圖片長寬 int actualWidth = options.outWidth; int actualHeight = options.outHeight; // Then compute the dimensions we would ideally like to decode to. mDesiredWidth = getResizedDimension(reqWidth, reqHeight, actualWidth, actualHeight); mDesiredHeight = getResizedDimension(reqHeight, reqWidth, actualHeight, actualWidth); // 根據如今獲得計算inSampleSize options.inSampleSize = calculateBestInSampleSize(actualWidth, actualHeight, mDesiredWidth, mDesiredHeight); // 使用獲取到的inSampleSize值再次解析圖片 options.inJustDecodeBounds = false; return options; } /** * Scales one side of a rectangle to fit aspect ratio. 最終獲得從新測量的尺寸 * * @param maxPrimary * Maximum size of the primary dimension (i.e. width for max * width), or zero to maintain aspect ratio with secondary * dimension * @param maxSecondary * Maximum size of the secondary dimension, or zero to maintain * aspect ratio with primary dimension * @param actualPrimary * Actual size of the primary dimension * @param actualSecondary * Actual size of the secondary dimension */ private static int getResizedDimension(int maxPrimary, int maxSecondary, int actualPrimary, int actualSecondary) { double ratio = (double) actualSecondary / (double) actualPrimary; int resized = maxPrimary; if (resized * ratio > maxSecondary) { resized = (int) (maxSecondary / ratio); } return resized; } /** * Returns the largest power-of-two divisor for use in downscaling a bitmap * that will not result in the scaling past the desired dimensions. * * @param actualWidth * Actual width of the bitmap * @param actualHeight * Actual height of the bitmap * @param desiredWidth * Desired width of the bitmap * @param desiredHeight * Desired height of the bitmap */ // Visible for testing. private static int calculateBestInSampleSize(int actualWidth, int actualHeight, int desiredWidth, int desiredHeight) { double wr = (double) actualWidth / desiredWidth; double hr = (double) actualHeight / desiredHeight; double ratio = Math.min(wr, hr); float inSampleSize = 1.0f; while ((inSampleSize * 2) <= ratio) { inSampleSize *= 2; } return (int) inSampleSize; } /** * @description 經過傳入的bitmap,進行壓縮,獲得符合標準的bitmap * * @param src * @param dstWidth * @param dstHeight * @return */ private static Bitmap createScaleBitmap(Bitmap tempBitmap, int desiredWidth, int desiredHeight) { // If necessary, scale down to the maximal acceptable size. if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth || tempBitmap.getHeight() > desiredHeight)) { // 若是是放大圖片,filter決定是否平滑,若是是縮小圖片,filter無影響 Bitmap bitmap = Bitmap.createScaledBitmap(tempBitmap, desiredWidth, desiredHeight, true); tempBitmap.recycle(); // 釋放Bitmap的native像素數組 return bitmap; } else { return tempBitmap; // 若是沒有縮放,那麼不回收 } } }
這個工具類構造的思想和本來的構造思想徹底一致,差異之處在於這裏的圖片是等比縮放的。app
測試代碼:ide
public void loadBitmap(boolean exactable) { int bmSize = 0; Bitmap bm = null; if (exactable) { // 經過工具類來產生一個符合ImageView的縮略圖 bm = BitUtils.decodeSampledBitmapFromResource(getResources(), R.drawable.saber, iv.getWidth(), iv.getHeight()); } else { // 直接加載原圖 bm = BitmapFactory.decodeResource(getResources(), R.drawable.saber); } iv.setImageBitmap(bm);
bmSize += bm.getByteCount(); // 獲得bitmap的大小 int kb = bmSize / 1024; int mb = kb / 1024; kb = kb % 1024; Log.d("Bitmap", "bitmap w = " + bm.getWidth() + " h = " + bm.getHeight()); Log.d("Bitmap", "bitmap size = " + mb + "MB " + kb + "KB"); Toast.makeText(this, "bitmap size = " + mb + "MB " + kb + "KB", Toast.LENGTH_LONG).show(); }
經過加載原圖和加載縮略圖進行比較,最終在log打印出圖片的寬高和圖片內存佔用。工具
測試結果:佈局
佈局文件:測試
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="16dp" tools:context="${relativePackage}.${activityClass}" > <ImageView android:id="@+id/imageView" android:layout_width="100dp" android:layout_height="100dp" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:src="@drawable/ic_launcher" /> <Button android:id="@+id/original_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:onClick="butonListener" android:text="加載原圖" /> <Button android:id="@+id/clip_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBaseline="@+id/original_button" android:layout_alignBottom="@+id/original_button" android:layout_alignParentRight="true" android:onClick="butonListener" android:text="加載縮略圖" /> </RelativeLayout>
前題:個人手機定義的imageview是100dp,實際是200pix。加載圖片的實際大小:850 x 1200this
① 加載原圖
bitmap寬 = 567,高 = 800;
內存佔用:1M 747KB
解釋:最終獲得的圖片大小和原始圖片不一樣,這裏應該是BitmapFactory在解碼時就已經作了壓縮,算是自帶的一個智能壓縮方案吧。
② 用工具類加載縮略圖
bitmap寬 = 141,高 = 200;
內存佔用:110KB
解釋:目標的imageview寬、高均爲100dp,在我手機上換算爲200pix,這裏作了等比縮放處理,因此高爲200.最後咱們也明顯的看出,用這種方式獲得的圖片比較小,不會輕易出現OOM
完整的activity代碼:
package com.kale.bitmaptest; import android.app.Activity; import android.app.ActivityManager; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.ImageView; import android.widget.ImageView.ScaleType; import android.widget.Toast; public class MainActivity extends Activity { ImageView iv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); iv = (ImageView) findViewById(R.id.imageView); iv.setScaleType(ScaleType.CENTER_CROP); // center 變了 getMemoryCacheSize(); } public void butonListener(View v) { switch (v.getId()) { case R.id.original_button: loadBitmap(false); // 加載原圖 break; case R.id.clip_button: loadBitmap(true); // 加載縮略圖 break; } } public void loadBitmap(boolean exactable) { int bmSize = 0; Bitmap bm = null; if (exactable) { // 經過工具類來產生一個符合ImageView的縮略圖 bm = BitUtils.decodeSampledBitmapFromResource(getResources(), R.drawable.saber, iv.getWidth(), iv.getHeight()); } else { // 直接加載原圖 bm = BitmapFactory.decodeResource(getResources(), R.drawable.saber); } iv.setImageBitmap(bm); bmSize += bm.getByteCount(); // 獲得bitmap的大小 int kb = bmSize / 1024; int mb = kb / 1024; kb = kb % 1024; Log.d("Bitmap", "bitmap w = " + bm.getWidth() + " h = " + bm.getHeight()); Log.d("Bitmap", "bitmap size = " + mb + "MB " + kb + "KB"); Toast.makeText(this, "bitmap size = " + mb + "MB " + kb + "KB", Toast.LENGTH_LONG).show(); } public int getMemoryCacheSize() { // Get memory class of this device, exceeding this amount will throw an // OutOfMemory exception. final int memClass = ((ActivityManager) getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass(); System.out.println("memory = " + memClass + "M"); return memClass; } public int dip2px(float dpValue) { final float scale = getResources().getDisplayMetrics().density; return (int) (dpValue * scale + 0.5f); } }
利弊:
利:
節約內存,下降出現OOM的概率
弊:
下降了圖片的清晰度,不適用於center模式的imageview。
左邊的是加載的縮略圖,右邊的是加載的原圖。右邊的圖片明顯比座標的清晰,但銳化過於嚴重了,左邊的雖然小,可是較爲模糊。
若是你用了android studio,你能夠很明顯的看出內存的變化:
Android Studio下的測試代碼:
package com.example.jack.loadbitmap; import com.kale.lib.activity.KaleBaseActivity; import com.kale.lib.utils.BitmapUtil; import com.kale.lib.utils.EasyToast; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.ImageView; public class MainActivity extends KaleBaseActivity { ImageView mImageView; Button loadOriginPicBtn; Button loadThumbPicBtn; @Override protected int getContentViewId() { return R.layout.activity_main; } @Override protected void findViews() { mImageView = getView(R.id.imageView); loadOriginPicBtn = getView(R.id.loadOriginal_button); loadThumbPicBtn = getView(R.id.loadThumb_button); } @Override protected void setViews() { // 加載原始的圖片 loadOriginPicBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.saber); setBitmapToImageView(bitmap); } }); // 加載壓縮後的圖片 loadThumbPicBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Bitmap bitmap = BitmapUtil.decodeSampledBitmapFromResource(getResources(), R.drawable.saber, mImageView.getWidth(), mImageView.getHeight()); setBitmapToImageView(bitmap); } }); } private void setBitmapToImageView(Bitmap bitmap) { mImageView.setImageBitmap(bitmap); int bmSize = BitmapUtil.getBitmapSize(bitmap); int mb = bmSize / 1024 / 1024; int kb = bmSize /1014 % 1024; String bitmapSizeStr = mb + "MB " + kb + "KB"; Log.d("Bitmap", "bitmap w = " + bitmap.getWidth() + " h = " + bitmap.getHeight()); Log.d("Bitmap", bitmapSizeStr); EasyToast.makeText(mContext, "bitmap size = " + bitmapSizeStr); } }
源碼下載:
http://download.csdn.net/detail/shark0017/8412329
利用AS構建的工程(推薦):http://download.csdn.net/detail/shark0017/8671957
參考自:
http://www.open-open.com/lib/view/open1329994992015.html