Android圖片加載庫Glide簡介(上)

Android圖片加載庫Glide簡介(上)

image

官網:GlideMaven Central

GlideGoogle員工的開源項目,Google官方App中已經使用,在2015年的Google I/O上被推薦。

Glide的優點:

  • 使用簡單
  • 可配置度高,自適應程度高
  • 支持多種數據源,網絡、本地、資源、Assets
  • 支持Gif圖片。
  • 支持WebP
  • 加載速度快、流暢度高。
  • Glidewith()方法不光接受Context,還接受ActivityFragment,這樣圖片加載會和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提供了變換去處理圖片顯示,通過設置centerCropfitCenter,可以得到兩個不同的效果。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);    
  • 更變圖片轉換

上面介紹了兩種自帶的圖片轉換方式fitCentercenterCrop。 但有些時候我們需要自定義一些別的轉換方式,自定義轉換方式需要繼承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);

通常,Glidefluent 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像素的圖片,你的ImageView500x500像素,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 );
}

參考


更多文章請查看AndroidNote

你的star是我的動力!!!

發佈了134 篇原創文章 · 獲贊 23 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章