1、前语

系列文章

5分钟学会设计形式之战略形式(Strategy Pattern)

上一篇,咱们举例一个View的例子,或许不是很明晰,所以根据公司用的图片加载Glide来封装一个图片加载结构,十分的简练易懂~

有时候Glide已经不能彻底满意需求,或许呈现了一些功能或稳定性问题。这时候,如果咱们在代码中直接运用了特定的结构,那么咱们需求对整个应用程序进行修正,这将会带来很大的风险和困难。关于图片加载结构来说,咱们需求封装一些笼统的接口,使得咱们能够在不影响应用程序其他部分的情况下,轻松更换底层的图片加载库。一起,咱们还需求将图片加载的逻辑封装在一个独立的模块中,使得咱们能够独自对其进行更新和维护。

如果您有任何疑问、对文章写的不满意、发现过错或许有更好的办法,欢迎在评论、私信或邮件中提出,十分感谢您的支撑。

2、笼统行为(战略接口)

IImageEngine接口便是战略接口,定义了加载图片的三个办法:loadResource、loadUrl和loadBitmap。它笼统出了不同的图片加载库所需完成的中心功能,并为详细战略供给了统一的办法签名。

interface IImageEngine {
  /**
   * 给定的资源 ID 加载一个图片资源
   */
  fun loadResource(
    view: ImageView?,
    @DrawableRes resourceId: Int,
    radius: Int = 0,
    callback: ImageLoadCallback?
   )
  /**
   * 指定的 URL 加载一张图片
   */
  fun loadUrl(view: ImageView?, url: String, radius: Int = 0, callback: ImageLoadCallback?)/**
   * 从指定的 URL 加载一张图片,并回来一个 Bitmap 目标。
   */
  fun loadBitmap(context: Context, url: String, radius: Int = 0, callback: ImageLoadCallback?)
}

3、加载回调

当然,一切的加载,不会总是一往无前,所以咱们需求一个加载回调

interface ImageLoadCallback {
  fun onSuccess(resource: Bitmap?) {}
  fun onLoadStarted(placeholder: Drawable?) {}
  fun onLoadFailed(errorDrawable: Drawable?) {}
}

4、GlideEngine完成

GlideEngine是完成了IImageEngine接口的详细完成类,咱们经过阅览代码就能够得知

internal object GlideEngine : IImageEngine {
  override fun loadResource(
    view: ImageView?,
    resourceId: Int,
    radius: Int,
    callback: ImageLoadCallback?
   ) {
    view?.run {
      Glide.with(this)
         .load(resourceId).also {
          if (radius > 0) {
            it.apply(RequestOptions().transform(RoundedCorners(radius)))
           }
         }.into(createTarget(view, callback))
     }
   }
​
  override fun loadUrl(
    view: ImageView?,
    url: String,
    radius: Int,
    callback: ImageLoadCallback?
   ) {
    view?.run {
      Glide.with(context).load(url).also {
        if (radius > 0) {
          it.apply(RequestOptions().transform(RoundedCorners(radius)))
         }
       }.into(createTarget(view, callback))
     }
   }
​
  override fun loadBitmap(
    context: Context,
    url: String, radius: Int,
    callback: ImageLoadCallback?
   ) {
    Glide.with(context).asBitmap().load(url).also {
      if (radius > 0) {
        it.apply(RequestOptions().transform(RoundedCorners(radius)))
       }
     }.into(createBitmapTarget(callback))
   }
​
  private fun createTarget(
    view: ImageView?,
    callback: ImageLoadCallback?
   ): CustomTarget<Drawable> {
    return object : CustomTarget<Drawable>() {
      override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
        view?.setImageDrawable(resource)
        callback?.onSuccess(null)
       }
​
      override fun onLoadStarted(placeholder: Drawable?) {
        callback?.onLoadStarted(placeholder)
       }
​
      override fun onLoadFailed(errorDrawable: Drawable?) {
        callback?.onLoadFailed(errorDrawable)
       }
​
      override fun onLoadCleared(placeholder: Drawable?) {}
     }
   }
​
  private fun createBitmapTarget(callback: ImageLoadCallback?): CustomTarget<Bitmap> {
    return object : CustomTarget<Bitmap>() {
      override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
        callback?.onSuccess(resource)
       }
      override fun onLoadStarted(placeholder: Drawable?) {
        callback?.onLoadStarted(placeholder)
       }
      override fun onLoadFailed(errorDrawable: Drawable?) {
        callback?.onLoadFailed(errorDrawable)
       }
      override fun onLoadCleared(placeholder: Drawable?) {}
     }
   }
}

5、COilEngine完成

COilEngine是完成了IImageEngine接口的详细完成类,咱们经过阅览代码就能够得知

class COilEngine : IImageEngine {
​
  override fun loadResource(
    view: ImageView?,
    @DrawableRes resourceId: Int,
    radius: Int,
    callback: ImageLoadCallback?
   ) {
    view?.setImageResource(resourceId)
    callback?.onSuccess(null)
   }
​
  override fun loadUrl(
    view: ImageView?,
    url: String,
    radius: Int,
    callback: ImageLoadCallback?
   ) {
    view?.let { imageView ->
      val request = createImageRequest(url, radius, callback)
      imageLoader().execute(request) {
        it.drawable?.let { drawable ->
          imageView.setImageDrawable(drawable)
          callback?.onSuccess(null)
         } ?: run {
          callback?.onLoadFailed(null)
         }
       }
     }
   }
​
  override fun loadBitmap(
    context: Context,
    url: String,
    radius: Int,
    callback: ImageLoadCallback?
   ) {
    val request = createImageRequest(url, radius, callback)
    imageLoader().execute(request) {
      it.toBitmap()?.let { bitmap ->
        callback?.onSuccess(bitmap)
       } ?: run {
        callback?.onLoadFailed(null)
       }
     }
   }
​
  private fun createImageRequest(
    url: String,
    radius: Int,
    callback: ImageLoadCallback?
   ): ImageRequest {
    val builder = ImageRequest.Builder(context)
       .data(url)
       .parameters(Parameters.Builder().set("radius", radius).build())
​
    if (callback != null) {
      builder.target(object : Target {
        override fun onStart(placeholder: Drawable?) {
          callback.onLoadStarted(placeholder)
         }
        override fun onError(error: Drawable?) {
          callback.onLoadFailed(error)
         }
        override fun onSuccess(result: Drawable) {}
​
        override fun onCancel() {}
       })
     }
    return builder.build()
   }
​
  private fun imageLoader(): coil.ImageLoader {
    return Coil.loader()
   }
}

6、上下文类的定义

ImageLoader类则是上下文类,担任根据调用方的需求,选择不同的图片加载库。它把详细的战略完成作为一个参数,经过结构函数或许其他办法注入到类中。在履行图片加载时,ImageLoader目标调用传入的详细战略完成的办法。

一般而言,咱们会这么写,经过一个单例,来调用传入的IImageEngine完成类

object ImageLoader {
  private var imageEngine: IImageEngine? = null
  fun <T : IImageEngine> init(ie: T) {
    imageEngine = ie
   }
​
  fun loadImage(
    view: ImageView?,
    url: String,
    radius: Int = 0,
    callback: ImageLoadCallback? = null
   ) {
    imageEngine?.loadUrl(view, url, radius, callback)
   }
​
  fun loadResource(
    view: ImageView?,
    resourceId: Int,
    radius: Int = 0,
    callback: ImageLoadCallback? = null
   ) {
    imageEngine?.loadResource(view, resourceId, radius, callback)
   }
​
  fun loadBitmap(
    context: Context,
    url: String,
    radius: Int = 0,
    callback: ImageLoadCallback? = null
   ) {
    imageEngine?.loadBitmap(context, url, radius, callback)
   }
}

但是得益于Kotlin的语法糖,咱们能够用托付的办法,省略许多的代码细节

object ImageLoader : IImageEngine by GlideEngine

其实编译出来的代码是如出一辙的

5分钟学会设计模式之策略模式(图片加载框架)

你也能够皮一点

object ImageLoader1 : IImageEngine by GlideEngine
object ImageLoader2 : IImageEngine by CoilEngine

这样调用也是十分简略了

 ImageLoader.loadUrl(imageView, url, radius, callback)

7、扩展函数

当然一个懒狗,是不会想多传一个ImageView目标的,用上Kotlin语法糖

fun ImageView.loadUrl(url: String, radius: Int = 0, callback: ImageLoadCallback?) {
  ImageLoader.loadUrl(this, url, radius, callback)
}
fun ImageView.loadRes(@DrawableRes resourceId: Int, radius: Int = 0, callback: ImageLoadCallback?) {
  ImageLoader.loadResource(this, resourceId, radius, callback)
}

这样你就能够直接把它当做ImageView的办法运用了

imageView.loadRes(xxx)

8、总结

作为一个加载图片的结构,咱们需求考虑以下几点:

  1. 图片加载的来历:本地图片、网络图片、Bitmap 等。
  2. 加载图片的办法:同步加载、异步加载,图片压缩等。
  3. 图片加载的功能和效率:需求考虑内存占用、加载速度、缓存等。
  4. 关于不同的加载场景,如列表、详情页等,需求采用不同的加载战略。

到这儿咱们都只考虑第一点的第一部分,不过由于主要是为了讲战略形式的应用,所以嘻嘻嘻。不过还是完成了对不同图片加载库的无缝切换

关于Glide、Coil的运用不做赘述啦

就到这)-(

如果您有任何疑问、对文章写的不满意、发现过错或许有更好的办法,欢迎在评论、私信或邮件中提出,十分感谢您的支撑。

“敞开生长之旅!这是我参加「日新方案 2 月更文应战」的第 5 天,点击检查活动详情”