Android性能優(yōu)化:Bitmap優(yōu)化

在日常開發(fā)的APP,大部分時候需要想用戶展示圖片信息,圖片最終對應Android中的Bitmap對象。而對于APP端來說Bitmap又是一個比較麻煩的問題,主要表現(xiàn)在Bitmap是非常占用內(nèi)存的對象,處理不當將導致APP運行卡頓甚至出現(xiàn)OOM。Google在其官方有針對Bitmap的使用專門寫了一個專題Displaying Bitmaps Efficiently

一、主動釋放Bitmap資源

當你確定這個Bitmap資源不會再被使用的時候(當然這個Bitmap不釋放可能會讓程序下一次啟動或者resume快一些,但是其占用的內(nèi)存資源太大,可能導致程序在后臺的時候被殺掉,反而得不償失),我們建議手動調(diào)用recycle()方法,釋放其Native內(nèi)存:

if(bitmap != null && !bitmap.isRecycled()){  
    bitmap.recycle(); 
    bitmap = null; 
}

調(diào)用bitmap.recycle之后,這個Bitmap如果沒有被引用到,那么就會被垃圾回收器回收。如果不主動調(diào)用這個方法,垃圾回收器也會進行回收工作,只不過垃圾回收器的不確定性太大,依賴其自動回收不靠譜(比如垃圾回收器一次性要回收好多Bitmap,那么需要的時間就會很多,導致回收的時候會卡頓)。所以我們需要主動調(diào)用recycle。

二、主動釋放ImageView的圖片資源

由于我們在實際開發(fā)中,很多情況是在xml布局文件中設置ImageView的src或者在代碼中調(diào)用ImageView.setImageResource/setImageURI/setImageDrawable等方法設置圖像,下面代碼可以回收這個ImageView所對應的資源:

 private static void recycleImageViewBitMap(ImageView imageView) {
        if (imageView != null) {
            BitmapDrawable bd = (BitmapDrawable) imageView.getDrawable();
            rceycleBitmapDrawable(bd);
        }
    }

    private static void rceycleBitmapDrawable(BitmapDrawable bitmapDrawable) {
        if (bitmapDrawable != null) {
            Bitmap bitmap = bitmapDrawable.getBitmap();
            rceycleBitmap(bitmap);
        }
        bitmapDrawable = null;
    }

    private static void rceycleBitmap(Bitmap bitmap) {
        if (bitmap != null && !bitmap.isRecycled()) {
            bitmap.recycle();
            bitmap = null;
        }
    }

三、主動釋放ImageView的背景資源

如果你的ImageView是有Background,那么下面的代碼可以釋放他:

 public static void recycleBackgroundBitMap(ImageView view) {
        if (view != null) {
            BitmapDrawable bd = (BitmapDrawable) view.getBackground();
            rceycleBitmapDrawable(bd);
        }
    }

    public static void recycleImageViewBitMap(ImageView imageView) {
        if (imageView != null) {
            BitmapDrawable bd = (BitmapDrawable) imageView.getDrawable();
            rceycleBitmapDrawable(bd);
        }
    }

    private static void rceycleBitmapDrawable(BitmapDrawable bitmapDrawable) {
        if (bitmapDrawable != null) {
            Bitmap bitmap = bitmapDrawable.getBitmap();
            rceycleBitmap(bitmap);
        }
        bitmapDrawable = null;
    }

四、盡量少用Png圖,多用NinePatch的圖

現(xiàn)在手機的分辨率越來越高,圖片資源在被加載后所占用的內(nèi)存也越來越大,所以要盡量避免使用大的PNG圖,在產(chǎn)品設計的時候就要盡量避免用一張大圖來進行展示,盡量多用NinePatch資源。

Android中的NinePatch指的是一種拉伸后不會變形的特殊png圖,NinePatch的拉伸區(qū)域可以自己定義。這種圖的優(yōu)點是體積小,拉伸不變形,可以適配多機型。Android SDK中有自帶NinePatch資源制作工具,Android-Studio中在普通png圖片點擊右鍵可以將其轉(zhuǎn)換為NinePatch資源,使用起來非常方便。

五、使用大圖之前,盡量先對其進行壓縮

圖片有不同的形狀與大小。在大多數(shù)情況下它們的實際大小都比需要呈現(xiàn)出來的要大很多。例如,系統(tǒng)的Gallery程序會顯示那些你使用設備camera拍攝的圖片,但是那些圖片的分辨率通常都比你的設備屏幕分辨率要高很多。
考慮到程序是在有限的內(nèi)存下工作,理想情況是你只需要在內(nèi)存中加載一個低分辨率的版本即可。這個低分辨率的版本應該是與你的UI大小所匹配的,這樣才便于顯示。一個高分辨率的圖片不會提供任何可見的好處,卻會占用寶貴的(precious)的內(nèi)存資源,并且會在快速滑動圖片時導致(incurs)附加的效率問題。

5.1 圖片大小壓縮:

直接使用ImageView顯示bitmap會占用較多資源,特別是圖片較大的時候,可能導致崩潰。
使用BitmapFactory.Options設置inSampleSize, 這樣做可以減少對系統(tǒng)資源的要求。
屬性值inSampleSize表示縮略圖大小為原始圖片大小的幾分之一,即如果這個值為2,則取出的縮略圖的寬和高都是原始圖片的1/2,圖片大小就為原始大小的1/4。

        BitmapFactory.Options bitmapFactoryOptions = new BitmapFactory.Options();
        bitmapFactoryOptions.inJustDecodeBounds = true;
        bitmapFactoryOptions.inSampleSize = 2;
        // 這里一定要將其設置回false,因為之前我們將其設置成了true  
        // 設置inJustDecodeBounds為true后,decodeFile并不分配空間,即,BitmapFactory解碼出來的Bitmap為Null,但可計算出原始圖片的長度和寬度  
        options.inJustDecodeBounds = false;
        Bitmap bmp = BitmapFactory.decodeFile(sourceBitmap, options);

5.2 圖片像素壓縮:

Android中圖片有四種屬性,分別是:
ALPHA_8:每個像素占用1byte內(nèi)存
ARGB_4444:每個像素占用2byte內(nèi)存
ARGB_8888:每個像素占用4byte內(nèi)存 (默認)
RGB_565:每個像素占用2byte內(nèi)存

Android默認的顏色模式為ARGB_8888,這個顏色模式色彩最細膩,顯示質(zhì)量最高。但同樣的,占用的內(nèi)存也最大。 所以在對圖片效果不是特別高的情況下使用RGB_565(565沒有透明度屬性),如下:

        public static Bitmap readBitMap(Context context, intresId) {
            BitmapFactory.Options opt = newBitmapFactory.Options();
            opt.inPreferredConfig = Bitmap.Config.RGB_565;
            opt.inPurgeable = true;
            opt.inInputShareable = true;
            //獲取資源圖片 
            InputStreamis = context.getResources().openRawResource(resId);
            returnBitmapFactory.decodeStream(is, null, opt);
        }

其他知識點:Android 關(guān)于dp dip sp px dpi density解析

  • 1.px
    px即像素(Pixel),1px代表了手機屏幕上一個物理的像素點。由于以px為單位的控件在不同手機上顯示大小不一定相同,故Android不推薦使用px來設置控件大小。

  • 2.分辨率
    分辨率通常表示為橫軸像素長度和縱軸像素長度的乘積,如320*480等。

  • 3.dpi/densityDpi
    dpi的全稱是Dots Per Inch,即點每英寸,一般被稱為像素密度,它代表了一英寸里面有多少個像素點。計算方法為屏幕總像素點(即分辨率的乘積除以屏幕大?。?,常見的取值有120,160,240。

  • 4.density
    density直譯為密度,它的計算公式為屏幕dpi除以160點每英寸,由于單位除掉了,故density只是一個比值,常見取值為1.0,1.5等。在Android中我們可以通過下面代碼獲取當前屏幕的density:getResources().getDisplayMetrics().density;

    簡單來說,可以理解為 density 的數(shù)值是 1dp=density px;densityDpi 是屏幕每英寸對應多少個點(不是像素點),在 DisplayMetrics 當中,這兩個的關(guān)系是線性的:

    density 1 1.5 2 3 3.5 4
    densityDpi 160 240 320 480 560 640
  • 5.dp(dip)
    dp,也叫做dip,全稱為Density independent pixels,叫做設備獨立像素。他是Android為了解決眾多手機dpi不同所定義的單位,dp是一種虛擬抽象的像素單位,他的計算公式為:px = dp * (dpi / 160) = dp * density。因此在dpi大小為160的手機上,1dp = 1px,而在dpi大小為320的手機上,1dp = 2px,即在屏幕越大的手機上,1dp代表的像素也越大。因此我們定義控件大小的時候應該使用dp代替使用px。

  • 6.sp
    sp是Android中定義字體大小的一種單位,全稱為Scaled Pixels,叫做放大像素。sp會根據(jù)用戶手機上設定的字體大小而改變,在用戶手機字體大小設置為正常的情況下,1sp = 1dp。sp與px之間的密度比例可以通過如下代碼獲?。?code>getResources().getDisplayMetrics().scaledDensity;

  • 7.資源文件分辨率
    一般而言,我們存放資源文件的目錄(res)會有多個子目錄,這些子目錄代表了不同系統(tǒng)屏幕分辨率:

密度 ldpi mdpi hdpi xhdpi xxhdpi xxxhdpi
中文 低分辨率 中分辨率 高分辨率 超高分辨率 超超高分辨率 超超超高分辨率
dpi 120以下 120~160 160~240 240~320 320~480 480~640
分辨率 240*320 320*480 480*800 720*1280 1080*1920 3840*2160
  • 8.找不到對應分辨率資源文件情況
    對于drawable資源,當應用在設備對應dpi目錄下沒有找到某個資源時,遵循“先高再低”原則,會從附近的分辨率獲取圖片,然后按比例進行縮放:

    比如,當前為xhdpi設備,并且只有以下幾個目錄,則drawable的尋找順序為:
    xhdpi->xxhdpi->xxxhdpi(如果沒有更高的了)->nodpi(如果有的話)->hdpi->mdpi,如果在xxhdpi中找到目標圖片,則壓縮2/3來使用,如果在mdpi中找到圖片,則放大2倍來使用。

    因此,以現(xiàn)在主流設備來說一般可能在drawable-xxhdpi放置一份即可,這樣可以盡量避免Android為我們放大圖片所導致的OOM

    對于values資源,當應用設備在當前dpi對應目錄的demins.xml中沒有找到目標條目時,采用“就近匹配”原則:

    比如,當前為hdpi設備,并且只有以下幾個目錄,則values的尋找順序為:
    hdpi->xhdpi->mdpi->values,即先向上級dpi目錄查找,再向下級dpi目錄查找,最后一路向下查找到values目錄,如果values下都找不到,就只有找values-ldpi,當然,現(xiàn)在有這個目錄的應用不多了。

單位換算

  1. 計算dpi
    例:一個手機屏幕4英寸(對角線長度),分辨率480×800,dpi如何計算?
    dpi(像素密度) = 對角線像素數(shù)量 ÷ 對角線長度
    對角線像素數(shù)量:利用勾股定理,通過屏幕分辨率480×800計算
    dpi = 233像素/英寸
    density = (233 px/inch)/(160 px/inch)=1.46

  2. 計算dp與px
    dp = (dpi / (160像素/英寸)) px = density px
    如1中的dp = 1.46px,意為在dip為233的屏幕上,1dp = 1.46px(像素)
    此時屏幕的相對分辨率為:
    寬度 = (480 / 1.46)dp = 329dp
    高度 = (800 / 1.46)dp = 548dp

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容