圖片做爲內存消耗大戶,一直是開發人員嘗試優化的重點對象。Bitmap的內存從3.0之前的位於native,到後來改爲jvm,再到8.0又改回到native。fresco花費不少精力在5.0系統以前把Bitmap內存改回到native,高版本上面則遵循系統實現,卻又被官方打臉。程序員
jvm每一個進程都有內存上限,而native則沒有限制(不是沒有影響,至少不會oom),因此把內存大戶Bitmap挪到native多是不少人的夢想,但native的管理和實現明顯比jvm更爲複雜,除非有現成實現,不多有人去動這一塊。行業裏面的大部分圖片庫都沒有涉及這塊,大部分的程序員也秉着夠用就好的態度用了不少年,這說明程序員也是會偷懶的。官方的策略修改到底緣由幾何,其實我也沒搜到相關說明,有知道的同窗歡迎留言。web
一個app裏面的圖片都會有尺寸,通常狀況下面圖片的尺寸就是view的大小,而view的大小在咱們使用dp單位後在不一樣的機器上面表現出來的實際像素都有差異,爲了節約流量開銷,加快返回速度,同時符合按需加載的原則,咱們應該只加載實際view尺寸大小的圖片。通常圖片存儲提供商都會提供在線壓縮服務,咱們只須要在請求連接裏面加上參數便可。這裏還有個問題咱們通常請求加載圖片的代碼都是寫在Activity的onCreate,或者Adapter的getView函數裏面,這個時候實際上是獲取不到view尺寸的(還未measure),這裏有幾種作法:面試
使用目測:好比一個列表是左右圖片佈局的,那就能夠請求屏幕一半寬度的尺寸圖片canvas
view使用了固定尺寸:這個沒有問題,咱們直接拿getLayoutParams()的width和height就能夠了緩存
view的maxWidth/maxHeight:view沒法固定尺寸,咱們能夠在xml裏面給view配置maxWidth/maxHeight來指導圖片庫加載什麼尺寸的圖片微信
加載圖片前先measure:不怎麼推薦,由於圖片加載出來後view還得measure一次網絡
通常作法是給圖片加載庫包裝一層,根據傳進來的url判斷是否已經指定大小(開發者固然能夠決定想加載多大圖片),若是還未指定則使用上面的策略進行動態調整,若是最後仍是沒能加上縮放參數,則有個兜底策略,不加載超過屏幕尺寸大小。app
作了上面按需加載後還有個問題,會發現有時候不一樣的頁面須要加載同一個圖片url,但在尺寸上面有細微差異,結果致使請求重複(通常圖片加載庫都是url做爲緩存key),有點弄巧成拙,反倒浪費了流量和時間。這種狀況咱們須要作些微調。對於A頁面圖片尺寸是200x200,對於B頁面圖片尺寸是180x180,咱們認爲可使用200x200的圖片縮放到180x180,這有兩種作法:第一種是讓開發者始終都去加載稍微大一點的圖,這個要求有點高,一個頁面開發的時候很難先後聯繫。第二種是修改圖片加載庫,自動完成這個事情。後者天然合理,修改圖片加載庫在決定使用緩存的那一步判斷是否有比本身大的緩存已經存在便可,固然這個策略能夠每一個產品本身調整,好比也能夠認爲已經存在的緩存尺寸小於必定值也是能夠接受也是能夠的。還有複雜的狀況好比緩存圖片高寬比和要增強的不同如何處理等等,策略均可以本身定,但必定有必要作這個事情。jvm
這裏還要補充一點,大型產品通常圖片域名會有好幾個,用來作鏈路擇優用的,必定要記得緩存的時候用來作key的url要去掉域名影響。ide
再補充一點,有些特殊的使用場景能夠考慮採用上面說的第一種方式來作,舉個例子好比一個操做必定會加載100x100的圖,而後也必定會等會加載500x500的同一張圖,這種場景下面按第二種方式來處理顯然會加載兩次,但若是開發者這2個位置寫死都加載500x500則明顯更好一些。因此方法是死的,人是活的,要看實際使用場景。
還有一些特殊場景,好比程序裏面有兩個進程,A進程會加載500x500的圖,B進程會加載無論什麼尺寸的同一張圖,默認狀況下面這2個請求會同時發出,這就極可能會形成重複請求,這種狀況下面須要作一點跨進程同步,或者簡單一點其中一個進程請求作一點延遲處理。
實在不得已要從服務端加載大圖或者原始尺寸下來,或者由於上面說的策略故意加載大圖下來,在decode的時候要進行採樣,這個是老生常談了,使用options.inJustDecodeBounds來獲取原始尺寸,而後按需使用options.inSampleSize來採樣圖片到接近view尺寸。
Bitmap在decode的時候可使用inPreferredConfig指定配置格式,常見的有:
參數取值含義ALPHA_8圖片中每一個像素用一個字節(8位)存儲,該字節存儲的是圖片8位的透明度值RGB_565圖片中每一個像素用兩個字節(16位)存儲,兩個字節中高5位表示紅色通道,中間6位表示綠色通道,低5位表示藍色通道ARGB_4444圖片中每一個像素用兩個字節(16位)存儲,Alpha,R,G,B四個通道每一個通道用4位表示ARGB_8888圖片中每一個像素用四個字節(32位)存儲,Alpha,R,G,B四個通道每一個通道用8位表示
對於質量細節要求比較高的圖片可使用ARGB_888,這也是fresco的默認配置。而對於JPG圖片可使RGB_565,從上面能夠看出內存佔用之間減小一半,很是有吸引力,而app裏面事實上大部分應該都是JPG。但每每在和視覺的PK當中開發每每敗下陣來,下降了圖片質量不行!!開發老是持之以恆,咱們能夠建議採用這樣的策略:對於尺寸小於必定尺寸的JPG(好比300),咱們使用565,而對於大圖爲了保留細節咱們仍然使用8888。仍是那句話策略是活的。
使用三級緩存機制,內存磁盤網絡,這也是官方推薦的方式。內存緩存旨在加快訪問速度,磁盤緩存避免反覆請求。關於這一點就不在贅述了,基本開源圖片庫都會這麼作
不少場景下面咱們須要顯示圖片的一部分,或者進行圖片效果疊加,好比作個倒影之類的。不少同窗上來就準備createBitmap,而後把疊加效果繪製到這個臨時Bitmap,或者從原始Bitmap裏面先剪一部分出來生成一個新的Bitmap,再設給ImageView。或者使用createScaledBitmap進行縮放。更不當心的同窗可能直接把這些操做代碼寫在UI線程,而後寫在子線程又比較麻煩,這邊推薦的是使用自定義繪製,canvas有個drawBitmap方法能夠把某個區域繪製到指定位置。疊加效果也能夠徹底使用自定義view來本身draw,這樣不會有臨時Bitmap生成,效率會更高。
若是自定義view有困難,咱們可使用Drawable,只要能拿到canvas,這兩種作法是同樣的。
這裏列舉一些實例,好讓你們能夠進一步理解:
一個按鈕有普通和按下狀態,按下是普通狀態上面疊加一個遮罩,不須要切兩張圖,按下狀態的Drawable可使用自定義Drawable的canvas先繪製普通狀態的圖,再在上面繪製一層顏色。或者按下狀態使用LayerDrawable,這個Drawable自動幫你作了這個事情
須要把Bitmap的[0,0,200,200]的區域顯示到ImageView上面,使用canvas.drawBitmap(bitmap, [0,0,200,200], [0,0,圖片寬,圖片高],paint)
繪製倒影,這個邏輯性比較強了,這裏就不具體展開,canvas的操做學習下,結合局部繪製其實很簡單
有個圖片,須要在左上角顯示一個角標,正常狀況下面須要在左上角擺一個view,若是使用Drawable自定義繪製,canvas畫一下就好,相似下面的示例代碼。
給你們一個自定義繪製的例子,隨心組合:
class WithLineDrawable extends DrawableWrapper { private MyConstantState mMyConstantState; private boolean mForTop; private Paint mLinePaint = new Paint(); public WithLineDrawable(Drawable drawable, boolean forTop) { super(drawable); mLinePaint.setColor(getLineColor()); mForTop = forTop; } @Override public void draw(Canvas canvas) { super.draw(canvas); if (mForTop) { canvas.drawLine(0, 0, getBounds().width(), 0, mLinePaint); } else { canvas.drawLine(0, getBounds().height(), getBounds().width(), getBounds().height(), mLinePaint); } } @Nullable @Override public ConstantState getConstantState() { if (mMyConstantState == null) { mMyConstantState = new MyConstantState(); } return mMyConstantState; } class MyConstantState extends ConstantState { @NonNull @Override public Drawable newDrawable() { return new WithLineDrawable(getWrappedDrawable().getConstantState().newDrawable(), mForTop); } @Override public int getChangingConfigurations() { return 0; } } }
必定要把觀念從Bitmap轉變到Drawable,當還在費勁心思Bitmap該如何處理的時候,想一想Drawable裏面如何使用canvas進行各類自定義繪製。
圖片格式發展到今天已經很是多樣了,目前不少開源庫都支持了webp來代替jpg和gif,webp在壓縮率上面有不少優點,雖然解碼上面略遜一籌,通過咱們測試仍是很不錯的。也是推薦你們使用,不管是網絡圖片下載仍是apk內置,用來代替jpg很合適,而代替png則還須要一些時間,主要是低版本系統對於透明webp還有些兼容問題。Android P上面支持了heif格式也是想代替jpg,不過這個格式目前還沒仔細研究過。
對於內置apk的圖標類,則推薦使用svg,再也不須要切幾套圖,並且很是小,官方使用的compat包裏面解碼svg會作緩存,也進一步提高性能。不過也正由於此儘可能不要一個圖片使用過多不一樣尺寸。大部分的圖標都使用代碼代替圖片後,apk大小能夠明顯減小,這也符合咱們的原則:能程序畫的就毫不切圖。
不少時候咱們須要給圖標換色,關於顏色混合有一套理論,官方很早就支持,使用ColorFilter,後來compat包裏面出了個tint,因此若是有顏色混合處理的相關邏輯,千萬不要去生成臨時Bitmap,使用相似以下代碼:
//1:經過圖片資源文件生成Drawable實例 Drawable drawable = getResources().getDrawable(R.mipmap.ic_launcher).mutate(); //2:先調用DrawableCompat的wrap方法 drawable = DrawableCompat.wrap(drawable); //3:再調用DrawableCompat的setTint方法,爲Drawable實例進行着色 DrawableCompat.setTint(drawable, Color.RED);
內置apk的圖片資源很是多,總有一些常規圖片仍然須要使用jpg或者png,咱們要想辦法進一步壓縮他們,這樣能夠有效控制apk大小,這裏推薦使用ImageOptim,這個工具集合了不少種壓縮方式,效果顯著。
後記:
不少面試的時候問如何作圖片加載優化,他們會回答recycle
bitmap,事實上這個操做要很謹慎,一不留神就會致使出問題。大部分的應用不太會幹這個事情,吃力不討好,交給jvm垃圾回收多好。圖片解碼還有一些參數能夠優化,好比inBitmap,這裏就不具體展開了。
更多文章請關注微信公衆號:安卓之美