这个礼拜学习了郭霖大神的Glide最全解析专题文章,特此在此做个总结。记录一些Glide的高级用法。
自定义GlideUrl加载图片
如果图片出于安全的考虑需要在图片地址后面加上token,这样导致同一张图片导致不同的地址,列如下面的地址:
http://url.image.com/test.jpg?token=adfnjkews8832734
这里的token可能随时都会变,这样就导致图片地址一直在变,这样就导致缓存失效,一发生改变就去网络加载图片。我们可以自己定义自己的GlideUrl来解决这个问题。
public class MyGlideUrl extends GlideUrl {
private String mUrl;
public MyGlideUrl(String url) {
super(url);
mUrl = url;
}
@Override
public String getCacheKey() {
if(!TextUtils.isEmpty(mUrl)) {
return mUrl.replace(findTokenParam(), "");
}
return "";
}
private String findTokenParam() {
String tokenParam = "";
int tokenKeyIndex = mUrl.contains("?token=") ? mUrl.indexOf("?token=") : mUrl.indexOf("&token=");
if (tokenKeyIndex != -1) {
int nextAndIndex = mUrl.indexOf("&", tokenKeyIndex + 1);
if (nextAndIndex != -1) {
tokenParam = mUrl.substring(tokenKeyIndex + 1, nextAndIndex + 1);
} else {
tokenParam = mUrl.substring(tokenKeyIndex);
}
}
return tokenParam;
}
}
接下来就可以用下面代码来使用了
Glide.with(this)
.load(new MyGlideUrl(url))
.into(imageView);
使用Target来加载显示图片
来看下Target的继承关系:
通常只需要在SimpleTarget和ViewTarget上自定义就可以。如下面用SimpleTarget来加载图片:
SimpleTarget<GlideDrawable> simpleTarget=new SimpleTarget<GlideDrawable>() {
@Override
public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) {
avatar.setImageDrawable(resource);
}
};
String url="https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=2334584121,1324775889&fm=27&gp=0.jpg";
Glide.with(getActivity()).load(url)
.into(simpleTarget);
这个效果和into(ImageView imageview)没什么区别,也可以用ViewTarget来实现给任意View加载图片。
先自定义一个View,代码如下:
public class MyView extends FrameLayout {
private ViewTarget<MyView,GlideDrawable> viewTarget;
public MyView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
viewTarget=new ViewTarget<MyView, GlideDrawable>(this) {
@Override
public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) {
MyView myView=getView();
myView.setDrawable(resource);
}
};
}
public ViewTarget<MyView,GlideDrawable> getViewTarget(){
return viewTarget;
}
public void setDrawable(Drawable drawable){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
setBackground(drawable);
}else {
setBackgroundDrawable(drawable);
}
}
}
在进行如下代码应用:
Glide.with(getActivity()).load(url)
.into(myView.getViewTarget());
使用preload方法预加载图片
Glide.with(this)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.SOURCE)
.preload();
注意的是我们使用了preload()方法,最好将缓存策略设置为DiskCacheStrategy.SOURCE。因为preload()默认预加载原图尺寸,而into()方法会根据控件大小去加载图片,如果不设置DiskCacheStrategy.SOURCE,很容易造成使用了preload()方法预加载图片,再用into()又去网络加载图片。
DiskCacheStrategy.SOURCE 只缓存原始图片 //预加载或者 仅仅下载图片使用
DiskCacheStrategy.RESULT 只缓存转换过得图片 //一般使用这个
DiskCacheStrategy.ALL 既缓存原始图片,也缓存转换过后的图片
DiskCacheStrategy.NONE 不缓存任何内容
调用了预加载后再去加载图片就不会消耗流量加载的很快了。
Glide.with(this)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.SOURCE)
.into(imageView);
获取缓存图片文件地址
有时候我们需要获取缓存图片的地址,这时候就需要用到downloadOnly()方法了。downloadOnly()方法表示只会下载图片,而不会对图片进行加载。当图片下载完成之后,我们可以得到图片的存储路径。它有两个重载方法如下:
- downloadOnly(int width, int height)
- downloadOnly(Y target)
其中downloadOnly(int width, int height)是用于在子线程中下载图片的,而downloadOnly(Y target)是用于在主线程中下载图片的。
先来看一下downloadOnly(int width,int height)如何使用:
final ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(new Runnable() {
@Override
public void run() {
try {
String url = "https://cdn.duitang.com/uploads/item/201204/09/20120409130851_Emr2W.jpeg";
final Context context = getApplicationContext();
FutureTarget<File> target = Glide.with(context)
.load(url)
.downloadOnly(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL);
final File imageFile = target.get(); //这里会一直阻塞等待结果返回
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(context, imageFile.getPath(), Toast.LENGTH_LONG).show();
}
});
} catch (Exception e) {
e.printStackTrace();
} finally {
executorService.shutdown();
}
}
});
下面看一下 downloadOnly(Y target)的实现,其实 downloadOnly(int width, int height) 内部也是依靠Targent来实现的,所以这里我们自己来创建一个Target
public class ImageTarget implements Target<File> {
@Override
public void onStart() {
}
@Override
public void onStop() {
}
@Override
public void onDestroy() {
}
@Override
public void onLoadStarted(Drawable placeholder) {
}
@Override
public void onLoadFailed(Exception e, Drawable errorDrawable) {
}
@Override
public void onResourceReady(File resource, GlideAnimation<? super File> glideAnimation) {
Log.d("lx", resource.getPath());
}
@Override
public void onLoadCleared(Drawable placeholder) {
}
@Override
public void getSize(SizeReadyCallback cb) {
cb.onSizeReady(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL);
}
@Override
public void setRequest(Request request) {
}
@Override
public Request getRequest() {
return null;
}
}
实现Target接口需要重写很多方法,这些方法大多是数Glide加载图片生命周期的一些回调,我们可以不用管它们,其中只有两个方法是必须实现的,一个是getSize()方法,一个是onResourceReady()方法。Glide在开始加载图片之前会先计算图片的大小,然后回调到onSizeReady()方法当中,之后才会开始执行图片加载。而这里,计算图片大小的任务就交给我们了。只不过这是一个最简单的Target实现,我在getSize()方法中就直接回调了Target.SIZE_ORIGINAL,表示图片的原始尺寸。图片下载完成之后就会回调onResourceReady()方法。
String url = "https://cdn.duitang.com/uploads/item/201204/09/20120409130851_Emr2W.jpeg";
Glide.with(this)
.load(url)
.downloadOnly(new ImageTarget());
Glide图片监听
Glide图片监听可以使用listener()方法,基本用法如下:
String url = "https://cdn.duitang.com/uploads/item/201204/09/20120409130851_Emr2W.jpeg";
Glide.with(this)
.load(url)
.listener(new RequestListener<String, GlideDrawable>() {
@Override
public boolean onException(Exception e, String model, Target<GlideDrawable> target,
boolean isFirstResource) {
//这里返回true表示事件已经消化了,不会往下传递,返回false表示没有消耗
//如果设置为true error(int resid)设置异常占位图将会失效
return false;
}
@Override
public boolean onResourceReady(GlideDrawable resource, String model,
Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
//这里返回true表示事件已经消化了,不会往下传递,返回false表示没有消耗
//设置为ture了,就不会调用Target的onResourceReady()方法了
return false;
}
})
.into(imageView);
Glide图片变换功能
Glide内置有两种图片变换功能,一个是个是CenterCrop,一个是FitCenter。
如下
Glide.with(this)
.load(url)
.centerCrop()
.into(imageView);
Glide.with(this)
.load(url)
.fitCenter()
.into(imageView);
centerCrop裁剪图片中心区域充满控件显示,fitCenter会按图片最短边缩放图片。
我们也可以自己定义自己需要的图片变换,如圆形、黑白、高斯模糊。需要继承 BitmapTransformation并重写transform()方法。并实现自己的逻辑。如下实现一个圆形变换。
public class CircleBitmapTransformation extends BitmapTransformation {
public CircleBitmapTransformation(Context context) {
super(context);
}
public CircleBitmapTransformation(BitmapPool bitmapPool) {
super(bitmapPool);
}
@Override
public String getId() {
//图形变换唯一id,于其他图片变换做区分,这里返回完整类名就好
return "com.liuxin.gliddemo.CircleCrop";
}
/*
*图片变换逻辑处理
*@params pool Glide中的一个Bitmap缓存池,用于对Bitmap对象进行重用
*@params toTransform 原始图片Bitmap对象
*@params outWidth 变换后的图片宽度
*@params outHeight 变换的图片高度
*/
@Override
protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
//取原始图片的宽高最小值为圆的直径
int diameter = Math.min(toTransform.getWidth(), toTransform.getHeight());
//从Bitmap缓存池中获取Bitmap
final Bitmap bitmap = pool.get(outWidth, outHeight, Bitmap.Config.ARGB_8888);
final Bitmap result;
if (bitmap != null) {
result = bitmap;
} else {
//如果为空就创建一个
result = Bitmap.createBitmap(diameter, diameter, Bitmap.Config.ARGB_8888);
}
//要把一个长方形的图片截取成一个圆形图片,我们从中间画圆截取
//需要位移的x
int dx = (toTransform.getWidth() - diameter) / 2;
//需要位移的y
int dy = (toTransform.getHeight() - diameter) / 2;
//以Bitmap 对象创建一个画布
Canvas canvas = new Canvas(result);
Paint paint = new Paint();
//创建图片纹理
BitmapShader shader = new BitmapShader(toTransform, BitmapShader.TileMode.CLAMP,
BitmapShader.TileMode.CLAMP);
//不为0移动画布,准备纹理
if (dx != 0 || dy != 0) {
Matrix matrix = new Matrix();
matrix.setTranslate(-dx, -dy);
shader.setLocalMatrix(matrix);
}
paint.setShader(shader);
paint.setAntiAlias(true);
float radius = diameter / 2f;
//画一个圆形纹理
canvas.drawCircle(radius, radius, radius, paint);
//把bitmap对象放入到Bitmap缓存池中
if (bitmap != null && !pool.put(bitmap)) {
bitmap.recycle();
}
//返回结果
return result;
}
}
用以下代码进行图片变换
Glide.with(this)
.load(url)
.transform(new CircleBitmapTransformation(this))
.into(imageView);
更多图片变换 https://github.com/wasabeef/glide-transformations。这个开源库实现了很多图片变换功能。
自定义图片缓存路径
默认情况下Glide图片缓存在当前应用的私有目录下,我们可以通过如下配置自定义图片的缓存路径
public class GlideCache implements GlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
//配置自己的参数
Logger.e("GlideCache=========applyOptions");
//设置图片的显示格式ARGB_8888(指图片大小为32bit)
builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888);
//设置磁盘缓存目录(和创建的缓存目录相同)
String downloadDirectoryPath = FileCacheUtil.getInstance().getPicCacheDir();
//设置缓存的大小为100M
int cacheSize = 200 * 1000 * 1000;
builder.setDiskCache(new DiskLruCacheFactory(downloadDirectoryPath, cacheSize));
}
@Override
public void registerComponents(Context context, Glide glide) {
//配置注册自己的组件
Logger.e("GlideCache=========registerComponents");
}
}
除了上面的配置还需要在配置清单中application节点内进行配置:
<meta-data android:name="com.liuxin.glidedemo.utils.GlideCache"
android:value="GlideModule"/>
替换网络加载模块实现带进度条加载图片
默认情况Glide使用的是原始的HttpURLConnection进行网络请求,我们怎么替换成OkHttp进行网络请求呢
可以通过自定义组件来实现。
通过如下步骤实现:
第一步实现DataFetcher
public class OkHttpFetcher implements DataFetcher<InputStream> {
private final OkHttpClient client;
private final GlideUrl url;
private InputStream stream;
private ResponseBody responseBody;
private volatile boolean isCancelled;
public OkHttpFetcher(OkHttpClient client, GlideUrl url) {
this.client = client;
this.url = url;
}
@Override
public InputStream loadData(Priority priority) throws Exception {
Request.Builder requestBuilder = new Request.Builder()
.url(url.toStringUrl());
for (Map.Entry<String, String> headerEntry : url.getHeaders().entrySet()) {
String key = headerEntry.getKey();
requestBuilder.addHeader(key, headerEntry.getValue());
}
Request request = requestBuilder.build();
if (isCancelled) {
return null;
}
Response response = client.newCall(request).execute();
responseBody = response.body();
if (!response.isSuccessful() || responseBody == null) {
throw new IOException("Request failed with code: " + response.code());
}
stream = ContentLengthInputStream.obtain(responseBody.byteStream(),
responseBody.contentLength());
return stream;
}
@Override
public void cleanup() {
try {
if (stream != null) {
stream.close();
}
if (responseBody != null) {
responseBody.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public String getId() {
return url.getCacheKey();
}
@Override
public void cancel() {
isCancelled = true;
}
}
第二步实现ModelLoader
public class OkHttpGlideUrlLoader implements ModelLoader<GlideUrl, InputStream> {
private OkHttpClient okHttpClient;
public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {
private OkHttpClient client;
public Factory() {
}
public Factory(OkHttpClient client) {
this.client = client;
}
private synchronized OkHttpClient getOkHttpClient() {
if (client == null) {
client = new OkHttpClient();
}
return client;
}
@Override
public ModelLoader<GlideUrl, InputStream> build(Context context, GenericLoaderFactory factories) {
return new OkHttpGlideUrlLoader(getOkHttpClient());
}
@Override
public void teardown() {
}
}
public OkHttpGlideUrlLoader(OkHttpClient client) {
this.okHttpClient = client;
}
@Override
public DataFetcher<InputStream> getResourceFetcher(GlideUrl model, int width, int height) {
return new OkHttpFetcher(okHttpClient, model);
}
}
第三步注册到glide中去
public class GlideCache implements GlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
//配置自己的参数
Logger.e("GlideCache=========applyOptions");
//设置图片的显示格式ARGB_8888(指图片大小为32bit)
builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888);
//设置磁盘缓存目录(和创建的缓存目录相同)
String downloadDirectoryPath = FileCacheUtil.getInstance().getPicCacheDir();
//设置缓存的大小为100M
int cacheSize = 200 * 1000 * 1000;
builder.setDiskCache(new DiskLruCacheFactory(downloadDirectoryPath, cacheSize));
}
@Override
public void registerComponents(Context context, Glide glide) {
//配置注册自己的组件
Logger.e("GlideCache=========registerComponents");
glide.register(GlideUrl.class, InputStream.class, new OkHttpGlideUrlLoader.Factory());
}
}
接下来实现带进度条下载图片,可以通过OkHttp拦截器来实现,实现如下:
第一步先写一个进度条接口
public interface ProgressListener {
void onProgress(int progress);
}
第二步实现进度逻辑处理
public class ProgressResponseBody extends ResponseBody {
private static final String TAG = "ProgressResponseBody";
private BufferedSource bufferedSource;
private ResponseBody responseBody;
private ProgressListener listener;
public ProgressResponseBody(String url, ResponseBody responseBody) {
this.responseBody = responseBody;
listener = ProgressInterceptor.LISTENER_MAP.get(url);
}
@Override
public MediaType contentType() {
return responseBody.contentType();
}
@Override
public long contentLength() {
return responseBody.contentLength();
}
@Override
public BufferedSource source() {
if (bufferedSource == null) {
bufferedSource = Okio.buffer(new ProgressSource(responseBody.source()));
}
return bufferedSource;
}
private class ProgressSource extends ForwardingSource {
long totalBytesRead = 0;
int currentProgress;
ProgressSource(Source source) {
super(source);
}
@Override
public long read(Buffer sink, long byteCount) throws IOException {
long bytesRead = super.read(sink, byteCount);
long fullLength = responseBody.contentLength();
if (bytesRead == -1) {
totalBytesRead = fullLength;
} else {
totalBytesRead += bytesRead;
}
int progress = (int) (100f * totalBytesRead / fullLength);
Log.d(TAG, "download progress is " + progress);
if (listener != null && progress != currentProgress) {
listener.onProgress(progress);
}
if (listener != null && totalBytesRead == fullLength) {
listener = null;
}
currentProgress = progress;
return bytesRead;
}
}
}
第三部实现进度条拦截器
public class ProgressInterceptor implements Interceptor {
static final Map<String, ProgressListener> LISTENER_MAP = new HashMap<>();
public static void addListener(String url, ProgressListener listener) {
LISTENER_MAP.put(url, listener);
}
public static void removeListener(String url) {
LISTENER_MAP.remove(url);
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
String url = request.url().toString();
ResponseBody body = response.body();
Response newResponse = response.newBuilder().body(new ProgressResponseBody(url, body)).build();
return newResponse;
}
}
最后在配置中添加
@Override
public void registerComponents(Context context, Glide glide) {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.addInterceptor(new ProgressInterceptor());
OkHttpClient okHttpClient = builder.build();
glide.register(GlideUrl.class, InputStream.class, new OkHttpGlideUrlLoader.Factory(okHttpClient));
}
到这一步就可以,使用方法:
ProgressInterceptor.addListener(url, new ProgressListener() {
@Override
public void onProgress(int progress) {
Logger.i(TAG,"progress"+progress);
}
});
Glide.with(this)
.load(url)
.listener(new RequestListener<String, GlideDrawable>() {
@Override
public boolean onException(Exception e, String model, Target<GlideDrawable> target,
boolean isFirstResource) {
//这里返回true表示事件已经消化了,不会往下传递,返回false表示没有消耗
//如果设置为true error(int resid)设置异常占位图将会失效
return false;
}
@Override
public boolean onResourceReady(GlideDrawable resource, String model,
Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
//这里返回true表示事件已经消化了,不会往下传递,返回false表示没有消耗
//设置为ture了,就不会调用Target的onResourceReady()方法了
ProgressInterceptor.removeListener(url);
return false;
}
})
.into(imageview);