Android Bitmap史上最詳細全解


深度解剖Bitmap

 

一.Bitmap的相關使用

關於Bitmap,之前以為它和Drawable差不多,就是一種圖片,直到淚水打濕了我胸前的紅領巾,我決定整理一波關於Bitmap的姿勢!
在這里插入圖片描述
Bitmap相關的使用主要有兩種:

  • 1.給ImageView設置背景
  • 2.當做畫布來使用

分別對應下面兩個方法

imageView.setImageBitmap(Bitmap bm);
 Canvas canvas = new Canvas(Bitmap bm)

二.Bitmap的格式

我們知道Bitmap是位圖,是由像素點組成的,這就涉及到兩個問題,

  • 第一:如何存儲每個像素點?
  • 第二:怎么壓縮像素點?
    在這里插入圖片描述

2.1 存儲格式

Bitmap有四種存儲方式,對應Bitmap.Config中的四個常量

  • ALPHA_8:只存儲透明度,不存儲色值,1個像素點占1個字節
  • ARGB_4444:ARGB各用4位存儲,1個像素點16位占2個字節
  • ARGB_8888:ARGB各用8位存儲,1個像素點32位占4個字節
  • RGB_565:只存儲色值,不存儲透明度,默認不透明,RGB分別占5,6,5位,一個像素點占用16位2個字節。

一般情況下,我們不會使用ALPHA_8,他只存儲透明度,沒啥用處。對於ARGB_4444,它的畫質又太感人了,ARGB_8888畫質高但是占內存,RGB_565還行,就是不可以設置透明度。
注意以下三點即可:

  • 1.一般情況下用ARGB_8888格式存儲Bitmap
  • 2.ARGB_4444畫面慘不忍睹,被棄用
  • 3.假如對圖片沒有透明度要求,可以使用RGB_565,比ARGB_8888節省一半的內存開銷

2.2 壓縮格式

我們不妨來計算一下,如果一張和手機屏幕大小一樣的Bitmap圖片,采用ARGB_8888格式存儲需要多大的內存!

按照1024*768的屏幕大小來計算,每個像素需要32位也就是4個字節,

result = 1024*768*32B=25165824B=24MB

在這里插入圖片描述

一張手機屏幕大小的Bitmap圖片竟然要24M? 那就不奇怪我的app為什么一直閃退了,只不過用for循環創建了幾十個用在滑動列表里面。

所以我們必須要對圖片進行壓縮呀,壓縮格式使用枚舉類Bitmap.CompressFormat中,有以下三種:

Bitmap.CompressFormat.JPEG:采用JPEG壓縮算法,是一種有損壓縮格式,會在壓縮過程中改變圖像原本質量,畫質越差,對原來的圖片質量損傷越大,但是得到的文件比較小,而且JPEG不支持透明度,當遇到透明度像素時,會以黑色背景填充。

Bitmap.CompressFormat.PNG:采用PNG算法,是一種支持透明度的無損壓縮格式。

Bitmap.CompressFormat.WEBP:WEBP是一種同時提供了有損壓縮和無損壓縮的圖片文件格式,在14<=api<=17時,WEBP是一種有損壓縮格式,而且不支持透明度,在api18以后WEBP是一種無損壓縮格式,而且支持透明度,有損壓縮時,在質量相同的情況下,WEBP格式的圖片體積比JPEG小40%,但是編碼時間比JPEG長8倍。在無損壓縮時,無損的WEBP圖片比PNG壓縮小26%,但是WEBP的壓縮時間是PNG格式壓縮時間的5倍。

三.Bitmap創建方法

3.1 Bitmap.Options

想要創建一個Bitmap有很多種方法,其中很多方法都要求傳入一個Bitmap.Options,它是什么呢,有什么作用呢?

在這里插入圖片描述
這個參數的作用非常大,他可以設置Bitmap的采樣率,通過改變圖片的寬度高度和縮放比例等,以達到減少圖片像素數的目的,一言以蔽之,通過設置這個參數我們可以很好的控制顯示和使用Bitmap。實際開發過程中,可以靈活設置該值,以降低OOM發生的概率。

介紹幾個重要的成員變量

  • inJustDecodeBounds:boolean類型,設為true時,無需要把圖片加載入內存就可以獲取圖片的高度,寬度和圖片的MIME類型。
    高度通過options.outWidth獲取 寬度通過options.outHeight獲取
    MIME通過options.outMineType獲取

  • inSampleSize:這個字段表示采樣率,打個比方說,設置為4,則是從原本圖片的四個像素中取一個像素作為結果返回。其余的都被丟棄。可見,采樣率越大,圖片越小,失真越嚴重。 如何計算采樣率呢?看一下這段代碼你就會明白

public int getSampleSize(BitmapFactory.Options options , int dstWidth,int dstHeight){
        //dstWidth:表示目前ImageView的寬度
        //dstHeight:表示目標ImageView的高度
        //option中獲取bitmap圖片的信息
        int  rawWidth = options.outWidth;
        int  rawHeight = options.outHeight;
        int sampleSize=1;
        if(rawWidth>dstWidth||rawHeight>dstHeight){
            float ratioHeight = (float) (rawHeight/dstHeight);
            float ratioWidth = (float) (rawWidth/dstWidth);
            sampleSize = (int) Math.min(rawHeight, ratioWidth);
        }
        return sampleSize;
    }

為了更清楚的介紹下面的知識,先補充幾點:

  1. 不同名稱的資源文件夾是為了適配不同的屏幕分辨率的,當屏幕分辨率與文件所在資源文件夾對應的分辨率相等時,直接使用圖片,不需要進行放縮。
  2. 當屏幕分辨率與圖片所在文件夾所對應的分辨率不同時,會進行縮放,縮放比例是屏幕分辨率/文件夾所對應的分辨率。
  3. 從本地文件中加載圖片時,不會對圖片進行縮放噢。

inScald:這個參數表示,在可以縮放時,是否對當前文件進行放縮,如果設置為false就不放縮。設置為true,則會根據文件夾分辨率和屏幕分辨率進行動態縮放。

inPreferredConfig:這個參數是用來設置像素的存儲格式的。

關於Options就介紹這幾個關鍵的字段,下面進入重頭戲,創建Bitmap。

在這里插入圖片描述

3.2 BitmapFactory

BitmapFactory提供了多種創建bitmap的靜態方法

//從資源文件中通過id加載bitmap
//Resources res:資源文件,可以context.getResources()獲得
//id:資源文件的id,如R.drawable.xxx
public static Bitmap decodeResources(Resources res,int id)
//第二種只是第一種的重載方法,多了個Options參數
public static Bitmap decodeResources(Resources res,int id,Options opt)
//傳入文件路徑加載,比如加載sd卡中的文件
//pathName:文件的全路徑名
public static Bitmap decodeFile(String pathName);
public static Bitmap decodeFile(String pathName,Options opt);
//從byte數組中加載
//offset:對應data數組的起始下標
//length:截取的data數組的長度
public static Bitmap decodeByteArray(byte[] data,int offset , int length);
public static Bitmap decodeByteArray(byte[] data,int offset , int length,Options opt);
//從輸入流中加載圖片
//InputStream is:輸入流
//Rect outPadding:用於返回矩形的內邊距
public static Bitmap decodeStream(InputStream is);
public static Bitmap decodeStream(InputStream is,Rect outPadding,Options opt);
//FileDescriptor :包含解碼位圖的數據文件的路徑
//通過該方式從路徑加載bitmap比decodeFile更節省內存,原因不解釋了。
public static Bitmap decodeFileDescriptor(FileDescriptor fd);
public static Bitmap decodeFileDescriptor(FileDescriptor fd,Rect outPadding,Options opt);

平時用這些函數都是糊里糊塗的,今天整理了一遍發現其實有規律可尋,也更加清楚了。

3.3 Bitmap靜態方法

//width和height是長和寬單位px,config是存儲格式
static Bitmap createBitmap(int width , int height Bitmap.Config config)
// 根據一幅圖像創建一份一模一樣的實例
static Bitmap createBitmap(Bitmap bm)
//截取一幅bitmap,起點是(x,y),width和height分別對應寬高
static Bitmap createBitmap(Bitmap bm,int x,int y,int width,int height)
//比上面的裁剪函數多了兩個參數,Matrix:給裁剪后的圖像添加矩陣 boolean filter:是否給圖像添加濾波效果
static Bitmap createBitmap(Bitmap bm,int x,int y,int width,int height,Matrix m,boolean filter);
//用於縮放bitmap,dstWidth和dstHeight分別是目標寬高
createScaledBitmap(Bitmap bm,int dstWidth,int dstHeight,boolean filter)

3.4 創建Bitmap的總結

1.加載圖像可以使用BitmapFactoryBitmap.create系列方法
2.可以通過Options實現縮放圖片,獲取圖片信息,配置縮放比例等功能
3.如果需要裁剪或者縮放圖片,只能使用create系列函數
4.注意加載和創建bitmap事通過try catch捕捉OOM異常
在這里插入圖片描述

四.常見函數

4.1 函數及其參數

copy(Config config,boolean isMutable)
//根據原圖像創建一個副本,但可以指定副本的像素存儲格式
//參數含義。
//  config:像素在內存中的存儲格式,但可以指定副本的像素存儲格式
//  boolean isMutable:新建的bitmap是否可以修改其中的像素值
extractAlpha()
//主要作用是從bitmap中獲取Alpha值,生成一幅只有Alpha值得圖像,存儲格式是ALPHA_8
getByteCount()//獲取bitmap的字節數
recycle()://不用的bitmap必須要及時回收,以免造成oom
isRecycled()//判斷bitmap是否被回收,被收回不可使用會造成crash

4.2 綜合案例演示

String items[] = {"copy","extractAlpha 1","extractAlpha 2","bitmap大小","recycle","isRecycled()"};
        ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item,items);
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        spinner.setAdapter(adapter);
        spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                switch (position){
                    case 0:
                        //copy
                        Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.photo);
                        Bitmap copy = bm.copy(Bitmap.Config.ARGB_8888, true);
                        imageView.setImageBitmap(copy);
                        bm.recycle();
                        break;
                    case 1:
                        //extractAlpha 不帶參數
                        Bitmap bp = BitmapFactory.decodeResource(getResources(), R.drawable.photo);
                        Bitmap alpha = bp.extractAlpha();
                        imageView.setImageBitmap(alpha);
                        bp.recycle();
                        break;
                    case 2:
                        //extractAlpha 帶參數
                        Bitmap bp1 = BitmapFactory.decodeResource(getResources(), R.drawable.photo);
                        Paint paint = new Paint();
                        BlurMaskFilter blurMaskFilter = new BlurMaskFilter(6, BlurMaskFilter.Blur.NORMAL);
                        paint.setMaskFilter(blurMaskFilter);
                        int[] offsetXY = new int[2];
                        Bitmap alpha1 = bp1.extractAlpha(paint, offsetXY);
                        imageView.setImageBitmap(alpha1);
                        break;
                    case 3:
                        //獲取bitmap大小
                        Bitmap b = BitmapFactory.decodeResource(getResources(), R.drawable.photo);
                        Toast.makeText(getApplicationContext(), "圖片大小為:"+b.getByteCount()+"字節", Toast.LENGTH_SHORT).show();
                        break;
                    case 4:
                        //回收bitmap
                        Bitmap b1 = BitmapFactory.decodeResource(getResources(), R.drawable.photo);
                        b1.recycle();
                        if(b1.isRecycled()){
                            Toast.makeText(getApplicationContext(), "已經被回收", Toast.LENGTH_SHORT).show();
                        }
                        //isRecycled()判斷是否被回收
                        break;
                }

            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {

            }
        });

五.常見問題

5.1 Bitmap與Canvas,View,Drawable的關系

1.我們在創建一個Canvas時,可以傳入一個BitmapPaintCanvas上的繪制實際上就是繪制在Bitmap對象上的。

2.我們自定義空間所顯示的View也是通過Canvas中的Bitmap來顯示的。

3.Drawable在內存占用和繪制速度這兩個非常關鍵的點上勝過Bitmap.
在這里插入圖片描述

5.2 使用Bitmap如何造成內存溢出的?

個人認為,Bitmap容易造成內存溢出是由於位圖較大,一張屏幕大小的ARGB_8888存儲格式的圖片竟然有24M,如果有幾個這種量級的圖片在內存中,並且沒有及時回收,那會非常容易造成OOM

5.3怎么解決或者避免Bitmap內存溢出?

1.我們可以對位圖進行壓縮,壓縮手段有PNG,JPEG,WEBP
2.對不使用的Bitmap一定要及時回收。
3.在創建Bitmap時使用try catch步驟OOM異常,使程序更健壯,即使發生了OOM也不會閃退,造成不好的使用體驗.

5.4Bitmap與Drawable的轉換

5.4.1 Drawable轉換成Bitmap
public static Bitmap drawableToBitmap(Drawable drawable) {  
        // 取 drawable 的長寬  
        int w = drawable.getIntrinsicWidth();  
        int h = drawable.getIntrinsicHeight();  
  
        // 取 drawable 的顏色格式  
        Bitmap.Config config = drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888  
                : Bitmap.Config.RGB_565;  
        // 建立對應 bitmap  
        Bitmap bitmap = Bitmap.createBitmap(w, h, config);  
        // 建立對應 bitmap 的畫布  
        Canvas canvas = new Canvas(bitmap);  
        drawable.setBounds(0, 0, w, h);  
        // 把 drawable 內容畫到畫布中  
        drawable.draw(canvas);  
        return bitmap;  
    }
5.4.2 Bitmap轉換成Drawable
Bitmap bm=Bitmap.createBitmap(xxx); 
BitmapDrawable bd= new BitmapDrawable(getResource(), bm);

六.小結

以前使用bitmap全靠cv,現在掌握了這么多知識,bitmap隨便用都不會出現問題,媽媽再也不用擔心我內存溢出,太棒了!

 

相關閱讀:

抖音dou+

抖音養號

抖音怎么合拍


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM