Bitmap內(nèi)存處理

Bitmap內(nèi)存處理

在Android開發(fā)中,Bitmap對象通常占用大量內(nèi)存,尤其是在處理高分辨率圖像時。優(yōu)化Bitmap的內(nèi)存使用對于提高應(yīng)用性能和避免內(nèi)存溢出(OutOfMemoryError)至關(guān)重要。以下是一些常見的Bitmap內(nèi)存優(yōu)化方法:

1. 使用合適的圖像分辨率

*   根據(jù)顯示需求加載適當(dāng)分辨率的圖像。例如,如果圖像只需在小的ImageView中顯示,則無需加載高分辨率的圖像。
*   使用`BitmapFactory.Options`類中的`inSampleSize`屬性來在解碼時對圖像進行下采樣。這可以在不降低圖像質(zhì)量的情況下減少內(nèi)存使用。

在Android開發(fā)中,加載大圖片時很容易消耗大量內(nèi)存,從而導(dǎo)致OutOfMemoryError。使用BitmapFactory.Options類中的inSampleSize屬性可以在解碼圖像時對圖像進行下采樣,從而減少內(nèi)存使用。下采樣是降低圖像分辨率的過程,通過該過程可以減小圖像的尺寸,但并不會明顯降低圖像的視覺質(zhì)量(在一定范圍內(nèi))。

下面是一個具體的實現(xiàn)步驟:

  1. 計算inSampleSize的值:
    根據(jù)圖像的原始尺寸和目標(biāo)尺寸,計算出合適的inSampleSize。
    inSampleSize是解碼時對圖像寬高進行縮放的倍數(shù),它的值必須是2的冪(如1、2、4、8等)。
  2. 配置BitmapFactory.Options:
    設(shè)置inSampleSize屬性。
    把BitmapFactory.Options傳遞給解碼方法。
  3. 解碼圖像:
    使用BitmapFactory.decodeFile()、BitmapFactory.decodeResource()或BitmapFactory.decodeStream()等方法來解碼圖像,并傳入配置好的BitmapFactory.Options。
    以下是一個具體的代碼示例:
import android.graphics.Bitmap;  
import android.graphics.BitmapFactory;  
import android.util.Log;  
  
public class ImageUtil {  
  
    // 計算inSampleSize的方法  
    public static int calculateInSampleSize(  
            BitmapFactory.Options options, int reqWidth, int reqHeight) {  
        // 原始圖像的寬度和高度  
        final int height = options.outHeight;  
        final int width = options.outWidth;  
        int inSampleSize = 1;  
  
        if (height > reqHeight || width > reqWidth) {  
  
            final int halfHeight = height / 2;  
            final int halfWidth = width / 2;  
  
            // 計算最大值的采樣率,保持圖像尺寸大于需求的尺寸  
            while ((halfHeight / inSampleSize) >= reqHeight  
                    && (halfWidth / inSampleSize) >= reqWidth) {  
                inSampleSize *= 2;  
            }  
        }  
  
        return inSampleSize;  
    }  
  
    // 從文件中加載圖片的示例方法  
    public static Bitmap decodeSampledBitmapFromFile(String filePath,  
                                                     int reqWidth, int reqHeight) {  
  
        // 第一次解碼以獲取圖像尺寸  
        final BitmapFactory.Options options = new BitmapFactory.Options();  
        options.inJustDecodeBounds = true;  
        BitmapFactory.decodeFile(filePath, options);  
  
        // 計算inSampleSize  
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);  
  
        // 第二次解碼以獲取實際圖像  
        options.inJustDecodeBounds = false;  
        return BitmapFactory.decodeFile(filePath, options);  
    }  
      
    // 從資源中加載圖片的示例方法  
    public static Bitmap decodeSampledBitmapFromResource(android.content.res.Resources res, int resId,  
                                                        int reqWidth, int reqHeight) {  
  
        // 第一次解碼以獲取圖像尺寸  
        final BitmapFactory.Options options = new BitmapFactory.Options();  
        options.inJustDecodeBounds = true;  
        BitmapFactory.decodeResource(res, resId, options);  
  
        // 計算inSampleSize  
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);  
  
        // 第二次解碼以獲取實際圖像  
        options.inJustDecodeBounds = false;  
        return BitmapFactory.decodeResource(res, resId, options);  
    }  
  
}
// 從文件中加載圖片  
String filePath = "/path/to/your/image.jpg";  
int targetWidth = 1024;  // 目標(biāo)寬度  
int targetHeight = 768;  // 目標(biāo)高度  
Bitmap bitmap = ImageUtil.decodeSampledBitmapFromFile(filePath, targetWidth, targetHeight);  
  
// 從資源中加載圖片  
int resourceId = R.drawable.your_image;  
Bitmap bitmapFromResource = ImageUtil.decodeSampledBitmapFromResource(getResources(), resourceId, targetWidth, targetHeight);  
  
// 將Bitmap設(shè)置到ImageView中  
imageView.setImageBitmap(bitmap);

通過上述步驟,可以在加載圖像時有效地減少內(nèi)存使用,避免OutOfMemoryError,同時盡量保持圖像的質(zhì)量。

2. 使用合適的圖像格式

*   選擇適當(dāng)?shù)膱D像格式。例如,ARGB\_8888格式每個像素占用4個字節(jié),而RGB\_565格式每個像素只占用2個字節(jié)。如果不需要透明度,可以選擇RGB\_565來減少內(nèi)存占用。
*   使用`Bitmap.Config`枚舉來選擇合適的圖像配置。

在Android開發(fā)中,選擇適當(dāng)?shù)膱D像格式可以顯著影響應(yīng)用程序的內(nèi)存使用和性能。如果你不需要透明度信息,可以使用 RGB_565 格式,它每個像素只占用2個字節(jié),相比 ARGB_8888 每個像素4個字節(jié)的內(nèi)存占用,可以節(jié)省大量內(nèi)存。

以下是如何在Android中加載和設(shè)置不同圖像格式的示例代碼。

使用 BitmapFactory 解碼圖像并設(shè)置格式
當(dāng)你從文件系統(tǒng)、資源或輸入流中加載圖像時,可以使用 BitmapFactory 類,并通過 BitmapFactory.Options 來設(shè)置圖像解碼的參數(shù)。

從資源中加載圖像:

// 從資源中加載圖片并設(shè)置為 RGB_565 格式  
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {  
    // 第一次解碼以獲取圖像尺寸  
    final BitmapFactory.Options options = new BitmapFactory.Options();  
    options.inJustDecodeBounds = true;  
    BitmapFactory.decodeResource(res, resId, options);  
  
    // 計算inSampleSize(縮放比例)  
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);  
  
    // 設(shè)置解碼參數(shù)為非只讀取邊界,并設(shè)置目標(biāo)格式為 RGB_565  
    options.inJustDecodeBounds = false;  
    // This line is crucial to set the desired config  
    options.inPreferredConfig = Bitmap.Config.RGB_565;  
  
    // 第二次解碼以獲取實際圖像  
    return BitmapFactory.decodeResource(res, resId, options);  
}  
  
// 計算縮放比例的方法  
private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {  
    final int height = options.outHeight;  
    final int width = options.outWidth;  
    int inSampleSize = 1;  
  
    if (height > reqHeight || width > reqWidth) {  
        final int halfHeight = height / 2;  
        final int halfWidth = width / 2;  
  
        while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) {  
            inSampleSize *= 2;  
        }  
    }  
  
    return inSampleSize;  
}

從文件中加載圖像

// 從文件中加載圖片并設(shè)置為 RGB_565 格式  
public static Bitmap decodeSampledBitmapFromFile(String filePath, int reqWidth, int reqHeight) {  
    // 第一次解碼以獲取圖像尺寸  
    final BitmapFactory.Options options = new BitmapFactory.Options();  
    options.inJustDecodeBounds = true;  
    BitmapFactory.decodeFile(filePath, options);  
  
    // 計算inSampleSize(縮放比例)  
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);  
  
    // 設(shè)置解碼參數(shù)為非只讀取邊界,并設(shè)置目標(biāo)格式為 RGB_565  
    options.inJustDecodeBounds = false;  
    // This line is crucial to set the desired config  
    options.inPreferredConfig = Bitmap.Config.RGB_565;  
  
    // 第二次解碼以獲取實際圖像  
    return BitmapFactory.decodeFile(filePath, options);  
}

使用 ImageView 設(shè)置圖像
加載完Bitmap后,可以將其設(shè)置到 ImageView 中進行顯示:

ImageView imageView = findViewById(R.id.imageView);  
Bitmap bitmap = decodeSampledBitmapFromResource(getResources(), R.drawable.your_image, 1024, 768);  
imageView.setImageBitmap(bitmap);

注意事項

  1. 質(zhì)量 vs 內(nèi)存:RGB_565 格式雖然節(jié)省內(nèi)存,但色彩表現(xiàn)不如 ARGB_8888 豐富。如果圖像質(zhì)量對你的應(yīng)用至關(guān)重要,請慎重選擇。
    兼容性:大多數(shù)現(xiàn)代設(shè)備都能很好地處理
  2. RGB_565,但在某些特定場景下(如需要高精度顏色表現(xiàn)的圖像處理任務(wù)),可能需要使用 ARGB_8888。

通過適當(dāng)選擇圖像格式和解碼參數(shù),你可以在內(nèi)存使用和圖像質(zhì)量之間找到一個平衡點,從而提高應(yīng)用的性能和用戶體驗。

ARGB_8888和RGB_565之外的格式

  1. ARGB_4444
    特點:ARGB_4444是一種16位的位圖配置,其中Alpha(透明度)、Red(紅色)、Green(綠色)和Blue(藍色)通道各占4位。這意味著每個顏色通道只有16種可能的顏色值(從0到15),因此ARGB_4444的顏色精度相對較低。
    適用場景:ARGB_4444適用于需要透明度但不需要高顏色精度的場合。由于它的內(nèi)存占用較?。總€像素16位),因此在內(nèi)存受限的設(shè)備或應(yīng)用程序中,ARGB_4444可能是一個合理的選擇。然而,由于顏色精度的限制,ARGB_4444可能不適合顯示高質(zhì)量圖像或進行顏色敏感的圖像處理任務(wù)。
  2. ALPHA_8
    特點:ALPHA_8是一種8位的位圖配置,它僅存儲透明度信息,不包含顏色信息。每個像素只有8位,因此只能表示256種不同的透明度級別。
    適用場景:ALPHA_8適用于僅需要透明度而不需要顏色信息的場合。例如,在圖像處理中,你可能需要一個遮罩層來確定哪些部分應(yīng)該被顯示或隱藏,而不需要關(guān)心這些部分的實際顏色。在這種情況下,ALPHA_8是一個高效且內(nèi)存占用小的選擇。然而,由于它不包含顏色信息,因此ALPHA_8不能用于顯示彩色圖像。

3. 使用內(nèi)存緩存

*   利用`LruCache`或`DiskLruCache`等緩存機制來緩存已經(jīng)加載的Bitmap對象,避免重復(fù)加載和內(nèi)存浪費。
*   當(dāng)內(nèi)存緊張時,可以自動回收不再使用的Bitmap對象,釋放內(nèi)存。

3.1 使用 LruCache:

import android.graphics.Bitmap;  
import android.graphics.BitmapFactory;  
import android.os.Bundle;  
import android.widget.ImageView;  
import androidx.annotation.Nullable;  
import androidx.appcompat.app.AppCompatActivity;  
import androidx.collection.LruCache;  
  
public class MainActivity extends AppCompatActivity {  
  
    private LruCache<String, Bitmap> bitmapCache;  
  
    @Override  
    protected void onCreate(@Nullable Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
  
        // 初始化 LruCache  
        final int maxMemory = 4 * 1024 * 1024; // 4MB  
        // 設(shè)置緩存的最大大小(例如,緩存中所有 Bitmap 對象的總大小不超過 4MB)  
        bitmapCache = new LruCache<String, Bitmap>(maxMemory) {  
            @Override  
            protected int sizeOf(String key, Bitmap bitmap) {  
                // 重寫此方法以測量 Bitmap 的大小,默認返回 Bitmap 占用的字節(jié)數(shù)  
                return bitmap.getByteCount();  
            }  
        };  
  
        ImageView imageView = findViewById(R.id.imageView);  
        String key = "example_bitmap_key";  
  
        // 嘗試從緩存中獲取 Bitmap  
        Bitmap bitmap = bitmapCache.get(key);  
  
        if (bitmap != null) {  
            // 使用緩存中的 Bitmap  
            imageView.setImageBitmap(bitmap);  
        } else {  
            // 加載 Bitmap(例如從資源中)  
            bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.example_image);  
            // 將 Bitmap 添加到緩存中  
            bitmapCache.put(key, bitmap);  
            // 設(shè)置 ImageView  
            imageView.setImageBitmap(bitmap);  
        }  
    }  
}

注意事項

  1. 緩存大小:設(shè)置適當(dāng)?shù)木彺娲笮》浅V匾?,以防止?nèi)存溢出。
  2. 鍵的唯一性:確保每個緩存項的鍵是唯一的,以便正確管理緩存。
  3. 線程安全:如果你的應(yīng)用在多線程環(huán)境中操作緩存,考慮使用 synchronized 塊或其他線程同步機制來確保線程安全。

3.2 使用 DiskLruCache

DiskLruCache 是一個由 Android 提供的磁盤緩存機制,它可以幫助我們緩存數(shù)據(jù)到磁盤中,以便在后續(xù)使用中能夠快速獲取,從而減少重復(fù)加載和數(shù)據(jù)浪費。對于 Bitmap 對象的緩存,我們可以使用 DiskLruCache 將 Bitmap 存儲到磁盤中,并在需要時從磁盤中讀取。

以下是一個使用 DiskLruCache 緩存 Bitmap 對象的基本實現(xiàn)步驟:

  1. 初始化 DiskLruCache:
    首先,我們需要指定緩存的目錄和緩存的最大大小來初始化 DiskLruCache。通常,緩存目錄可以選擇在應(yīng)用的內(nèi)部存儲或外部存儲中。
  2. 將 Bitmap 寫入 DiskLruCache:
    當(dāng)我們加載一個新的 Bitmap 時,可以將其寫入 DiskLruCache 中。我們需要將 Bitmap 轉(zhuǎn)換為一個字節(jié)數(shù)組或流,然后將其存儲到緩存中。
  3. 從 DiskLruCache 讀取 Bitmap:
    在需要顯示 Bitmap 時,我們首先嘗試從 DiskLruCache 中讀取。如果緩存中存在該 Bitmap,則直接將其讀出并顯示;否則,我們需要加載 Bitmap 并將其寫入緩存中。
    下面是一個簡單的代碼示例,展示了如何使用 DiskLruCache 緩存 Bitmap 對象:
import android.content.Context;  
import android.graphics.Bitmap;  
import android.graphics.BitmapFactory;  
import android.os.Build;  
import androidx.annotation.RequiresApi;  
  
import com.jakewharton.disklrucache.DiskLruCache;  
  
import java.io.File;  
import java.io.IOException;  
import java.io.InputStream;  
import java.io.OutputStream;  
  
/**  
 * 一個簡單的 Bitmap 緩存類,使用 DiskLruCache 進行磁盤緩存。  
 */  
public class BitmapCache {  
  
    // 磁盤緩存的最大大小,單位為字節(jié)。這里設(shè)置為 10MB。  
    private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10;  
  
    // 緩存目錄的名稱。  
    private static final String CACHE_DIR = "bitmap_cache";  
  
    // DiskLruCache 實例。  
    private DiskLruCache diskLruCache;  
  
    /**  
     * 構(gòu)造函數(shù),初始化 DiskLruCache。  
     *  
     * @param context 上下文,用于獲取緩存目錄。  
     */  
    public BitmapCache(Context context) {  
        try {  
            // 獲取緩存目錄,如果不存在則創(chuàng)建。  
            File cacheDir = new File(context.getCacheDir(), CACHE_DIR);  
            if (!cacheDir.exists()) {  
                cacheDir.mkdirs();  
            }  
  
            // 初始化 DiskLruCache,設(shè)置緩存目錄、版本號、每個 key 對應(yīng)的文件數(shù)量以及緩存總大小。  
            diskLruCache = DiskLruCache.open(cacheDir, 1, 1, DISK_CACHE_SIZE);  
        } catch (IOException e) {  
            // 捕獲并打印異常信息。  
            e.printStackTrace();  
        }  
    }  
  
    /**  
     * 從緩存中獲取 Bitmap。  
     *  
     * @param key 緩存的鍵。  
     * @return 對應(yīng)的 Bitmap 對象,如果緩存中不存在則返回 null。  
     */  
    public Bitmap getBitmap(String key) {  
        Bitmap bitmap = null;  
        try {  
            // 通過鍵獲取緩存的快照。  
            DiskLruCache.Snapshot snapshot = diskLruCache.get(key);  
            if (snapshot != null) {  
                // 如果快照存在,則從快照中獲取輸入流,并解碼為 Bitmap。  
                InputStream inputStream = snapshot.getInputStream(0);  
                bitmap = BitmapFactory.decodeStream(inputStream);  
                // 關(guān)閉快照以釋放資源。  
                snapshot.close();  
            }  
        } catch (IOException e) {  
            // 捕獲并打印異常信息。  
            e.printStackTrace();  
        }  
        return bitmap;  
    }  
  
    /**  
     * 將 Bitmap 存入緩存中。  
     *  
     * @param key   緩存的鍵。  
     * @param bitmap 要緩存的 Bitmap 對象。  
     */  
    @RequiresApi(api = Build.VERSION_CODES.KITKAT)  
    public void putBitmap(String key, Bitmap bitmap) {  
        try {  
            // 通過鍵獲取緩存的編輯器。  
            DiskLruCache.Editor editor = diskLruCache.edit(key);  
            if (editor != null) {  
                // 如果編輯器獲取成功,則創(chuàng)建輸出流,并將 Bitmap 壓縮后寫入。  
                OutputStream outputStream = editor.newOutputStream(0);  
                bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);  
                // 刷新輸出流以確保數(shù)據(jù)寫入。  
                outputStream.flush();  
                // 提交編輯,將數(shù)據(jù)真正寫入緩存。  
                editor.commit();  
            }  
        } catch (IOException e) {  
            // 捕獲并打印異常信息。  
            e.printStackTrace();  
        }  
    }  
  
    /**  
     * 關(guān)閉 DiskLruCache,釋放資源。  
     */  
    public void close() {  
        try {  
            if (diskLruCache != null) {  
                // 如果 DiskLruCache 不為空,則關(guān)閉它。  
                diskLruCache.close();  
            }  
        } catch (IOException e) {  
            // 捕獲并打印異常信息。  
            e.printStackTrace();  
        }  
    }  
}

注意事項:

  1. 線程安全:DiskLruCache 本身不是線程安全的,如果在多線程環(huán)境中使用,需要確保線程安全。
  2. 緩存大?。焊鶕?jù)實際情況設(shè)置合適的緩存大小,避免占用過多磁盤空間。
  3. 緩存鍵:選擇合適的緩存鍵,確保每個 Bitmap 對象都有唯一的鍵與之對應(yīng)。
  4. 關(guān)閉緩存:在不再需要使用 DiskLruCache 時,記得調(diào)用 close() 方法關(guān)閉緩存,釋放資源。
  5. 異常處理:在實際應(yīng)用中,需要添加更多的異常處理邏輯,以確保應(yīng)用的穩(wěn)定性和健壯性。

4. 及時回收Bitmap資源

*   當(dāng)Bitmap對象不再需要時,確保調(diào)用`bitmap.recycle()`方法來回收其占用的內(nèi)存。
*   注意:在Android 4.0(API級別14)及更高版本中,如果Bitmap是通過`BitmapFactory`的解碼方法創(chuàng)建的,并且沒有通過`inMutable`選項設(shè)置為可變,那么調(diào)用`recycle()`是安全的。但是,如果Bitmap是通過其他方式創(chuàng)建的(如從文件中讀?。瑒t可能需要謹慎處理。

5. 使用更小的圖像加載庫

*   考慮使用像Glide、Picasso或Fresco這樣的圖像加載庫。這些庫通常提供了高效的圖像加載、緩存和內(nèi)存管理功能,可以減少手動優(yōu)化Bitmap內(nèi)存的復(fù)雜性。

6. 避免在UI線程上解碼圖像

*   圖像解碼通常是一個耗時的操作,應(yīng)該在后臺線程上進行,以避免阻塞UI線程。
*   使用異步任務(wù)或Handler來在后臺線程上解碼圖像,并將解碼后的Bitmap傳遞回UI線程進行顯示。

7. 注意Bitmap對象的生命周期

*   確保在Activity或Fragment的適當(dāng)生命周期方法中管理Bitmap對象的創(chuàng)建和銷毀。
*   在`onPause()`、`onStop()`或`onDestroy()`方法中釋放不再需要的Bitmap資源。

通過采用這些優(yōu)化方法,可以顯著減少Bitmap對象對內(nèi)存的使用,提高Android應(yīng)用的性能和穩(wěn)定性。

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

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

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