Glide 調試、硬件位圖、從 v3 遷移到 v4

Glide 調試

Glide 硬件位圖

Glide 從 v3 遷移到 v4

大致目錄:
//調試
* 本地日志 (Local Logs)
  * 請求錯誤
  * 預料之外的緩存丟失
  * 圖片和本地日志丟失
    * 啟動請求失敗(Failing to start the request)
    * 未指定尺寸(Missing Size)
      * 自定義Target(Custom Targets)
      * Views
  * 請求監(jiān)聽器與定制日志

* Out of memory 錯誤
    * 過大的內(nèi)存分配
    * 內(nèi)存泄露

* 其他常見問題
  * “You can’t start or clear loads in RequestListener or Target callbacks”
//硬件位圖
* 什么是硬件位圖(Hardware Bitmaps)?

* 我們?yōu)槭裁磻撌褂糜布粓D?

* 如何啟用硬件位圖?

* 如何禁用硬件位圖?

* 哪些情況不能使用硬件位圖?

* 使用硬件位圖有什么缺點?
//從 v3 遷移到 v4
* 選項(Options)
  * RequestBuilder
  * 請求選項
  * 變換
  * 解碼格式
  * 過渡選項
    * 交叉淡入 (Cross fade)
  * Generated API

* 類型 (Type) 與目標 (Target)
  * 選擇資源類型
  * Drawables
  * Targets
    * 取消請求

* 配置
  * 應用程序
  * 程序庫
  * 清單解析
  * using(), ModelLoader, StreamModelLoader.
    * ModelLoader
    * using()

一、調試

1.1 本地日志 (Local Logs)

如果你擁有設備的訪問權限,你可以使用 adb logcat 或你的 IDE 查看一些日志。你可以使用 adb shell setprop log.tag.<tag_name> <VERBOSE|DEBUG> 操作為任何下面提到的標簽 (tag) 開啟日志。VERBOSE 級別的日志會顯得更加冗余但包含更多有用的信息。根據(jù)你要查看的標簽的不同,你可以把 VERBOSEDEBUG 級別的信息都嘗試一下,以決定哪個級別的信息是你最需要的。

1.1.1 請求錯誤

最高級別和最容易理解的日志都通過 Glide 標簽打印。Glide 標簽將記錄成功和失敗的請求以及不同級別的詳細信息,具體取決于日志級別。VERBOSE 會被用于記錄成功的請求,DEBUG 則會打印出詳細的錯誤信息。

你也可以通過手動調用 setLogLevel(int) 方法控制 Glide 標簽的冗余度。setLogLevel 允許你在開發(fā)構建 (developer builds) 時啟用更加冗余的日志,而在發(fā)布 (release builds) 構建時則關閉它們。

1.1.2 預料之外的緩存丟失

Engine 標簽會詳細記錄請求被填充的全過程,并包括用于存儲相應資源的完整內(nèi)存緩存鍵。如果你正在嘗試調試“內(nèi)存中明明有這個圖片,為什么沒在另一個地方用到”的問題,那么 Engine 標簽可以讓你直觀地比較兩者的緩存鍵的區(qū)別。

對于每一個開始了的請求,Engine 標簽將會記錄這個請求將會從哪個地方加載完成:緩存,活動資源,已存在的加載過程,或者一個新的加載過程。

  • 緩存:意味著這個資源暫時沒有被用到,但是在內(nèi)存緩存中可用。

  • 活動資源:表示這個資源正在被另一個 Target 使用,一般是在一個 View 中。

  • 已存在的加載過程:表示這個資源雖然現(xiàn)在在內(nèi)存中不可用,但是另一個 Target 已經(jīng)在早先發(fā)起了對同一個資源的請求,并且這個請求還在處理中。

  • 新的加載過程:表示這個資源既不在內(nèi)存中,也沒有被其他地方請求過,那么這將觸發(fā)一次新的加載。

1.1.3 圖片和本地日志丟失

在某些情況下,你可能會發(fā)現(xiàn)某個圖片永遠不會加載出來,而且這個請求甚至還沒有 Glide 標簽和 Engine 標簽的日志。這可能有以下一些原因。

  • 啟動請求失敗 (Failing to start the request)
    請檢查你是否為你的請求調用了 into() 或者 submit() 方法。很顯然,如果你忘記了調用這兩個方法,Glide 不會認為你已經(jīng)要求開始加載。

  • 未指定尺寸 (Missing Size)
    如果你確信你調用了 into() 、submit() 之一,并且仍然沒有看到日志,那么最可能的解釋是,Glide 無法決定你即將加載資源的 View 或 Target 的尺寸。

  • 自定義 Target (Custom Targets)
    如果你正在使用一個自定義的 Target ,請確保你實現(xiàn)了 getSize 方法并使用了非零的寬高來調用指定的回調方法,或者繼承自一個已經(jīng)為你實現(xiàn)了這個方法的 Target ,例如 ViewTarget。

  • Views
    如果你只是在往一個 View 中加載資源,那么最大的可能是你的這個 view 要么還沒有被布局 (layout) 過,要么被指定了零寬或高。View 的可見性被設置為 View.GONE 或它并沒有被 attach,都會導致 view 不會被 layout 。如果 View 和/或它們的父控件被以特定方式組合 wrap_contentmatch_parent 來作為寬高,則 view 可能會收到一個無效的或為 0 的寬高值。你可以試驗一下,將你的 view 設定為非 0 的尺寸,或在請求時使用 override(int, int) API 來為 Glide 傳入一個特定的尺寸。

1.1.4 請求監(jiān)聽器與定制日志

如果你想使用編程的辦法跟蹤成功和失敗信息、跟蹤應用中的整體緩存命中率,或增加對本地日志的控制,你可以使用 RequestListener 接口。RequestListener 可以通過 RequestBuilder#listener() 方法來添加到單獨的加載請求中。下面是一個使用示例:

Glide.with(fragment)
   .load(url)
   .listener(new RequestListener() {
       @Override
       boolean onLoadFailed(@Nullable GlideException e, Object model,
           Target<R> target, boolean isFirstResource) {
         // Log the GlideException here (locally or with a remote logging framework):
         Log.e(TAG, "Load failed", e);

         // You can also log the individual causes:
         for (Throwable t : e.getRootCauses()) {
           Log.e(TAG, "Caused by", t);
         }
         // Or, to log all root causes locally, you can use the built in helper method:
         e.logRootCauses(TAG);

         return false; // Allow calling onLoadFailed on the Target.
       }

       @Override
       boolean onResourceReady(R resource, Object model, Target<R> target,
           DataSource dataSource, boolean isFirstResource) {
         // Log successes here or use DataSource to keep track of cache hits and misses.

         return false; // Allow calling onResourceReady on the Target.
       }
    })
    .into(imageView);

請注意,每個 GlideException 都有多個 Throwable root cause。在 Glide 中可能有任意多的方法使得注冊組件 (ModelLoader, ResourceDecoder, Encoder 等) 作用于從給定的模型 (URL, File 等)加載給定的資源 (Bitmap, GifDrawable 等)。每個 Throwable root cause 描述了一個特定的 Glide 組件組合為什么失敗。理解某個特定請求為何失敗可能需要檢查所有的 root cause

然而,你也可能會發(fā)現(xiàn)某個單一的 root cause 比其他的要重要一些。例如你正在加載 URL 并試圖找出特定的 HttpException (它意味著你的加載是由于一個網(wǎng)絡錯誤而失敗),你可以遍歷所有的 root cause 并使用 instanceof 來檢查其類型:

for (Throwable t : e.getRootCauses()) {
  if (t instanceof HttpException) {
    Log.e(TAG, "Request failed due to HttpException!", t);
    break;
  }
}

當然你也可以使用類似的迭代過程和 instanceof 操作符來檢查 Http 錯誤之外其他你關心的異常類型。

為減少對象分配起見,你可以為多個加載重用相同的 RequestListener

1.2 Out of memory 錯誤

幾乎所有的 OOM 錯誤都是因為宿主應用出了問題,而不是 Glide 本身。 應用里兩種常見的 OOM 錯誤分別是:

  1. 過大的內(nèi)存分配 (Excessively large allocations)

  2. 內(nèi)存泄露 (Memory leaks),被分配的內(nèi)存沒有被釋放

1.2.1 過大的內(nèi)存分配

如果在打開一個單獨頁面或加載一個單獨圖片導致了 OOM,那么你的應用可能在加載一個不必要的大圖。

使用 Bitmap 顯示一張圖片所需的內(nèi)存數(shù)量為寬 (width) * 高 (height) * 每像素字節(jié)數(shù) (bytes per pixel)。 每像素字節(jié)數(shù)取決于顯示圖片所使用的 Bitmap.Config,但通常對于 ARGB_8888 的位圖來說,每個像素即為四個字節(jié)。因此,即使是一張普通的 1080P 圖片也需要 8MB 內(nèi)存。圖片越大,所需要的內(nèi)存就越多,因此一個 12M 像素的圖片會要求相當龐大的 48MB 內(nèi)存。

Glide 會將圖片自動下采樣 (downsample),這是基于 Target,ImageView 或 override() 提供的尺寸。如果你在 Glide 中看到了特別大的內(nèi)存分配,通常意味著你的 Target 或 override() 提供的尺寸太大,或你使用了 Target.SIZE_ORIGINAL 而又恰好碰上了一個大圖。

要解決這種過大的內(nèi)存分配,請避免使用 Target.SIZE_ORIGINAL 并確保你的 ImageView 尺寸或你通過 override() 方法提供給 Glide 的尺寸是合理的。

1.2.2 內(nèi)存泄露

如果在你的應用中持續(xù)重復特定步驟會逐步增加你應用的內(nèi)存使用并最終導致 OOM ,你可能有內(nèi)存泄露。

Android 官方文檔中有很多關于追蹤和調試內(nèi)存使用的有用信息。為了調查內(nèi)存泄露,你幾乎肯定需要捕捉一個 heap dump 并查看 Fragments, Activities 和以及其他不再被使用但卻仍被持有的對象。

要修復內(nèi)存泄露,你需要對已銷毀的 Fragment 或 Activity 在生命周期的合適時機移除對它們的引用,以避免持有過多的對象。使用 heap dump 來幫助查找你應用中持有其他內(nèi)存的方式并在找到后移除不必要的引用。通常你可以從列出對 Bitmap 對象使用 (MAT 或其他內(nèi)存分析器) 的最短路徑(不含弱引用)開始,然后尋找可疑的引用鏈。你還可以在你的內(nèi)存分析器中搜索 Activity 和 Fragment,以確保每個 Activity 不超過一個實例,并且 Fragment 的實例數(shù)目也在期望范圍內(nèi)。

1.3 其他常見問題

You can’t start or clear loads in RequestListener or Target callbacks

如果你嘗試在一個 TargetRequestListener 里的 onResourceReadyonLoadFailed 中開始一次新的加載,Glide 將會拋出一個異常。之所以拋出這個異常,是因為要處理和回收這種在通知過程中的 (notifying) 加載對 Glide 來說是一個巨大的挑戰(zhàn)。

好在這個問題很好解決。從 Glide 4.3.0 開始,你可以很輕松地使用 .error() 方法。這個方法接受一個任意的 RequestBuilder,它會且只會在主請求失敗時開始一個新的請求:

Glide.with(fragment)
  .load(url)
  .error(Glide.with(fragment)
     .load(fallbackUrl))
  .into(imageView);

對于 Glide 4.3.0 以前的版本,你也可以使用一個 Android Handlerpost 一個 Runnable 給你的請求:

private final Handler handler = new Handler();
...

Glide.with(fragment)
  .load(url)
  .listener(new RequestListener<Drawable>() {
      ...

      @Override
      public boolean onLoadFailed(@Nullable GlideException e, Object model, 
          Target<Drawable> target, boolean isFirstResource) {
        handler.post(new Runnable() {
            @Override
            public void run() {
              Glide.with(fragment)
                .load(fallbackUrl)
                .into(imageView);
            }
        });
      }
  )
  .into(imageView);

二、硬件位圖

2.1 什么是硬件位圖(Hardware Bitmaps)?

Bitmap.Config.HARDWARE 是一種 Android O 添加的新的位圖格式。硬件位圖僅在顯存 (graphic memory) 里存儲像素數(shù)據(jù),并對圖片僅在屏幕上繪制的場景做了優(yōu)化。

2.2 我們?yōu)槭裁磻撌褂糜布粓D?

因為硬件位圖僅儲存像素數(shù)據(jù)的一份副本。一般情況下,應用內(nèi)存中有一份像素數(shù)據(jù)(即像素字節(jié)數(shù)組),而在顯存中還有一份副本(在像素被上傳到 GPU 之后)。而硬件位圖僅持有 GPU 中的副本,因此:

  • 硬件位圖僅需要一半于其他位圖配置的內(nèi)存;

  • 硬件位圖可避免繪制時上傳紋理導致的內(nèi)存抖動。

2.3 如何啟用硬件位圖?

目前,你可以在 Glide 請求中將默認的 DecodeFormat 設置為 DecodeFormat.PREFER_ARGB_8888。要為應用中的所有請求都應用該操作,你需要在你的 GlideModule 中修改默認選項的 DecodeFormat。

未來 Glide 將默認加載硬件位圖而不需要額外的啟用配置,只保留禁用的選項。

2.4 如何禁用硬件位圖?

如果你需要禁用硬件位圖,你應當僅在以下的一些緩慢的或根本不可用 (broken) 的情況下才嘗試去做。你可以使用 disallowHardwareConfig() 來為一個特定的請求禁用硬件位圖。

如果你在使用 generated API:

GlideApp.with(fragment)
  .load(url)
  .disallowHardwareConfig()
  .into(imageView);

或直接使用 RequestOptions

RequestOptions options = new RequestOptions().disallowHardwareConfig();
Glide.with(fragment)
  .load(url)
  .apply(options)
  .into(imageView);

2.5 哪些情況不能使用硬件位圖?

在顯存中存儲像素數(shù)據(jù)意味著這些數(shù)據(jù)不容易訪問到,在某些情況下可能會發(fā)生異常。已知的情形列舉如下:

Canvas canvas = new Canvas(normalBitmap)
canvas.drawBitmap(hardwareBitmap, 0, 0, new Paint());
  • 在繪制位圖的 View 上使用軟件層 (software layer type)(例如,繪制陰影)
ImageView imageView = …
imageView.setImageBitmap(hardwareBitmap);
imageView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
  • 打開過多的文件描述符。每個硬件位圖會消耗一個文件描述符。這里存在一個每個進程的文件描述符限制 ( Android O 及更早版本一般為 1024,在某些 O - MR1 和更高的構建上是 32K)。Glide 將嘗試限制分配的硬件位圖以保持在這個限制以內(nèi),但如果你已經(jīng)分配了大量的文件描述符,這可能是一個問題。

  • 需要 ARGB_8888 Bitmaps 作為前置條件

  • 在代碼中觸發(fā)截屏操作,它會嘗試使用 Canvas 來繪制視圖層級。
    作為一個替代方案,在 Android O 以上版本你可以使用 PixelCopy。

  • 共享元素過渡 (shared element transition)

以下是一個示例 trace:

java.lang.IllegalStateException: Software rendering doesn't support hardware bitmaps
  at android.graphics.BaseCanvas.throwIfHwBitmapInSwMode(BaseCanvas.java:532)
  at android.graphics.BaseCanvas.throwIfCannotDraw(BaseCanvas.java:62)
  at android.graphics.BaseCanvas.drawBitmap(BaseCanvas.java:120)
  at android.graphics.Canvas.drawBitmap(Canvas.java:1434)
  at android.graphics.drawable.BitmapDrawable.draw(BitmapDrawable.java:529)
  at android.widget.ImageView.onDraw(ImageView.java:1367)
[snip]
  at android.view.View.draw(View.java:19089)
  at android.transition.TransitionUtils.createViewBitmap(TransitionUtils.java:168)
  at android.transition.TransitionUtils.copyViewImage(TransitionUtils.java:102)
  at android.transition.Visibility.onDisappear(Visibility.java:380)
  at android.transition.Visibility.createAnimator(Visibility.java:249)
  at android.transition.Transition.createAnimators(Transition.java:732)
  at android.transition.TransitionSet.createAnimators(TransitionSet.java:396)
[snip]

2.6 使用硬件位圖有什么缺點?

在某些情況下為了避免打斷用戶,Bitmap 類將執(zhí)行一次昂貴的顯存復制。在某些使用這些方法的情況下,你應該根據(jù)使用這些緩慢方法的使用頻率來考慮避免使用硬件位圖配置。如果你確實要使用這些方法,系統(tǒng)將會打印一條信息: “Warning attempt to read pixels from hardware bitmap, which is very slow operation”,并觸發(fā)一次 StrictMode#noteSlowCall

三、從 v3 遷移到 v4

3.1 選項 (Options)

Glide v4 中的一個比較大的改動是 Glide 庫處理選項 (centerCrop(), placeholder() 等) 的方式。在 v3 版本中,選項由一系列復雜的異構建造者 (multityped builders) 單獨處理。在新版本中,由一個單一類型的唯一一個建造者接管一系列選項對象。Glide 的 generated API 進一步簡化了這個操作:它會合并傳入建造者的選項對象和任何已包含的集成庫里的選項,以生成一個流暢的 API。

3.1.1 RequestBuilder

對于這類方法:

listener()
thumbnail()
load()
into()

在 Glide v4 版本中,只存在一個 RequestBuilder 對應一個你正在試圖加載的類型 (Bitmap, Drawable, GifDrawable 等)。 RequestBuilder 可以直接訪問對這個加載過程有影響的選項,包括你想加載的數(shù)據(jù)模型(url, uri 等),可能存在的縮略圖請求,以及任何的監(jiān)聽器。RequestBuilder 也是你使用 into() 或者 preload() 方法開始加載的地方:

RequestBuilder<Drawable> requestBuilder = Glide.with(fragment)
    .load(url);

requestBuilder
    .thumbnail(Glide.with(fragment)
        .load(thumbnailUrl))
    .listener(requestListener)
    .load(url)
    .into(imageView);

3.1.2 RequestOptions 請求選項

對于這類方法:

centerCrop()
placeholder()
error()
priority()
diskCacheStrategy()

大部分選項被移動到了一個單獨的稱為 RequestOptions 的對象中,

RequestOptions options = new RequestOptions()
    .centerCrop()
    .placeholder(R.drawable.placeholder)
    .error(R.drawable.error)
    .priority(Priority.HIGH);

RequestOptions 允許你一次指定一系列的選項,然后對多個加載重用它們:

RequestOptions myOptions = new RequestOptions()
    .fitCenter()
    .override(100, 100);

Glide.with(fragment)
    .load(url)
    .apply(myOptions)
    .into(drawableView);

Glide.with(fragment)
    .asBitmap()
    .apply(myOptions)
    .load(url)
    .into(bitmapView);

3.1.3 變換

Glide v4 里的 Transformations 現(xiàn)在會替換之前設置的任何變換。在 Glide v4 中,如果你想應用超過一個的 Transformation,你需要使用 transforms() 方法:

Glide.with(fragment)
  .load(url)
  .apply(new RequestOptions().transforms(new CenterCrop(), new RoundedCorners(20)))
  .into(target);

或使用 generated API:

GlideApp.with(fragment)
  .load(url)
  .transforms(new CenterCrop(), new RoundedCorners(20))
  .into(target);

3.1.4 解碼格式

在 Glide v3,默認的 DecodeFormatDecodeFormat.PREFER_RGB_565,它將使用 Bitmap.Config.RGB_565,除非圖片包含或可能包含透明像素。對于給定的圖片尺寸,RGB_565 只使用 Bitmap.Config.ARGB_8888 一半的內(nèi)存,但對于特定的圖片有明顯的畫質問題,包括條紋 (banding) 和著色 (tinting)。為了避免 RGB_565 的畫質問題,Glide 現(xiàn)在默認使用 ARGB_8888。結果是,圖片質量變高了,但內(nèi)存使用也增加了。

要將 Glide v4 默認的 DecodeFormat 改回 DecodeFormat.PREFER_RGB_565,請在 AppGlideModule 中應用一個RequestOption

@GlideModule
public final class YourAppGlideModule extends GlideModule {
  @Override
  public void applyOptions(Context context, GlideBuilder builder) {
    builder.setDefaultRequestOptions(new RequestOptions().format(DecodeFormat.PREFER_RGB_565));
  }
}

3.1.5 過渡選項

對于這類方法:

crossFade()
animate()

控制從占位符到圖片和/或縮略圖到全圖的交叉淡入和其他類型變換的選項,被移動到了 TransitionOptions 中。

要應用過渡(之前的動畫),請使用下列選項中符合你請求的資源類型的一個:

  • GenericTransitionOptions

  • DrawableTransitionOptions

  • BitmapTransitionOptions

如果你想移除任何默認的過渡,可以使用 TransitionOptions.dontTransition()。

過渡動畫通過 RequestBuilder 應用到請求上:

Glide.with(fragment)
    .load(url)
    .transition(withCrossFade(R.anim.fade_in, 300));
交叉淡入 (Cross fade)

不同于 Glide v3,Glide v4 將不會默認應用交叉淡入或任何其他的過渡效果。每個請求必須手動應用過渡。

要為一個特定的加載應用一個交叉淡入變換效果,你可以使用:

import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;

Glide.with(fragment)
  .load(url)
  .transition(withCrossFade())
  .into(imageView);

或:

Glide.with(fragment)
  .load(url)
  .transition(
      new DrawableTransitionOptions
        .crossFade())
  .into(imageView);

3.1.6 Generated API

為了讓使用 Glide v4 更簡單輕松,Glide 現(xiàn)在也提供了一套可以為應用定制化生成的 API。應用可以通過包含一個標記了 AppGlideModule 的實現(xiàn)來訪問生成的 API。

Generated API 添加了一個 GlideApp 類,該類提供了對 RequestBuilderRequestOptions 子類的訪問。RequestOptions 的子類包含了所有 RequestOptions 中的方法,以及 GlideExtensions 中定義的方法。RequestBuilder 的子類則提供了生成的 RequestOptions 中所有方法的訪問,而不需要你再手動調用 apply。舉個例子:

在沒有使用 Generated API 時,請求大概長這樣:

Glide.with(fragment)
    .load(url)
    .apply(centerCropTransform()
        .placeholder(R.drawable.placeholder)
        .error(R.drawable.error)
        .priority(Priority.HIGH))
    .into(imageView);

使用 Generated API,RequestOptions 的調用可以被內(nèi)聯(lián):

GlideApp.with(fragment)
    .load(url)
    .centerCrop()
    .placeholder(R.drawable.placeholder)
    .error(R.drawable.error)
    .priority(Priority.HIGH)
    .into(imageView);

你仍然可以使用生成的 RequestOptions 子類來應用相同的選項到多次加載中;但生成的 RequestBuilder 子類可能在多數(shù)情況下更為方便。

3.2 類型 (Type) 與目標 (Target)

3.2.1 選擇資源類型

Glide 允許你指定你想加載的資源類型。如果你指定了一個超類型,Glide 會嘗試加載任何可用的子類型。比如,如果你請求的是 Drawable,Glide 可能會加載一個 BitmapDrawable 或一個 GifDrawable。而如果你請求的是一個 GifDrawable,要么會加載出一個 GifDrawable,要么報錯,只要圖片不是 GIF 的話(即使它湊巧是一個完全有效的圖片也是如此)。

默認請求的類型是 Drawable:

Glide.with(fragment).load(url)

如果要明確指定請求 Bitmap:

Glide.with(fragment).asBitmap()

如果要創(chuàng)建一個文件路徑(本地圖片的最佳選項):

Glide.with(fragment).asFile()

如果要下載一個遠程文件到緩存然后創(chuàng)建文件路徑:

Glide.with(fragment).downloadOnly()
// or if you have the url already:
Glide.with(fragment).download(url);

3.2.2 Drawables

Glide v3 版本中的 GlideDrawable 類已經(jīng)被移除,支持標準的 Android Drawable。GlideBitmapDrawable 也已經(jīng)被刪除,由 BitmapDrawable 代替之。

如果你想知道某個 Drawable 是否是動畫 (animated),可以檢查它是否為 Animatable 的實例。

boolean isAnimated = drawable instanceof Animatable;

3.2.3 Targets

onResourceReady 方法的簽名做了一些修改。例如,對于 Drawables:

onResourceReady(GlideDrawable drawable, GlideAnimation<? super GlideDrawable> anim)

現(xiàn)在改為:

onResourceReady(Drawable drawable, Transition<? super Drawable> transition);

類似地,onLoadFailed 的簽名也有一些變動:

onLoadFailed(Exception e, Drawable errorDrawable)

改為:

onLoadFailed(Drawable errorDrawable)

如果你想要獲得更多導致加載失敗的錯誤信息,你可以使用 RequestListener

取消請求

Glide.clear(Target) 方法被移動到了 RequestManager 中:

Glide.with(fragment).clear(target)

使用 RequestManager 清除之前由它啟動的加載過程,通常能提高性能,雖然這并不是強制要求的。Glide v4 會為每一個 Activity 和 Fragment 跟蹤請求,所以你需要在合適的層級去清除請求。

3.3 配置

在 Glide v3 中,配置使用一個或多個 GlideModule 來完成。而在 Glide v4 中,配置改為使用一個類似但稍微復雜的系統(tǒng)來完成。

3.3.1 應用程序

在早期版本中使用了一個 GlideModule 的應用,可以將它轉換為一個 AppGlideModule。

在 Glide v3 中,你可能會有一個像這樣的 GlideModule:

public class GiphyGlideModule implements GlideModule {
  @Override
  public void applyOptions(Context context, GlideBuilder builder) {
    builder.setMemoryCache(new LruResourceCache(10 * 1024 * 1024));
  }

  @Override
  public void registerComponents(Context context, Registry registry) {
    registry.append(Api.GifResult.class, InputStream.class, new GiphyModelLoader.Factory());
  }
}

在 Glide v4 中,你需要將其轉換成一個 AppGlideModule ,它看起來像這樣:

@GlideModule
public class GiphyGlideModule extends AppGlideModule {
  @Override
  public void applyOptions(Context context, GlideBuilder builder) {
    builder.setMemoryCache(new LruResourceCache(10 * 1024 * 1024));
  }

  @Override
  public void registerComponents(Context context, Registry registry) {
    registry.append(Api.GifResult.class, InputStream.class, new GiphyModelLoader.Factory());
  }
}

請注意,@GlideModule 注解不能省略。

如果你的應用擁有多個 GlideModule,你需要把其中一個轉換成 AppGlideModule,剩下的轉換成 LibraryGlideModule。除非存在 AppGlideModule,否則程序不會發(fā)現(xiàn) LibraryGlideModule,因此你不能僅使用 LibraryGlideModule

3.3.2 程序庫

擁有一個或多個 GlideModule 的程序庫應該使用 LibraryGlideModule。程序庫不應該使用 AppGlideModule ,因為它在一個應用里只能有一個。因此,如果你試圖在程序庫里使用它,將不僅會妨礙這個庫的用戶設置自己的選項,還會在多個程序庫都這么做時造成沖突。

例如,v3 版本中 Volley 集成庫的 GlideModule

public class VolleyGlideModule implements GlideModule {
  @Override
  public void applyOptions(Context context, GlideBuilder builder) {
    // Do nothing.
  }

  @Override
  public void registerComponents(Context context, Registry registry) {
    registry.replace(GlideUrl.class, InputStream.class, new VolleyUrlLoader.Factory(context));
  }
}

在 v4 版本中可以轉換成為一個 LibraryGlideModule

@GlideModule
public class VolleyLibraryGlideModule extends LibraryGlideModule {
  @Override
  public void registerComponents(Context context, Registry registry) {
    registry.replace(GlideUrl.class, InputStream.class, new VolleyUrlLoader.Factory(context));
  }
}

3.3.3 清單解析

為了簡化遷移過程,盡管清單解析和舊的 GlideModule 接口已被廢棄,但它們在 v4 版本中仍被支持。AppGlideModule,LibraryGlideModule,與已廢棄的 GlideModule 可以在一個應用中共存。

然而,為了避免檢查元數(shù)據(jù)的性能天花板(以及相關的 bugs ),你可以在遷移完成后禁用掉清單解析,在你的 AppGlideModule 中復寫一個方法:

@GlideModule
public class GiphyGlideModule extends AppGlideModule {
  @Override
  public boolean isManifestParsingEnabled() {
    return false;
  }

  ...
}

3.3.4 using(), ModelLoader, StreamModelLoader

3.3.4.1 ModelLoader

ModelLoader API 在 v4 版本中仍然存在,并且它的設計目標仍然和它在 v3 中一樣,但有一些細節(jié)變化。

第一個細節(jié),ModelLoader 的子類型如 StreamModelLoader,現(xiàn)在已沒有存在的必要,用戶可以直接實現(xiàn) ModelLoader 。例如,一個 StreamModelLoader<File> 類現(xiàn)在可以通過 ModelLoader<File, InputStream> 的方式來實現(xiàn)和引用。

第二, ModelLoader 現(xiàn)在并不直接返回 DataFetcher,而是返回 LoadData。LoadData 是一個非常簡單的封裝,包含一個磁盤緩存鍵和一個 DataFetcher。

第三, ModelLoaders 有一個 handles() 方法,這使你可以為同一個類型參數(shù)注冊超過一個的 ModelLoader 。

將一個 ModelLoader 從 v3 API 轉換到 v4 API,通常是很簡單直接的。如果你在你的 v3 ModelLoader 中只是簡單地返回一個 DataFetcher

public final class MyModelLoader implements StreamModelLoader<File> {

  @Override
  public DataFetcher<InputStream> getResourceFetcher(File model, int width, int height) {
    return new MyDataFetcher(model);
  }
}

那么你在 v4 替代類上需要做的僅僅只是封裝一下這個 DataFetcher

public final class MyModelLoader implements ModelLoader<File, InputStream> {

  @Override
  public LoadData<InputStream> buildLoadData(File model, int width, int height,
      Options options) {
    return new LoadData<>(model, new MyDataFetcher(model));
  }

  @Override
  public void handles(File model) {
    return true;
  }
}

請注意,除了 DataFetcher 之外,模型也被傳遞給 LoadData 作為緩存鍵的一部分。這個規(guī)則為某些特殊場景提供了更多對磁盤緩存鍵的控制。大部分實現(xiàn)可以直接將 model 傳入 LoadData,就像上面這樣。

如果你僅僅是想為某些 model(而不是所有)使用你的 ModelLoader,你可以在你嘗試加載 model 之前使用 handles() 方法來檢查它。如果你從 handles 方法中返回了 false,那么你的 ModelLoader 將不能加載指定的 model ,即使你的 ModelLoader 類型 (在這個例子里是 File 和 InputStream) 與之匹配。

舉個例子,如果你在某個指定文件夾下寫入了加密的圖片,你可以使用 handles 方法來實現(xiàn)一個 ModelLoader 以從那個特定的文件夾下解密圖片,但是并不用于加載其他文件夾下的 File :

public final class MyModelLoader implements ModelLoader<File, InputStream> {
  private static final String ENCRYPTED_PATH = "/my/encrypted/folder";

  @Override
  public LoadData<InputStream> buildLoadData(File model, int width, int height,
      Options options) {
    return new LoadData<>(model, new MyDataFetcher(model));
  }

  @Override
  public void handles(File model) {
    return model.getAbsolutePath().startsWith(ENCRYPTED_PATH);
  }
}
3.3.4.2 using()

using API 在 Glide v4 中被刪除了,這是為了鼓勵用戶使用 AppGlideModule 一次性地注冊所有組件,避免對象重用。你無需每次加載圖片時都創(chuàng)建一個新的 ModelLoader ;你應該在 AppGlideModule 中注冊一次,然后交給 Glide 在每次加載時檢查 model (即你傳入 load() 方法的對象) 來決定什么時候使用你注冊的 ModelLoader。

為了確保你僅為特定的 model 使用你的 ModelLoader ,請像上面展示的那樣實現(xiàn) handles 方法:檢查每個 model ,但僅在應當使用你的 ModelLoader 時才返回 true。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容