欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android位圖(圖片)加載引入的內(nèi)存溢出問題詳細解析

 更新時間:2022年12月26日 08:36:21   作者:itbird01  
Android在加載大背景圖或者大量圖片時,常常致使內(nèi)存溢出,下面這篇文章主要給大家介紹了關于Android位圖(圖片)加載引入的內(nèi)存溢出問題的相關資料,需要的朋友可以參考下

Android ImageView進行圖片加載時,經(jīng)常會遇到內(nèi)存溢出的問題,本文針對于這一問題出現(xiàn)的定義、原理、過程、解決方案做統(tǒng)一總結(jié)。

1.一些定義

在分析具體問題之前,我們先了解一些基本概念,這樣可以幫助理解后面的原理部分。當然了,大家對于這部分定義已經(jīng)了然于胸的,就可以跳過了。

什么是內(nèi)存泄露?

我們知道Java GC管理的主要區(qū)域是堆,Java中幾乎所有的實例對象數(shù)據(jù)實際是存儲在堆上的(當然JDK1.8之后,針對于不會被外界調(diào)用的數(shù)據(jù)而言,JVM是放置于棧內(nèi)的)。針對于某一程序而言,堆的大小是固定的,我們在代碼中新建對象時,往往需要在堆中申請內(nèi)存,那么當系統(tǒng)不能滿足需求,于是產(chǎn)生溢出?;蛘呖梢赃@樣理解堆上分配的內(nèi)存沒有被釋放,從而失去對其控制。這樣會造成程序能使用的內(nèi)存越來越少,導致系統(tǒng)運行速度減慢,嚴重情況會使程序宕掉。

什么是位圖?

位圖使用我們稱為像素的一格一格的小點來描述圖像,計算機屏幕其實就是一張包含大量像素點的網(wǎng)格,在位圖中,平時看到的圖像將會由每一個網(wǎng)格中的像素點的位置和色彩值來決定,每一點的色彩是固定的,而每個像素點色彩值的種類,產(chǎn)生了不同的位圖Config,常見的有:

ALPHA_8, 代表8位Alpha位圖,每個像素占用1byte內(nèi)存 RGB_565,代表8位RGB位圖,每個像素占用2byte內(nèi)存 ARGB_4444 (@deprecated),代表16位ARGB位圖,每個像素占用2byte內(nèi)存 ARGB_8888,代表32位ARGB位圖,每個像素占用4byte內(nèi)存

其實很好理解,我們知道RGB是指紅藍綠,不同的config代表,計算機中每種顏色用幾位二進制位來表示,例如:RGB_565代表紅 5為、藍6位、綠5為。

2.原理分析

2.1 原理分析一

由第一節(jié)的基礎定義,我們知道不過JVM還是Android虛擬機,對于每個應用程序可用內(nèi)存大小是有約束的,而針對于單個程序中Bitmap所占的內(nèi)存大小也有約束(一般機器是8M、16M,大家可以通過查看build.prop文件去查看這個定義大?。?,一旦超過了這個大小,就會報OOM錯誤。 Android編程中,我們經(jīng)常會使用ImageView 控件,加載圖片,例如以下代碼:

package com.itbird.BitmapOOM;

import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.widget.ImageView;

import androidx.appcompat.app.AppCompatActivity;

import com.itbird.R;

public class ImageViewLoadBitmapTestActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.imageviewl_load_bitmap_test);
        ImageView imageView =  findViewById(R.id.imageview);
        imageView.setImageResource(R.drawable.bigpic);
        imageView.setBackgroundResource(R.drawable.bigpic);
        imageView.setImageBitmap(BitmapFactory.decodeFile("path/big.jpg"));
        imageView.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.bigpic));
    }
}

當圖片很小時,一般不會有問題,當圖片很大時,就會出現(xiàn)OOM錯誤,原因是直接調(diào)用decodeResource、setImageBitmap、setBackgroundResource時,實際上,這些函數(shù)在完成照片的decode之后,都是調(diào)用了java底層的createBitmap來完成的,需要消耗更多的內(nèi)存。至于為什么會消耗那么多內(nèi)存,如下面的源碼分析: android8.0之前Bitmap源碼

public final class Bitmap implements Parcelable {
    private static final String TAG = "Bitmap";
     ...
    private byte[] mBuffer;
     ...
    }

android8.0之后Bitmap源碼

public final class Bitmap implements Parcelable {
    ...
    // Convenience for JNI access
    private final long mNativePtr;
    ...
 }

對上上述兩者,相信大家已經(jīng)看出點什么了,android8.0之前,Bitmap在Java層保存了byte數(shù)組,而且細跟源碼的話,您也會發(fā)現(xiàn),8.0之前雖然調(diào)用了native函數(shù),但是實際其實就是在native層創(chuàng)建Java層byte[],并將這個byte[]作為像素存儲結(jié)構(gòu),之后再通過在native層構(gòu)建Java Bitmap對象的方式,將生成的byte[]傳遞給Bitmap.java對象。(這里其實有一個小知識點,android6.0之前,源碼里面很多這樣的實現(xiàn),通過C層來創(chuàng)建Java層對象)。

而android8.0之后,Bitmap在Java層保存的只是一個地址,,Bitmap像素內(nèi)存的分配是在native層直接調(diào)用calloc,所以其像素分配的是在native heap上, 這也是為什么8.0之后的Bitmap消耗內(nèi)存可以無限增長,直到耗盡系統(tǒng)內(nèi)存,也不會提示Java OOM的原因。

2.2 原理分析二

看完上面的源碼解讀,大家一定想知道,那我如果在自己應用中的確有大圖片的加載需求,那怎么辦呢?調(diào)用哪個函數(shù)呢? BitmapFactory.java中有一個Bitmap decodeStream(InputStream is)這個函數(shù),我們可以查看源碼,這個函數(shù)底層調(diào)用了native c函數(shù)

在底層進行了decode之后,轉(zhuǎn)換為了bitmap對象,返回給Java層。

3 編程中如何避免圖片加載的OOM錯誤

通過上面章節(jié)的知識探索,相信大家已經(jīng)知道了加載圖片時出現(xiàn)OOM錯誤的原因,其實真正的原因并未是網(wǎng)上很多文章說的,不要使用調(diào)用ImageView的某某函數(shù)、BitmapFactory的某某函數(shù),真正的原因是,對于大圖片,Java堆和Native堆無法申請到可用內(nèi)存時,就會出現(xiàn)OOM錯誤,那么針對于不同的系統(tǒng)版本,Android存儲、創(chuàng)建圖片的方式又有所不同,帶來了加載大圖片時的OOM錯誤。 那么接下來,大家最關心的解決方案,有哪些?我們在日常編碼中,應該如何編碼,才能有效規(guī)避此類錯誤的出現(xiàn),別急。

3.1 利用BitmapFactory.decodeStream加載InputStream圖片字節(jié)流的方式顯示圖片

 /**
     * 以最省內(nèi)存的方式讀取本地資源的圖片
     */
    public static Bitmap readBitMap(String path, BitmapFactory.Options opt, InputStream is) {
        opt.inPreferredConfig = Bitmap.Config.RGB_565;
        if (Build.VERSION.SDK_INT <=android.os.Build.VERSION_CODES.KITKAT ) {
            opt.inPurgeable = true;
            opt.inInputShareable = true;
        }
        opt.inSampleSize = 2;//二分之一縮放,可寫1即100%顯示
        //獲取資源圖片
        try {
            is = new FileInputStream(path);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        return BitmapFactory.decodeStream(is, null, opt);
    }

大家可以看到上面的代碼,實際上一方面針對Android 4.4之下的直接聲明了opt屬性,告訴系統(tǒng)可以回收,一方面直接進行了圖片縮放。說到這里,大家會有疑問,為什么是android4.4以下加這兩個屬性,難道之后就不用了了。不要著急,我們看源碼:

可以看到源碼上說明,此屬性4.4之前有用,5.0之后即使設置了,底層也是忽略的。也許大家會問,難道5.0之后Bitmap的源碼有什么大的改動嗎?的確是,可以看一下以下源碼。 8.0之后的Bitmap內(nèi)存回收機制 NativeAllocationRegistry是Android 8.0引入的一種輔助自動回收native內(nèi)存的一種機制,當Java對象因為GC被回收后,NativeAllocationRegistry可以輔助回收Java對象所申請的native內(nèi)存,拿Bitmap為例,如下:

Bitmap(long nativeBitmap, int width, int height, int density,
        boolean isMutable, boolean requestPremultiplied,
        byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets) {
    ...
    mNativePtr = nativeBitmap;
    long nativeSize = NATIVE_ALLOCATION_SIZE + getAllocationByteCount();
    <!--輔助回收native內(nèi)存-->
    NativeAllocationRegistry registry = new NativeAllocationRegistry(
        Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), nativeSize);
    registry.registerNativeAllocation(this, nativeBitmap);
   if (ResourcesImpl.TRACE_FOR_DETAILED_PRELOAD) {
        sPreloadTracingNumInstantiatedBitmaps++;
        sPreloadTracingTotalBitmapsSize += nativeSize;
    }
}

當然這個功能也要Java虛擬機的支持,有機會再分析。

**實際使用效果:**3M以內(nèi)的圖片加載沒有問題,但是大家注意到一點,沒我們代碼中是固定縮放了一般,這時大家肯定有疑問,有沒有可能,去動態(tài)根據(jù)圖片的大小,決定縮放比例。

3.2 利用BitmapFactory.decodeStream通過按比例壓縮方式顯示圖片

    /**
     * 以計算的壓縮比例加載大圖片
     *
     * @param res
     * @param resId
     * @param reqWidth
     * @param reqHeight
     * @return
     */
    public static Bitmap decodeCalSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {
        // 檢查bitmap的大小
        final BitmapFactory.Options options = new BitmapFactory.Options();
        // 設置為true,BitmapFactory會解析圖片的原始寬高信息,并不會加載圖片
        options.inJustDecodeBounds = true;
        options.inPreferredConfig = Bitmap.Config.RGB_565;

        BitmapFactory.decodeResource(res, resId, options);

        // 計算采樣率
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

        // 設置為false,加載bitmap
        options.inJustDecodeBounds = false;

        return BitmapFactory.decodeResource(res, resId, options);
    }

    /*********************************
     * @function: 計算出合適的圖片倍率
     * @options: 圖片bitmapFactory選項
     * @reqWidth: 需要的圖片寬
     * @reqHeight: 需要的圖片長
     * @return: 成功返回倍率, 異常-1
     ********************************/
    private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth,
                                             int reqHeight) {
        // 設置初始壓縮率為1
        int inSampleSize = 1;
        try {
            // 獲取原圖片長寬
            int width = options.outWidth;
            int height = options.outHeight;
            // reqWidth/width,reqHeight/height兩者中最大值作為壓縮比
            int w_size = width / reqWidth;
            int h_size = height / reqHeight;
            inSampleSize = w_size > h_size ? w_size : h_size;  // 取w_size和h_size兩者中最大值作為壓縮比
            Log.e("inSampleSize", String.valueOf(inSampleSize));
        } catch (Exception e) {
            return -1;
        }
        return inSampleSize;
    }

大家可以看到,上面代碼實際上使用了一個屬性inJustDecodeBounds,當inJustDecodeBounds設為true時,不會加載圖片僅獲取圖片尺寸信息,也就是說,我們先通過不加載實際圖片,獲取其尺寸,然后再按照一定算法(以需要的圖片長寬與實際圖片的長寬比例來計算)計算出壓縮的比例,然后再進行圖片加載。

**實際使用效果:**測試該方法可以顯示出來很大的圖片,只要你設定的長寬合理。

3,3 及時的回收和釋放

直接上代碼

 /**
     * 回收bitmap
     */
    private static void recycleBitmap(ImageView iv) {
        if (iv != null && iv.getDrawable() != null) {
            BitmapDrawable bitmapDrawable = (BitmapDrawable) iv.getDrawable();
            iv.setImageDrawable(null);
            if (bitmapDrawable != null) {
                Bitmap bitmap = bitmapDrawable.getBitmap();
                if (bitmap != null) {
                    bitmap.recycle();
                }
            }
        }
    }

    /**
     * 在Activity或Fragment的onDestory方法中進行回收(必須確保bitmap不在使用)
     */
    public static void recycleBitmap(Bitmap bitmap) {
        // 先判斷是否已經(jīng)回收
        if (bitmap != null && !bitmap.isRecycled()) {
            // 回收并且置為null
            bitmap.recycle();
            bitmap = null;
        }
    }

4.總結(jié)

4.1 OOM出現(xiàn)原因

對于大圖片,直接調(diào)用decodeResource、setImageBitmap、setBackgroundResource時,這些函數(shù)在完成照片的decode之后,都是調(diào)用了java底層的createBitmap來完成的,需要消耗更多的內(nèi)存。Java堆和Native堆無法申請到可用內(nèi)存時,就會出現(xiàn)OOM錯誤,那么針對于不同的系統(tǒng)版本,Android存儲、創(chuàng)建圖片的方式又有所不同,帶來了加載大圖片時的OOM錯誤。

4.2 解決方案

1.針對于圖片小而且頻繁加載的,可以直接使用系統(tǒng)函數(shù)setImageXXX等 2針對于大圖片,在進行ImageView setRes之前,需要先對圖片進行處理 1)壓縮 2)android4.4之前,需要設置opt,釋放bitmap,android5.0之后即使設置,系統(tǒng)也會忽略 3)設置optConfig為565,降低每個像素點的色彩值 4)針對于頻繁使用的圖片,可以使用inBitmap屬性 5)由于decodeStream直接讀取的圖片字節(jié)碼,并不會根據(jù)各種機型做自動適配,所以需要在各個資源文件夾下放置相應的資源 6)及時回收

總結(jié)

到此這篇關于Android位圖(圖片)加載引入的內(nèi)存溢出問題詳細解析的文章就介紹到這了,更多相關Android位圖加載引入內(nèi)存溢出內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

最新評論