性能優化-Bitmap內存管理及優化

Bitmap做爲重要Android應用之一,在不少時候若是應用不當,很容易形成內存溢出,那麼這篇文章的目的就在於探討Bitmap的有效運用及其優化html

緩存介紹

當屢次發送請求的時候,請求同一內容,爲了使資源獲得合理利用,那麼就須要設置緩存,避免同一內容被屢次請求
在這裏使用一個Http的緩存策略,對http自帶的緩存策略作一個簡單的使用介紹,從而引出今天的主角java

http自帶緩存的使用前提:服務器設置了緩存時間android

response.addHeader("Cache-control", "max-age=10"); //HttpServletResponse response

以上表明瞭在10秒重內不會再請求服務器,此時客戶端開啓了緩存的話,在10內就不會重複請求了
http自帶緩存的策略的使用:web

  1. 打開緩存,Android在默認狀況下HttpResponseCache(網絡請求響應緩存)是關閉的
try {
	File cacheDir = new File(getCacheDir(), "http");//緩存目錄,在應用目錄的cache文件夾下生成http文件夾
	long maxSize = 10 * 1024 * 1024;//緩存大小:byte
	HttpResponseCache.install(cacheDir, maxSize );
	Log.d(TAG, "打開緩存");
} catch (IOException e) {
	e.printStackTrace();
}
  1. 發送請求,這裏以向服務器請求一張圖片爲例
    此時,會鏈接服務器,獲取服務器裏面的內容
new Thread(new Runnable() {
	@Override
	public void run() {
		try {
			BitmapFactory.decodeStream((InputStream) new URL("http://192.168.1.7:8080/test.png").getContent());
			Log.d(TAG, "下載圖片");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}).start();
  1. 刪除緩存,就算如今不刪,也要找時間清理
HttpResponseCache cache = HttpResponseCache.getInstalled();
if(cache!=null){
	try {
		cache.delete();
		Log.d(TAG, "清空緩存");
	} catch (IOException e) {
		e.printStackTrace();
	}
}

通過以上步驟,就走完了http緩存的一個流程,在實際應用中,通常會採用本身設計緩存的方法,這裏只是引出緩存這個概念算法

Bitmap優化着手點

在使用Bitmap的時候,容易使得Bitmap超過系統分配的內存閾值,發麼此時就產生了常見的OOM報錯,所以,優化Bitmap的思路也就在於如何避免OOM的發生canvas

那麼避免OOM發生就要從圖片自己下手了,常見的處理方式即是將圖片進行壓縮,經常使用的壓縮算法有三種:數組

  • 質量壓縮
    質量壓縮是經過同化像素點周圍的相近的像素點,從而達到下降文件大小的做用,也就是說其自己的像素多少並無改變,壓縮出來的圖片雖然大小發生了改變,但分辨率沒有發生改變,而在Bitmap中,其是按照像素大小,即圖片的像素多少來計算內存空間的,所以這種方法並不能有效避免OOM,這也就是爲什麼只改變圖片大小對於Bitmap的內存使用沒有做用的緣由
    那麼質量壓縮有什麼做用呢?其實它的做用就是減小存儲體積,方便傳輸或者保存緩存

  • 尺寸壓縮
    尺寸壓縮的思路就是使用Canvas讀取如今的bitmap,而後對其尺寸進行修改,這裏是真實的改變了圖片的像素大小,因此在Bitmap使用的時候,就會獲得改變尺寸後的大小,那麼就能夠對Bitmap進行有效優化,這種操做通常用於緩存縮略圖服務器

  • 採樣率壓縮
    設置圖片的採樣率,下降圖片像素,其原理和尺寸壓縮相似,不過實現的方式不一樣網絡

Bitmap的Java層源代碼分析

因爲Bitmap的底層CPP代碼涉及到的東西略多,這裏就簡單介紹下Java層的源碼就好
在Bitmap使用的使用,一般會使用以下三個方法加載來自不一樣地方的圖片

BitmapFactory.decodeFile(path)
BitmapFactory.decodeResource(res, id)
BitmapFactory.decodeStream(is)

那麼在BitmapFactory源代碼中,這三個方法是怎麼處理的呢?
打開源代碼,咱們能夠看到:

public static Bitmap decodeFile(String pathName) {
	return decodeFile(pathName, null);
}
···
public static Bitmap decodeResource(Resources res, int id) {
	return decodeResource(res, id, null);
}
···
public static Bitmap decodeStream(InputStream is) {
	return decodeStream(is, null, null);
}

在這裏又傳遞了一次參數,那麼找到這三個傳遞參數的方法

public static Bitmap decodeFile(String pathName, Options opts) {
    Bitmap bm = null;
    InputStream stream = null;
    try {
        stream = new FileInputStream(pathName);
        bm = decodeStream(stream, null, opts);
    } catch (Exception e) {
        /* do nothing. If the exception happened on open, bm will be null. */
        Log.e("BitmapFactory", "Unable to decode stream: " + e);
    } finally {
        if (stream != null) {
            try {
                stream.close();
            } catch (IOException e) {
                // do nothing here
            }
        }
    }
    return bm;
}

decodeFile()方法中,將傳入文件轉化成了流,送到了decodeStream()進行處理,其餘就沒作什麼了,那麼咱們再看看decodeResource()作了啥

public static Bitmap decodeResource(Resources res, int id, Options opts) {
    Bitmap bm = null;
    InputStream is = null; 
    
    try {
        final TypedValue value = new TypedValue();
        is = res.openRawResource(id, value);

        bm = decodeResourceStream(res, value, is, null, opts);
    } catch (Exception e) {
        /* do nothing. If the exception happened on open, bm will be null. If it happened on close, bm is still valid. */
    } finally {
        try {
            if (is != null) is.close();
        } catch (IOException e) {
            // Ignore
        }
    }

    if (bm == null && opts != null && opts.inBitmap != null) {
        throw new IllegalArgumentException("Problem decoding into existing bitmap");
    }

    return bm;
}

由以上代碼看出,decodeResource()也只是中轉,把參數設置好,傳到decodeResourceStream()中去了,那麼順藤摸瓜,看一看這個方法裏幹了啥,因而咱們找到了這個方法

public static Bitmap decodeResourceStream(Resources res, TypedValue value,
            InputStream is, Rect pad, Options opts) {

    if (opts == null) {
        opts = new Options();
    }

    if (opts.inDensity == 0 && value != null) {
        final int density = value.density;
        if (density == TypedValue.DENSITY_DEFAULT) {
            opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
        } else if (density != TypedValue.DENSITY_NONE) {
            opts.inDensity = density;
        }
    }
    
    if (opts.inTargetDensity == 0 && res != null) {
        opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
    }
    
    return decodeStream(is, pad, opts);
}

查看代碼發現,這裏其實也沒有幹嗎,設置了Options參數,這裏涉及到一個像素密度和分辨率的轉化問題,其中,DisplayMetrics.DENSITY_DEFAULT = 160,這個問題網上有不少討論,這裏簡明說明一下
denstity與像素密度
px = dp * Density
詳細介紹能夠參閱此文:dpi、dip、分辨率、屏幕尺寸、px、density關係以及換算
綜合上面的分析,全部的線索都指向了decodeStream()這個方法,那麼咱們就來看看這個方法幹了啥吧

public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {
    // we don't throw in this case, thus allowing the caller to only check
    // the cache, and not force the image to be decoded.
    if (is == null) {
        return null;
    }

    Bitmap bm = null;

    Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap");
    try {
        if (is instanceof AssetManager.AssetInputStream) {
            final long asset = ((AssetManager.AssetInputStream) is).getNativeAsset();
            bm = nativeDecodeAsset(asset, outPadding, opts);
        } else {
            bm = decodeStreamInternal(is, outPadding, opts);
        }

        if (bm == null && opts != null && opts.inBitmap != null) {
            throw new IllegalArgumentException("Problem decoding into existing bitmap");
        }

        setDensityFromOptions(bm, opts);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
    }

    return bm;
}

看一看上面的代碼,先是判斷了輸出流,而後新建Bitmap對象,以後開始寫入跟蹤信息,這裏能夠忽略這個寫入跟蹤信息的玩意兒,下一步就是校驗輸入流的來源,是assets裏面的,仍是其餘的,由於位置不同,其處理方法不同,這裏的nativeDecodeAsset()方法就是JNI的方法了,而decodeStreamInternal又是啥呢?再往下看看

private static Bitmap decodeStreamInternal(InputStream is, Rect outPadding, Options opts) {
    // ASSERT(is != null);
    byte [] tempStorage = null;
    if (opts != null) tempStorage = opts.inTempStorage;
    if (tempStorage == null) tempStorage = new byte[DECODE_BUFFER_SIZE];
    return nativeDecodeStream(is, tempStorage, outPadding, opts);
}

這裏就是設置了一個數組,以供JNI使用,而後又傳遞到了JNI裏面去了,也就是說這裏調用了兩個JNI的方法,一個是assets目錄,一個是非assets目錄

private static native Bitmap nativeDecodeStream(InputStream is, byte[] storage,
        Rect padding, Options opts);
private static native Bitmap nativeDecodeAsset(long nativeAsset, Rect padding, Options opts);

那麼JNI中作了什麼事情呢?

static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage,
        jobject padding, jobject options) {

    jobject bitmap = NULL;
    std::unique_ptr<SkStream> stream(CreateJavaInputStreamAdaptor(env, is, storage));

    if (stream.get()) {
        std::unique_ptr<SkStreamRewindable> bufferedStream(
                SkFrontBufferedStream::Create(stream.release(), SkCodec::MinBufferedBytesNeeded()));
        SkASSERT(bufferedStream.get() != NULL);
        bitmap = doDecode(env, bufferedStream.release(), padding, options);
    }
    return bitmap;
}

nativeDecodeAsset()處理與其相似,這裏就再也不展開了,這篇文章進行了詳細分析:android 圖片佔用內存大小及加載解析
那麼,Java層代碼到此分析結束,在Java層,其最終調用的就是decodeStream()方法了

decodeStream()方法參數分析

由上面的代碼分析,咱們已經知道,BitmapFactory的調用參數能夠設置的由三個InputStreamRectOptions
那麼Options又有什麼參數能夠設置呢?
inDensity:bitmap的像素密度
inTargetDensity:bitmap輸出的像素密度

質量壓縮實現方法

BitmapFactory.Options options = new BitmapFactory.Options();
Bitmap bitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options);

public void qualitCompress(Bitmap bmp, File file){
	int quality = 50; //0~100
	ByteArrayOutputStream baos = new ByteArrayOutputStream();
	bmp.compress(Bitmap.CompressFormat.JPEG, quality, baos); //圖片格式能夠選擇JPEG,PNG,WEBP
	try {
		FileOutputStream fos = new FileOutputStream(file);
		fos.write(baos.toByteArray());
		fos.flush();
		fos.close();
	} catch (Exception e) {
		e.printStackTrace();
	}
}

尺寸壓縮的實現方法

BitmapFactory.Options options = new BitmapFactory.Options();
Bitmap bitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options);

public static void sizeCompress(Bitmap bmp,File file){
	int ratio = 4; //縮小的倍數
	Bitmap result = Bitmap.createBitmap(bmp.getWidth() / ratio, bmp.getHeight() / ratio,
				Bitmap.Config.ARGB_8888);//這裏設置的是Bitmap的像素格式
	
	Canvas canvas = new Canvas(result);
	RectF rect = new RectF(0, 0, bmp.getWidth() / ratio, bmp.getHeight() / ratio);
	canvas.drawBitmap(bmp, null, rect, null); //經過Canvas從新畫入
	
	ByteArrayOutputStream baos = new ByteArrayOutputStream();
	result.compress(Bitmap.CompressFormat.JPEG, 100, baos);
	try {
		FileOutputStream fos = new FileOutputStream(file);
		fos.write(baos.toByteArray());
		fos.flush();
		fos.close();
	} catch (Exception e) {
		e.printStackTrace();
	}
}

採樣率壓縮的實現方法

public static void rateCompress(String filePath, File file){
	int inSampleSize = 8; //設置採樣率,數值越大,像素越低
	BitmapFactory.Options options = new BitmapFactory.Options();
	options.inJustDecodeBounds = false; //爲true的時候不會真正加載圖片,而是獲得圖片的寬高信息
	options.inSampleSize = inSampleSize; //採樣率
	Bitmap bitmap = BitmapFactory.decodeFile(filePath, options);
	
	ByteArrayOutputStream baos = new ByteArrayOutputStream();
	bitmap.compress(Bitmap.CompressFormat.JPEG, 100 ,baos); // 把壓縮後的數據存放到baos中
	try {
		if(file.exists()){
			file.delete();
		} else {
			file.createNewFile();
		}
		FileOutputStream fos = new FileOutputStream(file);
		fos.write(baos.toByteArray());
		fos.flush();
		fos.close();
	} catch (Exception e) {
		e.printStackTrace();
	}
}

總結

以上就是系統自帶的壓縮算法實現Bitmap的處理 質量壓縮:適合網絡傳輸 尺寸壓縮和採樣率壓縮:適合生成縮略圖

相關文章
相關標籤/搜索