背景调研看这里->图片缓存第三方库
去年年初的时候,有做过一轮调研。当时是选了Glide,也针对它做了一些封印。把它解耦出来,方便换库。时间向前,当时那个项目已经停了快一年了,代码的封装还是蛮细心的,这里放上来给自己做个笔记吧。
Glide的API很友好,使用起来挺简单的。二级缓存主要是LRU算法。
LRU 即 Least Recently Used。每发生一次读内存操作,首先查找待读取的数据是否存在于缓存中,若是,则缓存命中,返回数据;若否,则缓存未命中,从内存中读取数据,并把该数据添加到缓存中。向缓存添加数据时,如果缓存已满,则需要删除访问时间最早的那条数据,这种更新缓存的方法就叫做LRU。实现的话,简单做就是双向链表+hashmap。 这个算法很经典。kernel的页缓存管理也是用这个的。
当然这几个问题都修正了,细节看代码 :)
base on ‘com.github.bumptech.glide:glide:3.7.0’
if 4.x -> migrating
调用入口
public class IMGs { public static final ImageLoader INSTANCE = new ImageLoaderGlideImpl(); }接口
interface ImageLoader { @UiThread fun printCurrentDiskCacheSizeToLog() fun cleanCache(): Boolean fun cleanCacheOnDisk(): Boolean fun getCacheFile(): File @UiThread fun bind(context: Context, view: ImageView, uri: Uri) /** * used ImageLoader#bind(Context,ImageView, String, int) instead */ @Deprecated("") @UiThread fun bind(context: Context, view: ImageView, url: String?) @UiThread fun bind(context: Context, view: ImageView, url: String?, errorResId: Int) @UiThread fun bind(context: Context, view: ImageView, resId: Int) /** * In context'Lifecycle , bind option to ImageView */ @UiThread fun bind(context: Context, view: ImageView, options: Options) /** * In context'Lifecycle , bind option to Callback */ @UiThread fun bind(context: Context, options: Options, bitmapCall: BitmapCall) @UiThread fun bindCircle(context: Context, view: ImageView, errorResId: Int) @UiThread fun bindCircle(context: Context, view: ImageView, url: String?, errorResId: Int) @UiThread fun bindCircle(context: Context, view: ImageView, url: String?, errorResId: Int, cornerPx: Int) } public interface BitmapCall { @UiThread void onFail(@NonNull Exception e, @Nullable Drawable errorDrawable); @UiThread void onSuccess(@NonNull Bitmap bitmap); }Glide 实现类
/********************************************************************* * This file is part of seeyoutime project * Created by wuyisheng@seeyoutime.com on 2017/2/21. * Copyright (c) 2017 XingDian Co.,Ltd. - All Rights Reserved *********************************************************************/ final class ImageLoaderGlideImpl implements ImageLoader { private final RequestListener<Object, GlideDrawable> drawableRequestListener; ImageLoaderGlideImpl() { drawableRequestListener = new RequestListener<Object, GlideDrawable>() { @Override public boolean onException(Exception e, Object model, Target<GlideDrawable> target, boolean isFirstResource) { if (e != null) Lg.e(e); return false; } @Override public boolean onResourceReady(GlideDrawable resource, Object model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) { return false; } }; } @SuppressLint("StaticFieldLeak") @UiThread @Override public void printCurrentDiskCacheSizeToLog() { new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { File files = getCacheFile(); long size = getSize(files); Lg.d(size / (1024 * 1024) + "M/" + size); return null; } }.execute(); } @Override public boolean cleanCache() { Glide.get(SeeYouApp.getInstance()).clearMemory(); return true; } @Override public boolean cleanCacheOnDisk() { Glide.get(SeeYouApp.getInstance()).clearDiskCache(); return true; } @NonNull @Override public File getCacheFile() { return Glide.getPhotoCacheDir(SeeYouApp.getInstance()); } @Override public void bind(@NonNull Context context, @NonNull ImageView view, @NonNull final Uri uri) { bind(context, view, new Options(uri)); } @Deprecated @Override public void bind(@NonNull Context context, @NonNull ImageView view, @Nullable final String url) { bind(context, view, new Options(url)); } @Override public void bind(@NonNull Context context, @NonNull ImageView view, final int resId) { bind(context, view, new Options(resId)); } @UiThread public void bind(@NonNull Context context, @NonNull ImageView view, @Nullable final String url, final int errorResId) { bind(context, view, new Options(url, errorResId)); } @UiThread public void bindCircle(@NonNull Context context, @NonNull ImageView view, final int errorResId) { Options ops = new Options(errorResId); ops._roundCorner = true; ops._roundCornerPx = view.getWidth(); bind(context, view, ops); } @UiThread public void bindCircle(@NonNull Context context, @NonNull ImageView view, @Nullable final String url, final int errorResId) { Options ops = new Options(url, errorResId); ops._roundCorner = true; ops._roundCornerPx = view.getWidth(); bind(context, view, ops); } @UiThread public void bindCircle(@NonNull Context context, @NonNull ImageView view, @Nullable final String url, final int errorResId, final int cornerPx) { Options ops = new Options(url, errorResId); ops._roundCorner = true; ops._roundCornerPx = cornerPx; bind(context, view, ops); } @UiThread public void bind(@NonNull final Context context, @NonNull ImageView view, @NonNull final Options options) { if (!isValidContext(context)) { return; } if (!options.isValid()) { if (options._placeDrawable != null) { view.setImageDrawable(options._placeDrawable); return; } else if (options._errorDrawable != null) { view.setImageDrawable(options._errorDrawable); return; } //Is frustrated that 'url' from remoteService(http) can be empty/null //only inner-test can throw exception //throw new IllegalArgumentException("ImageMgr option is not valid,'_resId、_url、_uri、_placeResId、_placeDrawable、_errorDrawable' can't be empty"); return; } if (view instanceof CircleImageView) { options._dontAnimate = true; } RequestManager requestManager = Glide.with(context); options.build(requestManager).listener(drawableRequestListener).into(view); } @Override public void bind(@NonNull final Context context, @NonNull final Options options, @NonNull final BitmapCall bitmapCall) { if (!isValidContext(context)) { Lg.e("not Valid Context"); return; } if (!options.isValid()) { //Is frustrated that 'url' from remoteService(http) can be empty/null //only inner-test can throw exception //throw new IllegalArgumentException("ImageMgr option is not valid,'_resId、_url、_uri' can't be empty"); Lg.repr(Author.YISHENG, new Lg.ErrReporter() {{ _http_report = true; _message = context.getClass().getName() + options.toString(); _tag = ImageLoaderGlideImpl.class.getSimpleName(); }}); bitmapCall.onFail(new IllegalArgumentException("option is not valid"), null); return; } RequestManager requestManager = Glide.with(context); SimpleTarget<GlideDrawable> target = new SimpleTarget<GlideDrawable>() { @Override public void onLoadFailed(@Nullable Exception e, @Nullable Drawable errorDrawable) { e = e == null ? new Exception("un-know error,onLoadFailed") : e; Lg.e(e); bitmapCall.onFail(e, errorDrawable); } @Override public void onResourceReady(GlideDrawable bitmap, GlideAnimation glideAnimation) { if (bitmap != null && bitmap instanceof GlideBitmapDrawable) { Bitmap tmp = ((GlideBitmapDrawable) bitmap).getBitmap(); if(tmp != null){ bitmapCall.onSuccess(tmp); return; } } bitmapCall.onFail(new Exception("un-know error,bitmap is null"), null); } }; options.build(requestManager).listener(drawableRequestListener).into(target); } private boolean isValidContext(Context context) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) { Lg.e("not support : Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2"); return false; } if (context instanceof Activity) { Activity ac = ((Activity) context); if (ac.isFinishing() || ac.isDestroyed()) { Lg.d("Activity isFinishing or Destroyed"); return false; } } return true; } private long getSize(File file) { int size = 0; if (file != null && file.exists()) { if (file.isDirectory()) { File[] fs = file.listFiles(); if (fs != null) { for (File f : fs) { size += getSize(f); } } } else if (file.isFile()) { size += file.length(); } } return size; } } /*** * 图片加载自定义option */ public class Options { public static final int DEFAULT_RES_ID = 0; //目标资源Id private final int _resId; //目标资源网络地址 private final String _url; //目标资源URI private final Uri _uri; //优先级别:如果是头像那些,优先级可以提高一点 public Priority _priority = Priority.NORMAL; //加载占位图资源Id*/ public int _placeResId = DEFAULT_RES_ID; public Drawable _placeDrawable = null; public int _errorResId = DEFAULT_RES_ID; public Drawable _errorDrawable = null; //定义图片大小/占位 px public int _sizeWidth = DEFAULT_RES_ID; public int _sizeHeight = DEFAULT_RES_ID; //缩略图显示 public boolean _thumbnail = false; //缩略图缩小尺寸 default:eg 1000×1000px to 100*100px public float _thumbnailSize = 0.1f; //矩形图片 直角 显示成 圆角 public boolean _roundCorner = false; //定义圆角弧度 public int _roundCornerPx = 0; //circleImageView 会干扰 Glide 的渐变动画 //(合理的方法是替换掉全部的circleImageView,但是修改略多) public boolean _dontAnimate = false; public Options(@Nullable String url) { this._url = url; this._resId = DEFAULT_RES_ID; this._uri = null; } public Options(@NonNull Uri uri) { this._uri = uri; this._url = null; this._resId = DEFAULT_RES_ID; } public Options(int resId) { this._resId = resId; this._url = null; this._uri = null; } public Options(@Nullable String url, int errorResId) { this._errorResId = errorResId; if (TextUtils.isEmpty((url))) { this._url = null; this._resId = errorResId; } else { this._url = url; this._resId = DEFAULT_RES_ID; } this._uri = null; } @Override public String toString() { //noinspection StringBufferReplaceableByString return new StringBuilder("_resId:").append(_resId) .append(",_url:").append(_url) .append(",_uri:").append(_uri) .append(",_placeResId:").append(_placeResId) .append(",_errorResId:").append(_errorResId) .append(",_sizeWidth:").append(_sizeWidth) .append(",_sizeHeight:").append(_sizeHeight) .append(",_thumbnail:").append(_thumbnail) .append(",_thumbnailSize:").append(_thumbnailSize) .append(",_roundCorner:").append(_roundCorner) .append(",_roundCornerPx:").append(_roundCornerPx) .toString(); } /*package*/ boolean isValid() { return (_resId != DEFAULT_RES_ID && _resId != -1) || !TextUtils.isEmpty(_url) || _uri != null; } /*package*/ DrawableTypeRequest<?> build(RequestManager requestManager) { if (!isValid()) { throw new IllegalArgumentException("ImageMgr option is not valid"); } DrawableTypeRequest<?> builder; if (_resId != DEFAULT_RES_ID) { builder = requestManager.load(_resId); } else if (_uri != null) { builder = requestManager.load(_uri); } else if (!TextUtils.isEmpty(_url)) { builder = requestManager.load(_url); } else { builder = requestManager.load(_errorResId); } if (builder != null) { builder.diskCacheStrategy(DiskCacheStrategy.ALL); builder.signature(new StringSignature(Facts.getInstance().appVersion)); if (_errorResId != DEFAULT_RES_ID) { builder.error(_errorResId); } if (_placeResId != DEFAULT_RES_ID) { builder.placeholder(_placeResId); } if (_placeDrawable != null) { builder.placeholder(_placeDrawable); } if (_errorDrawable != null) { builder.error(_errorDrawable); } if (_sizeWidth > 0 && _sizeHeight > 0) { builder.override(_sizeWidth, _sizeHeight); } if (_thumbnail && _thumbnailSize > 0) { builder.thumbnail(_thumbnailSize); } if (_roundCorner && _roundCornerPx > 0) { builder.transform(new RoundCornerTransformation(_roundCornerPx)); } if (_dontAnimate) { builder.dontAnimate(); } builder.priority(_priority); } return builder; } private static class RoundCornerTransformation extends BitmapTransformation { private int BITMAP_ID = 0; private int _roundPx = 0; RoundCornerTransformation(int roundPx) { super(SeeYouApp.getInstance()); this._roundPx = roundPx; } @Override protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) { if (_roundPx > 0) { BITMAP_ID = toTransform.hashCode(); Bitmap output = pool.get(toTransform.getWidth(), toTransform.getHeight(), Bitmap.Config.ARGB_4444); if (output == null) { output = Bitmap.createBitmap(toTransform.getWidth(), toTransform.getHeight(), Bitmap.Config.ARGB_4444); } Canvas canvas = new Canvas(output); final int color = 0xff424242; final Paint paint = new Paint(); final Rect rect = new Rect(0, 0, toTransform.getWidth(), toTransform.getHeight()); final RectF rectF = new RectF(rect); paint.setAntiAlias(true); canvas.drawARGB(0, 0, 0, 0); paint.setColor(color); canvas.drawRoundRect(rectF, _roundPx, _roundPx, paint); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); canvas.drawBitmap(toTransform, rect, rect, paint); return output; } else { return toTransform; } } @Override public String getId() { return String.valueOf(BITMAP_ID); } } } public class XDGlideModule implements GlideModule { @Override public void applyOptions(Context context, GlideBuilder builder) { /* no costumization */ } @Override public void registerComponents(Context context, Glide glide) { glide.register(String.class, InputStream.class, new HeaderLoader.Factory()); } private static class HeaderLoader extends BaseGlideUrlLoader<String> { HeaderLoader(Context context) { super(context); } @Override protected String getUrl(String model, int width, int height) { return model; } @Override protected Headers getHeaders(String model, int width, int height) { LazyHeaders.Builder builder = new LazyHeaders.Builder(); for (Map.Entry<String, Object> entry : NetMgr.getInstance().headerTemplate().entrySet()) { builder.addHeader(entry.getKey(), String.valueOf(entry.getValue())); } return builder.build(); } static class Factory implements ModelLoaderFactory<String, InputStream> { @Override public StreamModelLoader<String> build(Context context, GenericLoaderFactory factories) { return new HeaderLoader(context); } @Override public void teardown() { /* nothing to free */ } } } }PS: 现在公司代码管控很严格,所以今后只能讲理论,不会直接放代码了。 : (
