Android圖片加載庫Glide簡介(上)
官網:Glide
Glide
是Google
員工的開源項目,Google
官方App
中已經使用,在2015年的Google I/O
上被推薦。
Glide
的優點:
- 使用簡單
- 可配置度高,自適應程度高
- 支持多種數據源,網絡、本地、資源、
Assets
等 - 支持
Gif
圖片。 - 支持
WebP
。 - 加載速度快、流暢度高。
Glide
的with()
方法不光接受Context
,還接受Activity
和Fragment
,這樣圖片加載會和Activity/Fragment
的生命週期保持一致,比如Pause
狀態在暫停加載,在Resume
的時候又自動重新加載。- 支持設置圖片加載優先級。
- 支持縮略圖,可以在同一時間加載多張圖片到同一個
ImageView
中,例如可以首先加載只有ImageView
十分之一大小的縮略圖,然後等再加載完整大小的圖片後會再顯示到該ImageView
上。 - 內存佔用低,
Glide
默認的Bitmap
格式是RGB_565
,比ARGB_8888
格式的內存開銷要小一半,所以圖片質量會稍微差一些,當然這些配置都是可以修改的。 Glide
緩存的圖片大小是根據ImageView
尺寸來緩存的的。這種方式優點是加載顯示非常快。且可以設置緩存圖片的尺寸- 默認使用
HttpUrlConnection
下載圖片,可以配置爲OkHttp
或者Volley
下載,也可以自定義下載方式。 - 默認使用兩個線程池來分別執行讀取緩存和下載任務,且可以自定義。
- 默認使用手機內置存儲進行磁盤緩存,可以配置爲外部存儲,可以配置緩存大小,圖片池大小。
- 在加載同樣配置的圖片時,
Glide
內存佔用更少,因爲Glide
是針對每個ImageView
適配圖片大小後再存儲到磁盤的,這樣加載進內存的是壓縮過的圖片,內存佔用自然就比較少。這種做法有助於減少OutOfMemoryError
的出現。 - 高效處理
Bitmap
,使用Bitmap Pool
來對Bitmap
進行復用,主動調用recycle
回收需要回收的Bitmap
,減小系統回收壓力
缺點:
- 體積相對來說比較大,目前最新版的大小在
500k
左右 - 當我們從遠程
URL
地址下載圖片時,Picasso
相比Glide
要快很多。可能的原因是Picasso
下載完圖片後直接將整個圖片加載進內存,而Glide
還需要針對每個ImageView
的大小來適配壓縮下載到的圖片,這個過程需要耗費一定的時間。(當然我們可以使用thumbnail()
來減少壓縮的時間)
使用方法:
repositories {
mavenCentral()
maven { url 'https://maven.google.com' }
}
dependencies {
compile 'com.github.bumptech.glide:glide:4.1.1'
annotationProcessor 'com.github.bumptech.glide:compiler:4.1.1'
}
Java
代碼使用:
和Picasso
一樣,Glide
使用fluent interface
的模式,想要加載圖片,需要至少傳入三個參數:
with(Context context)
:Context
是很多android api
所必要的參數,glide
也一樣。可以傳遞Activity/Fragment
,而且
它會由Activity/Fragment
的生命週期進行綁定。load(String imageUrl)
:圖片的URL
地址。into(ImageView targetImageView)
:需要將加載的圖片顯示到的對應的ImageView
。
// For a simple view:
@Override public void onCreate(Bundle savedInstanceState) {
...
ImageView imageView = (ImageView) findViewById(R.id.my_image_view);
GlideApp.with(this).load("http://goo.gl/gEgYUd").into(imageView);
}
// For a simple image list:
@Override public View getView(int position, View recycled, ViewGroup container) {
final ImageView myImageView;
if (recycled == null) {
myImageView = (ImageView) inflater.inflate(R.layout.my_image_view, container, false);
} else {
myImageView = (ImageView) recycled;
}
String url = myUrls.get(position);
GlideApp
.with(myFragment)
.load(url)
.centerCrop()
.placeholder(R.drawable.loading_spinner)
.into(myImageView);
return myImageView;
}
Proguard
防混淆配置:
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.AppGlideModule
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
# for DexGuard only
-keepresourcexmlelements manifest/application/meta-data@value=GlideModule
接下來是一些常用的方法:
- 從網絡加載圖片
GlideApp.with(context).load(internetUrl).into(targetImageView);
- 從文件加載圖片
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),"Test.jpg");
GlideApp.with(context).load(file).into(imageViewFile);
- 加載
resource
資源
int resourceId = R.mipmap.ic_launcher;
GlideApp.with(context).load(resourceId).into(imageViewResource);
- 加載
URI
地址
GlideApp.with(context).load(uri).into(imageViewUri);
- 設置佔位圖
GlideApp
.with(context)
.load(UsageExampleListViewAdapter.eatFoodyImages[0])
.placeholder(R.mipmap.ic_launcher) // can also be a drawable
.into(imageViewPlaceholder);
- 設置出錯時的圖片
GlideApp
.with(context)
.load("http://futurestud.io/non_existing_image.png")
.placeholder(R.mipmap.ic_launcher) // can also be a drawable
.error(R.mipmap.future_studio_launcher) // will be displayed if the image cannot be loaded
.into(imageViewError);
fallback
佔位圖
上面的error()
展位圖是當資源無法獲取時使用的,例如圖片地址無效,但是還有另外一種情況是你傳遞了null
值。比如在一個顯示用戶資料列表中,由於並不是
每個人都有頭像圖片,所有這時可能會傳遞null
值。如果想要指定一個在傳遞null
值時顯示的錯誤圖片可以使用.fallback()
.
String nullString = null; // could be set to null dynamically
GlideApp
.with(context)
.load( nullString )
.fallback( R.drawable.floorplan )
.into( imageViewNoFade );
- 結合
ListView
使用
public static String[] eatFoodyImages = {
"http://i.imgur.com/rFLNqWI.jpg",
"http://i.imgur.com/C9pBVt7.jpg",
"http://i.imgur.com/rT5vXE1.jpg",
"http://i.imgur.com/aIy5R2k.jpg",
"http://i.imgur.com/MoJs9pT.jpg",
"http://i.imgur.com/S963yEM.jpg",
"http://i.imgur.com/rLR2cyc.jpg",
"http://i.imgur.com/SEPdUIx.jpg",
"http://i.imgur.com/aC9OjaM.jpg",
"http://i.imgur.com/76Jfv9b.jpg",
"http://i.imgur.com/fUX7EIB.jpg",
"http://i.imgur.com/syELajx.jpg",
"http://i.imgur.com/COzBnru.jpg",
"http://i.imgur.com/Z3QjilA.jpg",
};
public class UsageExampleAdapter extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_usage_example_adapter);
listView.setAdapter(
new ImageListAdapter(
UsageExampleAdapter.this,
eatFoodyImages
)
);
}
}
public class ImageListAdapter extends ArrayAdapter {
private Context context;
private LayoutInflater inflater;
private String[] imageUrls;
public ImageListAdapter(Context context, String[] imageUrls) {
super(context, R.layout.listview_item_image, imageUrls);
this.context = context;
this.imageUrls = imageUrls;
inflater = LayoutInflater.from(context);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (null == convertView) {
convertView = inflater.inflate(R.layout.listview_item_image, parent, false);
}
GlideApp
.with(context)
.load(imageUrls[position])
.into((ImageView) convertView);
return convertView;
}
}
- 圖片過度
Transitions
無論你是否使用佔位圖,在UI
過程中改變ImageView
的圖片都是一個很大的動作。有一個簡單的方法可以使這種改變變的更平滑,更容易讓人接受,那就是使用
crossfade
動畫。
GlideApp
.with(context)
.load(UsageExampleListViewAdapter.eatFoodyImages[0])
.placeholder(R.mipmap.ic_launcher) // can also be a drawable
.error(R.mipmap.future_studio_launcher) // will be displayed if the image cannot be loaded
.transition(DrawableTransitionOptions.withCrossFade())// withCrossFade(int duration)方法可以傳入時間,默認時間是300毫秒
.into(imageViewCombined);
- 自定義過度動畫
上面提供了crossfade
動畫,但是有些時候我們需要自定義更多的樣式。
Glide
也是支持xml
中自定義的動畫文件的。
GlideApp
.with(context)
.load(eatFoodyImages[0])
.transition(GenericTransitionOptions.with(R.anim.zoom_in))
.into(imageView1);
- 圖片大小調整
理想情況下,你的服務器或者API
能夠返回所需分辨率的圖片,這是在網絡帶寬、內存消耗和圖片質量下的完美方案。
跟Picasso
比起來,Glide
在內存上佔用更優化。Glide
在緩存和內存裏自動限制圖片的大小去適配ImageView
的尺寸。Picasso
也有同樣的能力,但需要調用fit()
方法。用Glide
時,如果圖片不需要自動適配ImageView
,調用override(horizontalSize, verticalSize)
,它會在將圖片顯示在ImageView
之前調整圖片的大小。
這個設置也有利於沒有明確目標,但已知尺寸的視圖上。例如,如果app
想要預先緩存在splash
屏幕上,還沒法測量出ImageVIews
具體寬高。但是如果你已經知道圖片應當爲多大,使用override
可以提供一個指定的大小的圖片。
GlideApp
.with(context)
.load(UsageExampleListViewAdapter.eatFoodyImages[0])
.override(600, 200) // resizes the image to these dimensions (in pixel). resize does not respect aspect ratio
.into(imageViewResize);
- 縮放圖片
對於任何圖像的任何處理,調整圖像的大小可能會扭曲長寬比,醜化圖片的顯示。在大多數情況下,你希望防止這種事情發生。Glide
提供了變換去處理圖片顯示,通過設置centerCrop
和fitCenter
,可以得到兩個不同的效果。CenterCrop()
會縮放圖片讓圖片充滿整個ImageView
的邊框,然後裁掉超出的部分。ImageVIew
會被完全填充滿,但是圖片可能不能完全顯示出。
fitCenter()
會縮放圖片讓兩邊都相等或小於ImageView
的所需求的邊框。圖片會被完整顯示,可能不能完全填充整個ImageView
。
GlideApp
.with(context)
.load(UsageExampleListViewAdapter.eatFoodyImages[0])
.override(600, 200) // resizes the image to these dimensions (in pixel)
.centerCrop() // this cropping technique scales the image so that it fills the requested bounds and then crops the extra.
.into(imageViewResizeCenterCrop);
GlideApp
.with(context)
.load(UsageExampleListViewAdapter.eatFoodyImages[0])
.override(600, 200)
.fitCenter()
.into(imageViewResizeFitCenter);
- 更變圖片轉換
上面介紹了兩種自帶的圖片轉換方式fitCenter
和centerCrop
。 但有些時候我們需要自定義一些別的轉換方式,自定義轉換方式需要繼承BitmapTransformation
類。
public class BlurTransformation extends BitmapTransformation {
public BlurTransformation(Context context) {
super( context );
}
@Override
protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
return null; // todo
}
@Override
public String getId() {
return null; // todo
}
}
接下來使用Renderscript
來實現圖片的模糊處理。
public class BlurTransformation extends BitmapTransformation {
private RenderScript rs;
public BlurTransformation(Context context) {
super( context );
rs = RenderScript.create( context );
}
@Override
protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
Bitmap blurredBitmap = toTransform.copy( Bitmap.Config.ARGB_8888, true );
// Allocate memory for Renderscript to work with
Allocation input = Allocation.createFromBitmap(
rs,
blurredBitmap,
Allocation.MipmapControl.MIPMAP_FULL,
Allocation.USAGE_SHARED
);
Allocation output = Allocation.createTyped(rs, input.getType());
// Load up an instance of the specific script that we want to use.
ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
script.setInput(input);
// Set the blur radius
script.setRadius(10);
// Start the ScriptIntrinisicBlur
script.forEach(output);
// Copy the output to the blurred bitmap
output.copyTo(blurredBitmap);
toTransform.recycle();
return blurredBitmap;
}
@Override
public String getId() {
// getId()方法爲這個變換描述了一個獨有的識別。Glide使用那個關鍵字作爲緩存系統的一部分。防止出現異常問題,確保其唯一。
return "blur";
}
}
傳遞你的類的實例作爲.transform()
的參數。不管是圖片還是gif
都可以進行變換。
GlideApp
.with(context)
.load(eatFoodyImages[0])
.transform(new BlurTransformation(context))
.into(imageView2);
通常,Glide
的fluent interface
允許方法被連接在一起,然而變換並不是這樣的。確保你只調用.transform()
一次,不然之前的設置將會被覆蓋!然而,你可以通過傳遞多個轉換對象當作參數到.transform()
中來進行多重變換:
GlideApp
.with(context)
.load(eatFoodyImages[0])
.transform(
new MultiTransformation(
new GrayscaleTransformation(context),
new BlurTransformation(context)))
.into(imageView3);
- 播放
gif
動畫
String gifUrl = "http://i.kinja-img.com/gawker-media/image/upload/s--B7tUiM5l--/gf2r69yorbdesguga10i.gif";
GlideApp
.with(context)
.load(gifUrl)
.into(imageViewGif);
gif
檢查
Glide
接受Gif
和圖片作爲load()
的參數。上面代碼中潛在的一個問題,如果提供的源不是Gif
,可能是一個普通的圖片。即使是一個完好的圖片(非Gif
),Glide
也會加載失敗。.error()
回調方法會被調用,並加載錯誤佔位圖。這樣引入了一個額外的方法.asGif()
強迫生成一個Gif
。
GlideApp
.with(context)
.asGif()
.load(gifUrl)
.error(R.drawable.full_cake)
.into(imageViewGifAsGif);
- 把
Gif
當作Bitmap
播放
如果你的app
需要顯示一組網絡URL
,可能包括普通的圖片或者Gif
。在一些情況下,你可能並不在意是否要播放完整的Gif
。如果你只是想要顯示Gif
的第一幀,當URl
指向的的確是Gif
,你可以調用asBitmap()
將其作爲常規圖片顯示。
GlideApp
.with(context)
.asBitmap()
.load(gifUrl)
.into(imageViewGifAsBitmap);
- 顯示本地視頻縮略圖
String filePath = "/storage/emulated/0/Pictures/example_video.mp4";
GlideApp
.with(context)
.asBitmap()
.load(Uri.fromFile(new File(filePath)))
.into(imageViewGifAsBitmap);
- 緩存設置
GlideApp
.with(context)
.load(gifUrl)
.asGif()
.error(R.drawable.full_cake)
.diskCacheStrategy(DiskCacheStrategy.DATA)
.into(imageViewGif);
- 內存緩存
默認情況下,我們不用去特意的操作緩存設置,因爲Glide
默認會使用內存和硬盤緩存,但是如果在知道某一個圖片會快速變化時,你可能會關閉緩存功能。
GlideApp
.with(context)
.load(eatFoodyImages[0])
.skipMemoryCache(true)
.into(imageView1);
上面調用了.skipMemoryCache(true)
方法來告訴Glide
禁用內存緩存功能。這就意味着Glide
不會將圖片緩存到內存中,但是這只是影響內存緩存,Glide
仍然會將圖片
緩存到硬盤中來避免下一次顯示該圖片時重複請求。
- 硬盤緩存
如上面所講到的,即使你關閉了內存緩存,所請求的圖片仍然會被保存在設備的磁盤存儲上。如果你有一張不段變化的圖片,但是都是用的同一個URL
,你可能需要禁止磁盤緩存了。
你可以用.diskCacheStrategy()
方法改變Glide
的行爲。不同於.skipMemoryCache()
方法,它將需要從枚舉型變量中選擇一個,而不是一個簡單的boolean
。如果你想要禁止請求的磁盤緩存,使用枚舉型變量DiskCacheStrategy.NONE
作爲參數。
GlideApp
.with(context)
.load(eatFoodyImages[1])
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(imageView2);
上面的這種方式只是會禁用硬盤緩存,Glide
還會使用內存緩存。如果想把內存緩存和硬盤緩存都禁用,需要把上面的兩個方法都設置
GlideApp
.with(context)
.load(eatFoodyImages[1])
.diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(true)
.into(imageView2);
- 自定義磁盤緩存行爲
如從上面提到的,Glide
爲硬盤緩存提供了多種設置方式。Glide
的磁盤緩存是相當複雜的。例如,Picasso
只緩存全尺寸圖片。Glide
,會緩存原始,全尺寸的圖片和額外的小版本圖片。例如,如果你請求一個1000x1000
像素的圖片,你的ImageView
是500x500
像素,Glide
會保存兩個版本的圖片到緩存裏。
DiskCacheStrategy.NONE
禁用硬盤緩存功能DiskCacheStrategy.DATA
只緩存原始的全尺寸圖. 例如上面例子中1000x1000
像素的圖片DiskCacheStrategy.RESOURCE
只緩存最終剪輯轉換後降低分辨的圖片。例如上面離職中500x500
像素的圖片DiskCacheStrategy.AUTOMATIC
基於資源只能選擇緩存策略(默認的行爲)DiskCacheStrategy.ALL
緩存所有分辨率對應的類型的圖片圖片請求優先級
Glide
支持使用.priority()
方法來設置圖片請求的優先級。
- Priority.LOW
- Priority.NORMAL
- Priority.HIGH
- Priority.IMMEDIATE
private void loadImageWithHighPriority() {
GlideApp
.with(context)
.load(UsageExampleListViewAdapter.eatFoodyImages[0])
.priority(Priority.HIGH)
.into(imageViewHero);
}
private void loadImagesWithLowPriority() {
GlideApp
.with(context)
.load(UsageExampleListViewAdapter.eatFoodyImages[1])
.priority(Priority.LOW)
.into(imageViewLowPrioLeft);
GlideApp
.with(context)
.load(UsageExampleListViewAdapter.eatFoodyImages[2])
.priority(Priority.LOW)
.into(imageViewLowPrioRight);
}
- 縮略圖
縮略圖不同於前面文章中提到的佔位圖。佔位圖應當是跟app
綁定在一起的資源。縮略圖是一個動態的佔位圖,可以從網絡加載。縮略圖也會被先加載,直到實際圖片請求加載完畢。如果因爲某些原因,縮略圖獲得的時間晚於原始圖片,它並不會替代原始圖片,而是簡單地被忽略掉。
Glide
提供了兩種產生縮略圖的方式。第一種,是通過在加載的時候指定一個小的分辨率,產生一個縮略圖。這個方法在ListView
和詳細視圖的組合中非常有用。如果你已經在ListView
中用到了250x250
像素的圖片,那麼在在詳細視圖中會需要一個更大分辨率的圖片。然而從用戶的角度,我們已經看見了一個小版本的圖片,爲什麼需要好幾秒,同樣的圖片(高分辨率的)才能被再次加載出來呢?
在這種情況下,從顯示250x250
像素版本的圖片平滑過渡到詳細視圖裏查看大圖更有意義。Glide
裏的.thumbnail()
方法讓這個變爲可能。這裏.thumbnal()
的參數是一個浮點乘法運算。
String internetUrl = "http://i.imgur.com/DvpvklR.png";
GlideApp
.with(context)
.load(internetUrl)
.thumbnail(0.1f)
.into(imageView);
例如,如果你傳遞一個0.1f
作爲參數,Glide
會加載原始圖片大小的10%
的圖片。如果原始圖片有1000x1000
像素,縮略圖的分辨率爲100x100
像素。
爲.thumbnail()
傳入一個浮點類型的參數,非常簡單有效,但並不是總是有意義。如果縮略圖的生成也需要從網絡加載同樣全分辨率圖片後纔可以,那這樣加載速度並不會比不用縮略圖快。
因此Glide
提供了另一個方法去加載和顯示縮略圖。第二種方式需要傳遞一個新的Glide
請求作爲參數。
String internetUrl = "http://i.imgur.com/DvpvklR.png";
// setup Glide request without the into() method
RequestBuilder<Drawable> thumbnailRequest = GlideApp
.with(context)
.load(internetUrl);
// pass the request as a a parameter to the thumbnail request
GlideApp
.with(context)
.load(UsageExampleGifAndVideos.gifUrl)
.thumbnail(thumbnailRequest)
.into(imageView);
區別在於第一個縮略圖請求是完全獨立於第二個原始請求的。縮略圖可以來自不同資源或者圖片URL,你可以在它上面應用不同的變換。
- 旋轉圖片
android.graphics.Matrix
提供了旋轉的方法:
Bitmap toTransform = ... // your bitmap source
Matrix matrix = new Matrix();
matrix.postRotate(rotateRotationAngle);
Bitmap.createBitmap(toTransform, 0, 0, toTransform.getWidth(), toTransform.getHeight(), matrix, true);
但是如果要在Glide
中使用旋轉方法,需要用BitmapTransformation()
進行包裹:
public class RotateTransformation extends BitmapTransformation {
private float rotateRotationAngle = 0f;
public RotateTransformation(Context context, float rotateRotationAngle) {
super( context );
this.rotateRotationAngle = rotateRotationAngle;
}
@Override
protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
Matrix matrix = new Matrix();
matrix.postRotate(rotateRotationAngle);
return Bitmap.createBitmap(toTransform, 0, 0, toTransform.getWidth(), toTransform.getHeight(), matrix, true);
}
@Override
public String getId() {
return "rotate" + rotateRotationAngle;
}
}
然後將上面的方法傳遞到.transform()
中:
private void loadImageOriginal() {
Glide
.with( context )
.load( eatFoodyImages[0] )
.into( imageView1 );
}
private void loadImageRotated() {
Glide
.with( context )
.load( eatFoodyImages[0] )
.transform( new RotateTransformation( context, 90f ))
.into( imageView3 );
}
參考
- Glide — Getting Started
- Glide — Advanced Loading
- Glide — ListAdapter (ListView, GridView)
- Glide — Placeholders & Fade Animations
- Glide — Image Resizing & Scaling
- Glide — Displaying Gifs & Video Thumbnails
- Glide — Caching Basics
- Glide — Request Priorities
- Glide — Thumbnails
- Glide — Callbacks: SimpleTarget and ViewTarget for Custom View Classes
- Glide — Exceptions: Debugging and Error Handling
- Glide — Custom Transformations
- Glide — Custom Animations with animate()
- Glide — Integrating Networking Stacks
- Glide — Customize Glide with Modules
- How to Rotate Images
- Glide Module Example: Customize Caching
- Glide Module Example: Optimizing By Loading Images In Custom Sizes
你的star
是我的動力!!!