Android如何加載大圖,防止OOM

Android加載大圖,防止OOM

本文是根據Android開發文檔寫的,其中屢次提到了堆內存,不太瞭解的同窗能夠先預習下JVM的內存模型,再來食用本文。android

OOM與SOF

OutOfMemory(內存溢出),當Java虛擬機因爲內存不足而沒法分配對象,而且垃圾回收器沒法再提供更多內存時,拋出異常OutOfMemoryError。api

StackOverFlow(棧溢出),當應用程序遞歸過深致使堆棧溢出時拋出異常StackOverflowError。緩存

(ps:內存泄露的主要緣由是new出來的Object放在Heap上沒法被gc回收,會致使內存泄漏)bash

感謝你們指出的錯誤,這裏補充一下兩個的區別網絡

爲何咱們的移動設備加載大圖須要處理

在網絡上有着許許多多的圖片,有高清的,有高糊的,在pc端咱們能夠隨心所欲,但在移動端,不可能讓咱們這樣啊,手機的內存沒有那麼的大,咱們的圖片都是加載到內存堆中,一個app分配的內存堆是有限的,咱們還要存儲其餘對象使用,還有一個重要因素就是雖然wifi已經普及,但還有大多數上了年紀的人對流量的概念不清楚,用你的app幾分鐘不只oom了或者ANR了還欠費了(UI線程加載位圖可能會下降程序性能,致使前臺響應速度變慢而ANR),因此咱們須要「改變圖片的大小」。app

處理的邏輯

顯示的時候符合實際的顯示規格,而不是整個圖片的加載,當用戶本身想看完整分辨率的圖片的時候再將完整的圖片載入內存顯示。框架

處理方案

最原始的處理方案就是官網的方案,Android對位圖的處理提供了整整一組的方法。這我想也應該是其餘加載圖片框架的基礎原理。異步

CreateScaledBitmap

CreateScaledBitmap是Bitmap中的一個api,能更具自定義建立一個新的Bitmap,若是與原來的寬高相同則不會建立新的Bitmap。 缺點:他必須先建立一個位圖。也就是說須要這個圖片先被加載,解碼。致使性能不高。 因此通常不採用該模式加載大圖,除非你的需求是顯示同一張圖片不一樣大小。ide

BitmapFactory bitmapFactory = new BitmapFactory();
Bitmap bitmap = null;
try {
    bitmap = BitmapFactory.decodeStream(getAssets().open(("scg.jpeg")));
} catch (IOException e) {
    e.printStackTrace();
}
if(null != bitmap){
    //必須傳入一個不爲null的bitmap,寬度,高度,是否使用雙線性濾波優化圖片
    Bitmap changeBt = Bitmap.createScaledBitmap(bitmap, 480,240,true); 
    imgv.setImageBitmap(bitmap);
    cImgv.setImageBitmap(changeBt);
}
複製代碼

效果圖: 性能

creatScaledBitmap

inSampleSize

BitmapFactory.Options#inSampleSize

inSampleSize

這是BitmapFactory.Options中的一個成員變量: 使用解碼器對原圖進行二次取樣,接收的是一個int值,由於咱們的位圖就是咱們的像素圖,是由一個一個的小像素塊組成的(你把一個圖使勁放大就能看到它是由一堆小方塊拼接而成的了)。小於等於1的值返回的結果都與原圖同樣,inSampleSize == 4 返回的圖像爲原始寬度/高度的1/4,像素數目的1/16。
它的原理是:行列方向每隔n格取一個像素,最後合併爲一張圖片

inSampleSize

BitmapFactory.Options cBitmapOptions = new BitmapFactory.Options();
cBitmapOptions.inSampleSize = 2;
Bitmap cbitmap = BitmapFactory.decodeResource(getResources(), R.drawable.scg, cBitmapOptions);

cImgv.setImageBitmap(cbitmap);
複製代碼

效果圖:

inSalmpSize.png

BitmapFactory.Options三件套(inScaled+inDensity+inTargetDensity)

inScaled

inScaled

inScaled設置爲true時(設置此標誌時),若是inDensityinTargetDensity不爲0,Bitmap就會在加載的時候直接進行縮放以匹配inTargetDensity,而不是繪製的時候進行縮放。(加載到堆內存時已經縮放了大小了)(.9圖會忽略此標誌)

inDensity

加載圖片的原始寬度,若是此密度與inTargetDensity不匹配,則在返回Bitmap前會將它縮放至目標密度。

inTargetDensity

目標圖片的顯示寬度,它與inScaled與inDensity結合使用,肯定如何在返回Bitmap前對其進行縮放。

原理

它的會進行相應的計算,進行顏色的混合,從而生成新的圖片。也就是說圖片越大它處理的性能也就越差。但它顯示的效果要比inSampleSize的效果好,色彩還原度要高許多。

inDensity

擴展(如何知道圖片的原始大小?)

咱們要先知道圖片的原始大小,而後對其進行目標密度的縮放,但如何知道圖片的原始大小呢?先加載到堆內存?而後再生成新的Bitmap?這樣的話和CreateScaledBitmap不是幾乎同樣了嗎?仍是會有致使OOM的風險。 咱們就要介紹inJustDecodeBounds屬性,它屬於BitmapFactory.Options,將其設置爲true,它會進行一次圖片的解析,但不會生成Bitmap對象,它返回的是null,但他會對out...屬性賦值,容許調用者查詢Bitmap而不用爲其分配內存。後續咱們就可使用BitmapFactory.Options的實例中的outWidth,outHeight獲取原始圖片的寬高,就能夠進行像素的壓縮了。

代碼

BitmapFactory.Options mBitmapOptions = new BitmapFactory.Options();
mBitmapOptions.inJustDecodeBounds = true;

BitmapFactory.decodeResource(getResources(), R.drawable.scg, mBitmapOptions);
int srcWidth = mBitmapOptions.outWidth;
int srcHeight = mBitmapOptions.outHeight;

BitmapFactory.Options cBitmapOptions = new BitmapFactory.Options();
cBitmapOptions.inScaled = true;
cBitmapOptions.inDensity = srcWidth;
cBitmapOptions.inTargetDensity = srcWidth * 2;

Bitmap cbitmap = BitmapFactory.decodeResource(getResources(), R.drawable.scg, cBitmapOptions);

cImgv.setImageBitmap(cbitmap);
Toast.makeText(this,""+mBitmapOptions.outHeight,Toast.LENGTH_SHORT).show(); //結果:265 ,我本地的圖片爲265x265 
複製代碼

效果圖:

inJustDecodeBounds

綜合

結合上面兩種方案,咱們能夠配套使用 inSampleSize + 三件套 。 先將圖片進行快速初略壓縮(inSampleSize),再進行細微的精細壓縮(三件套),只測量原圖的相應屬性使用inJustDecodeBounds,最後就能夠按須要顯示「大圖」了。想要原圖就使用下載或者只加載對應的原位圖到堆內存中就好。

總結

以上就是Android官方文檔視頻對位圖處理的方法。我想也應該是其餘框架的壓縮原理。官網視頻還介紹了Glide與Picass中有異步解碼和緩存。(這裏我想說的是這兩個框架的使用方法幾乎如出一轍,但好像在with中的綁定是不一樣的,他們的綁定生命週期不一樣,你使用Glide的with若是綁定的是this,可能按了home再回到Activity應用會crash掉,若是有興趣你們能夠去了解一下這兩個框架)

PS

在最後一個🌰中我發現:上面的那個原理圖上寫着inTargetDensity / inDensity ,它主要是按像素進行壓縮的,是一種圖形處理的規則吧!由於我肯定了inDensity後,無論怎麼修改inTargetDensity圖片的寬高都不會改變,但爲0的話圖片的清晰度不會變,inTargetDensity / inDensity 的值很小的話他會糊掉,應該就是像原理圖同樣進行色彩混合了,inTargetDensity / inDensity 的值很大的話,會有明顯的短暫白屏(應該是計算合併處理會花費大量的時間),再大就oom了,因此在處理大圖的時候纔有了總結的先使用inSampleSize。想改變圖片的長寬的話,就使用inDensity。它是根據你手機的dpi與inDensity的進行縮放的。(我模擬器使用的xmdpi是160 / inDensity,因此咱們看效果圖沒縮小一半)若是inDensity設置爲80它就會放大爲原圖的2倍。同理320就是縮小爲一半。inDensity = srcWidth的話就至關於進行了自適應。

就是說inTargetDensity能夠不設置,設置後是根據inTargetDensity / inDensity的值對圖片進行「優化」;inDensity設置是根據手機的像素密度dpi/inDensity的值進行圖片的「縮放」。

bitmap所佔內存大小計算方式:

圖片長度 x 圖片寬度 x 一個像素點佔用的字節數(因此壓縮像素也是一種壓縮方式)

相關文章
相關標籤/搜索