Android性能優(yōu)化-圖片篇

(1)drawable目錄詳解(mdpi,hdpi,xhdpi,xxhdpi,xxxhdpi)

1.1、圖片在各個目錄中要如何存放?(必須理解)

android的drawable目錄有:

  • drawable-ldpi(低密度)
  • drawable-mdpi(中等密度)
  • drawable-hdpi(高密度)
  • drawable-xhdpi(超高密度)
  • drawable-xxhdpi(超超高密度)
  • drawable-xxxhdpi(超超超高密度)
  • drawable-nohdpi(無縮放)
  • 默認(rèn)的drawable

而安卓加載圖片的原理是根據(jù)手機的密度(dpi)來選擇不同的文件夾下的圖片,如果沒有,就會從別的密度文件夾來獲取圖片并按照一定比例來縮放展示圖片。獲取設(shè)備密度的方法為:

float xdpi = getResources().getDisplayMetrics().xdpi;
float ydpi = getResources().getDisplayMetrics().ydpi;

知道了dpi,就知道會去哪個文件夾獲取圖片了:

對應(yīng)文件夾 密度范圍(dpi) 縮放比例
ldpi 0~120 0.75
mdpi 120~160 1(基準(zhǔn))
hdpi 160~240 1.5
xhdpi 240~320 2.0
xxhdpi 320~480 3.0
xxxhdpi 480~640 4.0

而當(dāng)前的主流機型密度基本都是在320~480之間,一般放兩套圖就夠了,一套放到xhdpi,一套放到xxhdpi,那么如果是一個hdpi的手機來運行會發(fā)生什么呢?安卓是有一套圖片匹配規(guī)則的:

一個hdpi密度的手機,肯定是先去匹配hdpi目錄下的圖片,如果沒有,那么就會向上級去查找,分別是xhdpi->xxhdpi->xxxhdpi->nodpi,如果都沒有,就會往下級目錄去查,分別是mdpi->ldpi,如果還沒有,就會去drawable目錄去查找,如果還沒有!就會開啟自毀模式,嘣!!(resouse not found)

1.2、相同圖片在不同目錄所占用資源分析(了解即可)

不知道有沒有朋友做過這樣一個實驗,把一個xhdpi密度的圖片放到xxhdpi密度的文件夾中去加載會發(fā)生什么,它所占的內(nèi)存會有區(qū)別嗎?
例如一個在xhdpi下為200 * 200的圖片,xxhdpi密度下為300 * 300,但此時圖片只有200 * 200,所以它會放大到300 * 300,這時候圖片就會出現(xiàn)模糊的情況了,所以如果你在項目中發(fā)現(xiàn)圖片模糊的情況可以檢查下圖片的尺寸是否正確。

接下來我們來做一個實驗,使用一個名叫icon_database_xls 64 * 64 PNG的圖片,將它放到xhdpi目錄中,執(zhí)行下面的代碼獲取內(nèi)存占用情況:

Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.icon_database_xls);
Log.i("HJ",bitmap.getByteCount()+"");

結(jié)果如下:

2019-01-08 10:56:01.872 18507-18507/jie.com.imageoptimize I/HJ: 30976

接下來將圖片放到xxhdpi目錄下,再次執(zhí)行代碼:

2019-01-08 10:56:32.123 18677-18677/jie.com.imageoptimize I/HJ: 13924

可以看到結(jié)果差距巨大,我們知道,一個圖片的內(nèi)存占用公式為:圖片內(nèi)存寬度 * 圖片內(nèi)存高度* 每像素占用的內(nèi)存位數(shù),那我們的內(nèi)存寬高是如何計算的呢?我們可以追蹤framework的源碼,路徑為/frameworks/base/core/jni/android/graphics/BitmapFactory.cpp,找到doDecode方法:

static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream,
                        jobject padding, jobject options) {
    int sampleSize = 1;
    bool onlyDecodeSize = false;
    ... ...
    float scale = 1.0f;
    ... ...
    if (options != NULL) {
        sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
        if (sampleSize <= 0) {
            sampleSize = 1;
        }

        if (env->GetBooleanField(options, gOptions_justBoundsFieldID)) {
            onlyDecodeSize = true;
        }
        ... ...
        if (env->GetBooleanField(options, gOptions_scaledFieldID)) {
            const int density = env->GetIntField(options, gOptions_densityFieldID);
            const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID);
            const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID);
            if (density != 0 && targetDensity != 0 && density != screenDensity) {
                scale = (float) targetDensity / density;  //縮放比在這里計算的
            }
        }
    }
    ... ...

    if (scale != 1.0f) {
        willScale = true;
        scaledWidth = static_cast<int>(scaledWidth * scale + 0.5f); //內(nèi)存寬度計算
        scaledHeight = static_cast<int>(scaledHeight * scale + 0.5f); //內(nèi)存高度計算
     }

以上源碼總結(jié)為:
內(nèi)存寬高 = 圖片本身的寬高 * (設(shè)備密度/使用的密度)+ 0.5
所以,內(nèi)存的占用與圖片所在的密度目錄是有密切關(guān)系的

1.3、 drawable-xxhdpi與mipmap-xxhdpi的區(qū)別(了解即可)

從新版Android studio開始,系統(tǒng)會默認(rèn)給我們創(chuàng)建mipmap文件夾而不是drawable,先前我一直以為這個目錄只是放icon的(流下了菜逼的心酸淚水),后來發(fā)現(xiàn)這個目錄與drawable都可以放圖片,而且貌似也沒有任何差別????,為了弄清楚,上網(wǎng)查了一下,大意是會對圖片縮放做性能優(yōu)化,不過我在平常的使用中并沒有發(fā)現(xiàn)有太大的區(qū)別,可能是高版本drawable也做了優(yōu)化把,所以這個簡單了解一下即可。

(2)Bitmap優(yōu)化詳解

2.1、基礎(chǔ)知識

安卓的圖片加載都會對應(yīng)到bitmap對象,當(dāng)bitmap占用的內(nèi)存過高,超過了android為app分配的最大內(nèi)存,那么它就會不開心,它就會OOM,所以我們的圖片優(yōu)化,本質(zhì)就是內(nèi)存優(yōu)化。
我們常用的圖片格式一般有三種:

  • JPEG 一種有損壓縮格式,不支持透明通道,所以有透明背景需求的圖片不要用jpeg格式,它會變成黑色,會變黑?。。?,會變黑?。?!,會變黑?。?!
  • PNG 無損壓縮格式,支持透明通道
  • WEBP 同時支持無損和有損壓縮格式,然而兼容性較差,需要適配庫
    可參考:webp-android

在代碼中可以通過Bitmap.CompressFormat來指定生成的圖片格式
bitmap還可以配置像素占用字節(jié)數(shù)(一個字節(jié)對應(yīng)8位),這與內(nèi)存占用息息相關(guān),對應(yīng)Bitmap.Config類:

  • ALPHA_8 8位ALPHA通道,即A=8,一個像素占用一個字節(jié),它是沒有顏色的,只有透明度
  • ARGB_4444 16位 A=4,R=4,G=4,B=4,一個像素占用字節(jié) = 4+4+4+4 = 16位 = 2個字節(jié)
  • ARGB_8888 32位 A=8,R=8,G=8,B=8,一個像素占用字節(jié) = 8+8+8+8 = 32位 = 4個字節(jié)
  • RGB_565 16位 R=5,G=6,B=5,因為沒有A,所以它沒有透明度,計算同上也是占用2個字節(jié)
    像平常如果沒有透明度要求使用RGB_565即可

2.2、優(yōu)化的本質(zhì)

前面已經(jīng)提到我們的圖片優(yōu)化實際是內(nèi)存的優(yōu)化,而我們的內(nèi)存計算公式是:
內(nèi)存占用寬高相乘 * 每像素占用的內(nèi)存位數(shù),所以要么就減少圖片內(nèi)存占用寬高,要么就減少像素占用內(nèi)存數(shù)。減少像素內(nèi)存占用數(shù)可以通過配置Bitmap.Config解決,而減少圖片內(nèi)存占用寬高,可以有以下幾個方式:
1.縮小圖片實際寬高,縮小到正好正常展示
2.匹配合適的像素密度
此外還能主動回收占用的內(nèi)存從而緩解內(nèi)存緊張的問題。

2.3、優(yōu)化方式

分析了一下優(yōu)化的本質(zhì)問題,我們可以針對性的使用以下幾種方式來進行優(yōu)化:
1.配置BitmapConfig(前面已經(jīng)講到)
2.拿到一個圖片,通過inJustDecodeBounds不消耗內(nèi)存拿到圖片的寬高,然后使用inSampleSize來設(shè)置圖片的縮放比。
簡單示例代碼:

        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(getResources(),R.mipmap.icon_database_xls,options);
        Log.i("HJ","初始圖片寬:"+options.outWidth);
        Log.i("HJ","初始圖片高:"+options.outHeight);
        options.inSampleSize = 2;
        options.inJustDecodeBounds = false;
        BitmapFactory.decodeResource(getResources(),R.mipmap.icon_database_xls,options);
        Log.i("HJ","縮放后圖片寬:"+options.outWidth);
        Log.i("HJ","縮放后圖片高:"+options.outHeight);

運行后:

2019-01-08 17:07:50.935 25900-25900/jie.com.imageoptimize I/HJ: 初始圖片寬:64
2019-01-08 17:07:50.935 25900-25900/jie.com.imageoptimize I/HJ: 初始圖片高:64
2019-01-08 17:07:50.936 25900-25900/jie.com.imageoptimize I/HJ: 縮放后圖片寬:32
2019-01-08 17:07:50.937 25900-25900/jie.com.imageoptimize I/HJ: 縮放后圖片高:32

可以看到inSampleSize設(shè)為2,圖片的寬和高都變成了原來的一半,所以可得縮放公式為:
原始圖片寬度/inSampleSize = 縮放后的圖片寬度。高度同理,所以圖片的整體大小變?yōu)榱嗽瓉淼?/4。

3.壓縮圖片,除了外部壓縮,內(nèi)部也可以使用compress()來壓縮圖片。或者自定義壓縮算法,使用比較廣泛的有Luban算法

compress()示例代碼:

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.icon_database_xls);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
boolean compress = bitmap.compress(Bitmap.CompressFormat.PNG, 90, stream);

第一個參數(shù)是需要生成的圖片格式,第二個是壓縮百分比,第三個是生成的輸出流容器。請注意,此方法只會壓縮圖片大小,但是并不會減少內(nèi)存占用

4.主動釋放內(nèi)存,綁定控件生命周期,在銷毀的時候調(diào)用recycler()方法

if(bitmap != null && !bitmap.isRecycled()){  
    bitmap.recycle(); 
    bitmap = null; 
}
最后編輯于
?著作權(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)容