(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;
}