Android內存優化之圖片優化

背景

通常來講,圖片是APP佔用內存高的主要緣由,因此優化圖片的內存佔用是避免OOM的根本手段。對於圖片佔用的內存,咱們可能總有這樣的誤區:圖片自己所佔的存儲空間越小,佔用的內存越小。因此認爲只要將圖片進行壓縮,就至關於減少了內存佔用。其實這是不對的,圖片佔用的存儲空間大小與所佔內存大小沒有直接關係。git

既然與內存沒有關係,那壓縮圖片有什麼意義呢?對於APK而言,壓縮圖片是爲了減少APK的體積,而對於須要網絡請求的圖片,壓縮則是爲了更快的網絡響應。github

因此優化以前須要清楚2個基本原則:

  • 圖片佔用內存的大小與圖片自己的大小沒有直接關係;
  • WebP格式的圖片雖然小,但佔用的內存和其餘格式無差異;

圖片佔用內存的大小

memorySize ≈ width * height * 每一個像素須要的字節數
複製代碼

優化策略

既然須要的內存公式已獲得,那優化就顯而易見了,無非就是減少的這三個參數的值,具體的策略以下:web

這裏咱們將圖片分爲2種狀況來探討:網絡

drawable中的圖片

單獨探討這種狀況,是由於Android系統會對drawable中的圖片進行縮放,縮放係數與設置的屏幕分辨率和drawable所表示的分辨率有關,具體的公式以下:優化

scale = 設備分辨率 / 資源目錄分辨率  如:1080x1920的圖片顯示xhdpi中的圖片,scale = 480 / 320 = 1.5
複製代碼

因此此時圖片佔用的內存大小爲:spa

memorySize ≈ (width * scale) * (height * scale) * 每一個像素須要的字節數
           ≈ width * height * scale ^ 2 * 每一個像素須要的字節數
複製代碼

具體的縮放過程可參考Android中Bitmap內存優化code

這裏咱們只探討一下scale係數的影響因素:設備分辨率和資源目錄分辨率。至於其餘的可變因子會在另外一種狀況中介紹。設備分辨率咱們無法改變,因此影響因素只有資源目錄分辨率,也就是說,同一張圖片,放在不一樣的drawable中,佔用的內存大小不一樣。從公式可看出,使用同一個設備時,drawable表示的分辨率越高,則圖片佔用的內存越小,反之越大。因此,在作圖片的兼容性時,若是隻想使用一張圖片,則應使用3倍甚至4倍的圖片(3倍是主流機型,但在4倍手機上會被放大,圖片可能失真),這樣在低分辨率的手機上,不只顯示清晰,並且系統會自動進行縮放,從而確保佔用較小的內存。orm

一樣是存放圖片的位置,爲何mipmap不在這種狀況的考慮範圍以內呢?由於mipmap是Android系統爲了不Launcher Icon變形而添加的資源目錄,也就是說,mipmap中的圖片不會被縮放。因此Google也不推薦將除Launcher Icon以外的圖片放在mipmap目錄中。圖片

其餘位置的圖片

其餘位置的圖片包括mipmap, asset, 本地圖片,網絡圖片等。這些位置的圖片都有一個共同點——不會被縮放。因此只須要考慮如何改變圖片分辨率和每一個像素須要的字節數便可。ip

本地圖片

本地圖片一般都是經過Android提供的BitmapFactory來加載的, 這裏看幾個經常使用的API:

// 根據路徑加載
public static Bitmap decodeFile(String pathName, Options opts);
// 加載drawable或mipmap中的圖片
public static Bitmap decodeResource(Resources res, int id, Options opts)
// 根據字節流加載
public static Bitmap decodeByteArray(byte[] data, int offset, int length)
// 根據IO流加載
public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts)
複製代碼

圖片的優化可經過Options參數來實現(Options的介紹可參考從fresco 看圖片優化):

方式一:inSampleSize

inSampleSize可理解爲圖片的縮小比例,若inSampleSize小於1,則當作1處理。設置inSampleSize後,圖片的寬度和高度將變成原來的1/inSampleSize, 其佔用的內存空間將是原來的1/(inSampleSize ^ 2)。可是具體如何取值呢,可經過如下代碼來獲取:

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.drawable.image, options);
options.inSampleSize = getSampleSize(options, 100, 100);
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.abc, options);
imageView.setImageBitmap(bitmap);

public static int getSampleSize(BitmapFactory.Options options, int viewWidth, int viewHeight) {
	if (viewWidth == 0 || viewHeight == 0 || options == null) {
		return 1;
	}
	int widthScale = options.outWidth / viewWidth;
	int heightScale = options.outHeight / viewHeight;
	Log.i("out", "width==" + widthScale + " heightScale==" + heightScale);
	return widthScale >= heightScale ? heightScale : widthScale;
}
複製代碼

方式二:inDensity

inDensity至關於上面說的資源目錄分辨率,前面說了,這裏考慮的狀況,圖片不會被縮放,其緣由就是inDensity和設備分辨率的取值是一致的,由於inDensity=設備分辨率,因此scale=1, 若是將inDensity設置爲大於設備分辨率的值,那麼圖片就會被縮小。例如,當前的手機1dp=2px, 即2X屏幕,此時的inDensity爲320, 若是將inDensity修改成480, scale=320f/480f=2/3, 那麼圖片所佔用的內存將變成原來的4/9。

方式三:inPreferredConfig

inPreferredConfig的取值爲Bitmap.Config類型(這裏只考慮如下幾種狀況),它是一個枚舉類型,用來設置每一個像素須要的字節數:

ALPHA_8:佔1個字節
RGB_565:佔2個字節
ARGB_4444:佔2個字節,已廢棄,不推薦使用
ARGB_8888:32位真彩色,帶透明度,佔4個字節
複製代碼

顯示圖片時默認都是ARGB_8888,因此咱們可經過inPreferredConfig的值進行內存優化。但實際上inPreferredConfig的取值對內存的影響並非簡單的Bitmap.Config.ALPHA_8佔1個字節,ARGB_4444和RGB_565佔2個字節,ARGB_8888佔4個字節,而是與具體的圖片格式有關:

  • inPreferredConfig對jpeg和gif格式的圖片無做用,不管inPreferredConfig的值取什麼,jpeg格式的圖片每一個像素始終佔用4個字節,而gif格式的圖片每一個像素始終佔1個字節;
  • 對於webp格式的圖片,inPreferredConfig取值爲RGB_565的時候,每一個像素佔用2個字節,其他的取值每一個像素仍然佔4個字節;
  • 對於png格式的圖片,須要分png8, png24, png32三種狀況來講。png8格式的圖片每一個像素佔用的字節數隨inPreferredConfig的取值而變化,取值爲ALPHA_8時佔用一個字節,取值爲RGB_565時佔用2個字節,取值爲ARGB_4444或ARGB_8888時佔用4個字節。png24格式的圖片,當inPreferredConfig的取值爲RGB_565時,每一個像素佔用2個字節,取其餘的值(ALPHA_8, ARGB_4444和ARGB_8888)每一個像素都佔用4個字節。而對於png32格式的圖片,inPreferredConfig的取值(ALPHA_8, RGB_565, ARGB_4444或ARGB_8888)對每一個像素佔用的字節數無影響。

因此,若是經過inPreferredConfig來優化圖片的內存佔用,就須要webp或png24格式的圖片,png24與png32相比,也就是不支持透明度而已,對於大多數圖片來講,二者沒有明顯的差異。固然,做爲一種新的圖片格式,web可認爲是一種不錯的選擇。

注意: 9patch圖雖然在使用時會根據View的尺寸進行放大,但其像素仍然不變,可視爲普通圖片來處理;

網絡圖片

網絡圖片一般咱們都是使用開源庫進行加載(這裏順便推薦一個好用的圖片加載庫ImageSet), inPreferredConfig的值一般可在初始化時進行配置,至於縮放,可以讓後臺進行實現。即:根據圖片的請求參數返回合適的尺寸。最大也只須要控件的大小便可,再大也沒意義,不只浪費流量,還佔用內存。若是你的APP中有不少圖片,那麼可對圖片的寬高根據設備的內存狀況進行適當的縮小:

// 根據內存大小設置縮放係數
public static float getDefaultScale() {
    float scale = 1.0f;
    int totalMemorySize = AndroidPlatformUtil.getTotalMemorySize();
    if (totalMemorySize >= 4) {
        scale = 1.0f;
    } else if (totalMemorySize >= 2 && totalMemorySize < 4) {
        scale = 0.8f;
    } else {
        scale = 0.6f;
    }

    return scale;
}

// 獲取設備的內存大小,返回值單位爲G
public static int getTotalMemorySize(){
	String path = "/proc/meminfo";
	String firstLine = null;
	FileReader fileReader = null;
	BufferedReader bufferedReader = null;
	try{
		fileReader = new FileReader(path);
		bufferedReader = new BufferedReader(fileReader,8192);
		firstLine = bufferedReader.readLine().split("\\s+")[1];
	} catch (Exception e){
		e.printStackTrace();
	} finally {
		try {
			if (bufferedReader != null) {
				bufferedReader.close();
			}
		} catch (Exception e) {
			e.printStackTrace(System.out);
		}

		try {
			if (fileReader != null) {
				fileReader.close();
			}
		} catch (Exception e) {
			e.printStackTrace(System.out);
		}
	}

	if(TextUtils.isEmpty(firstLine)){
		return (int)Math.ceil((new Float(Float.valueOf(firstLine) / (1024 * 1024)).doubleValue()));
	}

	return 0;
}
複製代碼

總結

對於一個多圖片的APP來講,圖片所佔內存的優化是一項必不可少的工做。總的來講,其優化也就是經過縮放和指定Bitmap.Config的值來實現的,只是不一樣位置,不一樣格式的圖片有所差別而已。

相關文章
相關標籤/搜索