一、定义
Glide 一个被google所推荐的图片加载库,作者是bumptech。对Android SDk 最低要求是 API 10
与之功能类似的是Square公司的picasso
二、基本概念
Model :数据来源 :Uri、本地文件、资源ID
Data :加工数据
Resource :对原始数据进行解码,解码之后的资源 resource
Resource decode :资源解码器
TransformedResource:转换资源
TranscodedResource:转码,将静态、Gif动态图进行格式转换以便能加载
Target :目标图片
三、整体流程
A:Model图片数据源 ———ModelLoader加载—>原始数据Data——Decoder解码——>
Resource——Transform裁剪——>TransformResource——Transcode转码——>TranscodeResource——封装——>Target
四、源码
引入:compile 'com.github.bumptech.glide:glide:3.7.0'
4.1、使用流程三步曲: Glide
.with(“上下文context”)
.load(“url”)
.into(“显示的控件资源");
4.2、常用加载图片的配置参数:
public void LoadImage(View view) {
//with 创建一个加载图片的Glide实例,流式接口
Glide.with(getApplicationContext()) //指定的Context,传入的参数会决定整个Glide的生命周期
ps:图片的加载会和传入的Acitivty或是Fragment保持一致,所以建议使用Activity 或是Fragment作为参数而不是单纯的使用this 或是 context
.load("url")//指定的图片的URL
加载占位图:-int or Drawable
.placeholder(R.mipmap.ic_launcher) //指定图片未成功加载前现实的图片占位符
// ,直到加载的网络图片显示就会被替换,仅支持本地图片资源
错误占位图:-int or Drawable
.error(R.mipmap.ic_launcher) //指定图片加载失败显示的图片占位图
加载本地缩略图:
.thumbnail( float ) //0.2f Glide会显示原始图片的20%大小,注意ImageView的ScaleType值的设置
加载网络缩略图:
DrawableRequestBuilder<String> thumbnailRequest = Glide.with(context).load(url);
Glide.with( context) .load(url)
.thumbnail (thumbnailRequest ).into (imageView);
加载图片动画效果:
.crossFade() or crossfade(int duration) //强制开启GLide默认的图片淡出淡入效果,默认持续时间300ms,设置dontAnimate()设置无任何淡出淡入效果
显示Gif 和Video 功能:
String gifUrl = “…xxxxoo.git”;
.load (gifUrl)
.asGif()
.error(xxxx)
// 如果图片类型不是Gif的话 就会当作load 失败来处理
显示静态的Gif图片 //仅仅显示Gif的第一桢图像
String gifUrl = “…xxxxoo.git”;
.load (gifUrl)
.asBitmap()
.error(xxxx)
显示手机本地视频文件:
String filePath = “/storage/emulated/0/xxxx.mp4"
.load(Uri.fromFile ( new File (filePath) ))
.override(300, 300) //不自动适配图片尺寸的时候进行手动进行图片尺寸设置 ,glide可以自动限制图片的尺寸来优化图片的加载
//ps:Glide不会完整的加载原始图片资源到内存当中,会自动判断imageView大小再进行最优尺寸选择加载
.fitCenter() //指定图片缩放类型1,显示的图像宽和高都小于等于ImageView的边界范围
//ps:缺陷 有可能不被填满所需要的ImageView, FIT_CENTER
.centerCrop() //指定图片缩放类型2。显示的图像填充整个ImageView,然后裁剪额外超出的部分
//ps:缺陷,有可能完整填充ImageView但是图片不能完整显示,CENTER_CROP
.skipMemoryCache(true) //不将该图片放在内存缓存中,但是仍然会利用该部分磁盘缓存,依然会在磁盘缓存中建立区域,Glide默认将图片放入内存缓存中
//ps: Glide默认会将图片缓存到内存缓存中所以无需传入false值,若同一Url地址在首次没有调用该方法则第二次直接从内存中缓存缓存图片,若想调整行为需要保证每次调用行为一致
//硬盘缓存策略-枚举值:默认开启
.diskCacheStrategy(DiskCacheStrategy.NONE) //禁用硬盘的缓存 让其处于null状态,跳过磁盘缓存
.diskCacheStrategy(DiskCacheStrategy.SOURCE) //仅仅只缓存原来的全分辨率尺寸图像
.diskCacheStrategy(DiskCacheStrategy.RESULT) //仅仅只缓存资源最终的加载图像(降低压缩分辨率后的图片)
.diskCacheStrategy(DiskCacheStrategy.ALL) //缓存所有版本的图片
.priority(Priority.HIGH)//优先级 先处理优先级高的图片信息 ,但不保证所有图片按照优先级顺序进行加载
.into(imageview); //显示到指定的ImageView
}
4.3、with()解析: 基础目的:获取RequestManager对象(管理Request请求)
根据当前的上下文和组建选择不同的with构造方法,不同的参数对于图片加载的生命周期产生不同的影响,将图片加载的生命周期和组件相挂钩。
以context 为例:
public static RequestManager with(Context context) {
//用于产生requestManager 图片请求管理
RequestManagerRetriever 生产requestManager
RequestManagerRetrieverretriever = RequestManagerRetriever.get();
return retriever.get(context);
}
//通过RequestManagerRetriever 这个生产Request类来处理,同时Glide绑定了组件的生命周期
RequestManager的创建流程:
public RequestManager get(Context context) {
if (context == null) {
throw new IllegalArgumentException("You cannot start a load on a null Context");
}
//当前Context 是否是在主线程,context是否是application的实例
else if (Util.isOnMainThread() && !(context instanceof Application)) {
if (context instanceof FragmentActivity) {
return get((FragmentActivity) context);
} else if (context instanceof Activity) {
return get((Activity) context);
} else if (context instanceof ContextWrapper) {
return get(((ContextWrapper) context).getBaseContext());
}
}
//返回一个单例模式的ApplicationManager
return getApplicationManager(context);
}
//双重锁检查机制的单例模型
获取唯一个RequestManager,进行图片请求的处理
private RequestManager getApplicationManager(Context context) {
if (applicationManager == null) {
synchronized (this) {
if (applicationManager == null) {
applicationManager = new RequestManager(context.getApplicationContext(),
new ApplicationLifecycle(), new EmptyRequestManagerTreeNode());
}
}
}
return applicationManager;
}
RequestManagerRetriever的 get()解析:
a、传入application 类型的Context
当Glide传入的是整个程序的生命周期的时候,就不需要进行过多处理
b、传入非application 类型的Context (activity、fragment)
public RequestManager get(FragmentActivity activity) {
//是否是在后台线程,只有在非UI线程才进else
if (Util.isOnBackgroundThread()) {
return get(activity.getApplicationContext());
} else {
assertNotDestroyed(activity);
FragmentManager fm = activity.getSupportFragmentManager();
return supportFragmentGet(activity, fm);
}
}
supportFragmentGet():
RequestManager supportFragmentGet(Context context, FragmentManager fm) {
//Fragment 添加到Activity有两种方式,A有Ui的Fragment B没有Ui界面的Fragment
//这里使用第二种方式,通过RequestManagerFragment来监听Activity的生命周期来完成绑定生命周期,图片加载选择的过程
ps:Glide无法直接监听Activity的生命周期
SupportRequestManagerFragment current
= getSupportRequestManagerFragment(fm);
//创建RequestManager实例,完成Glide对象的构造,通过RequestManager就可以控制整个界面的生命周期的监听,通过监听进行图片的相应操作
RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
requestManager = new RequestManager(context,
current.getLifecycle(), current.getRequestManagerTreeNode());
current.setRequestManager(requestManager);
}
return requestManager;
}
setRequestManager():将RequestManagerFragment这个空界面的Fragment和RequestManager进行绑定;
目的:监听Activity生命周期,管理图片加载的整个流程 绑定到Activity一起操作
public void setRequestManager(RequestManager requestManager) {
this.requestManager = requestManager;
}
ps:一个RequestManager 对应 一个RequestManagerFragment,一一对应关系
思考:前文提到的 SupportRequestManagerFragment 空的Fragment 是如何和RequestManager建立生命周期关系的呢?
答:在RequestManagerFragment中含有ActivityFragmentLifecycle函数,用于管理组建生命周期的。RequestManager实际上在RequestManagerFragment中注册了一些回调接口,通过接口监听Activity 或是Fragment的生命周期
例如:
@Override
public void onStart() {
super.onStart();
lifecycle.onStart();
//说明RequestManagerFragment 这个无Ui的Fragment是通过LifeCycle进行生命周期的管理
}
根据不同的生命周期进行相应的处理
4.4、load()解析: 初始化操作 获取DrawableTypeRequest对象
思考:为什么Glide会有一大堆重载方法?
答:因为Glide支持多种格式的图片来源,依托于此Glide加载也就需要不同的类型
public DrawableTypeRequest<String> load(String string) {
return (DrawableTypeRequest<String>)
fromString().load(string);
}
DrawableTypeRequest() :表示Glide中所有加载图片的Request请求
class DrawableTypeRequest<ModelType> extends DrawableRequestBuilder<ModelType> implements DownloadOptions
能想象到DrawableTypeRequest 应该就是通过Builder()内部类的构建者模式进行构建初始化的。
DrawableRequestBuilder:对Glide参数初始化的配置工作
而DrawableRequestBuilder又extends继承自 GenericRequestBuilder
GenericRequestBuilder:Glide所有配置参数基本的最终类:
public class DrawableRequestBuilder<ModelType>
extends GenericRequestBuilder<ModelType, ImageVideoWrapper, GifBitmapWrapper, GlideDrawable>
implements BitmapOptions, DrawableOptions
ps:DrawableRequestBuilder可以通过链式调用配置参数
在GenericRequestBuilder中有一个很重要的成员变量:
protected final RequestTracker requestTracker; //负责跟踪整个图片请求的周期
fromString():传入String的Class对象作为参数
public DrawableTypeRequest<String> fromString() {
return loadGeneric(String.class);
}
loadGeneric():
private <T> DrawableTypeRequest<T>
loadGeneric(Class<T> modelClass) {
//创建两个ModelLoader对象,通过数据来源,ML将来源加载成原始数据
ModelLoader<T, InputStream> streamModelLoader
= Glide.buildStreamModelLoader(modelClass, context);
ModelLoader<T, ParcelFileDescriptor>fileDescriptorModelLoader
= Glide.buildFileDescriptorModelLoader(modelClass, context);
if (modelClass != null && streamModelLoader == null && fileDescriptorModelLoader == null) {
throw new IllegalArgumentException(...);
}
//创建DrawableTypeRequest 对象
return optionsApplier.apply(
new DrawableTypeRequest<T>(modelClass, streamModelLoader, fileDescriptorModelLoader, context,
glide, requestTracker, lifecycle, optionsApplier));
}
DrawableTypeRequest():相对常用的方法,通过返回不同的TypeRequest来选择需要的方法
//强制指定加载静态图片
public BitmapTypeRequest<ModelType> asBitmap() {
return optionsApplier.apply(new BitmapTypeRequest<ModelType>
(this, streamModelLoader,
fileDescriptorModelLoader, optionsApplier));
}
//强制指定加载动态图片
public GifTypeRequest<ModelType> asGif() {
return optionsApplier.apply(new GifTypeRequest<ModelType> (this, streamModelLoader, optionsApplier));
}
4.5、into()解析:——在主线程中调用的更新UI操作
public Target<TranscodeType> into(ImageView view) {
//1、是否在主线程中操作,非主线程抛出异常
Util.assertMainThread();
if (view == null) {
throw new IllegalArgumentException("You must pass in a non null View");
}
//2、判断类型是否进行哪类图片处理
if (!isTransformationSet && view.getScaleType() != null) {
switch (view.getScaleType()) {
case CENTER_CROP:
applyCenterCrop();
break;
case FIT_CENTER:
case FIT_START:
case FIT_END:
applyFitCenter();
break;
//$CASES-OMITTED$
default:
// Do nothing.
}
}
//3、创建一个Target对象,图片所要显示的控件
return into(glide.buildImageViewTarget(view, transcodeClass));
}
applyCenterCrop():
————>centerCrop()————>centerCrop():
public DrawableRequestBuilder<ModelType> centerCrop() {
return transform(glide.getDrawableCenterCrop());
}
transform()://图片转换,对transformation进行赋值操作
public GenericRequestBuilder<ModelType, DataType,
ResourceType, TranscodeType> transform(
Transformation<ResourceType>... transformations) {
isTransformationSet = true;
if (transformations.length == 1) {
transformation = transformations[0];
} else {
transformation = new MultiTransformation<ResourceType>(transformations);
}
return this;
}
glide.buildImageViewTarget() 中Target接口 :
//Glide能绑定生命周期的原因:
interface Target<R> extends LifecycleListener
接口内含有onStart()、onStop()、onDestory()函数
在进行.with()操作的时候,会传入context or Activity or Fragment进行相应生命周期的绑定操作 ,这时候就是通过LifecycleListener进行组件的监听
<R> Target<R> buildImageViewTarget(ImageView imageView, Class<R> transcodedClass) {
//通过imageViewTargetFactory工厂构建Target
return imageViewTargetFactory.buildTarget(imageView, transcodedClass);
}
//通过传入class对象判断Glide需要加载哪类图片,buildTarget()就创建哪类Target
public <Z> Target<Z> buildTarget(ImageView view, Class<Z> clazz) {
if (GlideDrawable.class.isAssignableFrom(clazz)) {
//未调用asBitmap()方法就会默认采取GlideDrawableImageViewTarget()方法
return (Target<Z>) new GlideDrawableImageViewTarget(view);
} else if (Bitmap.class.equals(clazz)) {
//调用了asBitmap()
return (Target<Z>) new BitmapImageViewTarget(view);
} else if (Drawable.class.isAssignableFrom(clazz)) {
//使用较少
return (Target<Z>) new DrawableImageViewTarget(view);
} else {
throw new IllegalArgumentException("Unhandled class: " + clazz
+ ", try .as*(Class).transcode(ResourceTranscoder)");
}
}
into():
3、新建一个Request绑定到Target上
4、发送Request 交给RequestTracker进行相应的处理
public <Y extends Target<TranscodeType>> Y into(Y target) {
//是否在主线程 (更新UI必须在主线程)
Util.assertMainThread();
…
1、对旧的绑定的Target对象进行清除
//获取当然Target对象所绑定的旧的Request对象
Request previous = target.getRequest();
if (previous != null) {
//清理操作
previous.clear();
//取消当前Request请求避免图片错位
requestTracker.removeRequest(previous);
//在该实现类中将成员变量赋值为null
ps:并调用REQUEST_POOL.offer(this);当前一个Request不用的时候会被放入请求池以备复用
previous.recycle();
}
2、创建一个加载图片的Request
ps:list图片错位的问题?
解决:给View绑定setTag(),将view和图片进行绑定
Request request = buildRequest(target);
//将图片Request请求和ImageView绑定
target.setRequest(request);
lifecycle.addListener(target);
//3、将Request发送给RequestTracker执行request请求
requestTracker.runRequest(request);
return target;
}
如何buildRequest?
buildRequest():————>buildRequestRecursive()
...
obtainRequest()————>GenericRequest.obtain():实际创建Request()
GenericRequest.obtain():
//从线程池中获取一个请求,如果有可以复用的就复用一个请求,如果没有就创建一个新的
...
GenericRequest<A, T, Z, R> request =
(GenericRequest<A, T, Z, R>) REQUEST_POOL.poll();
if (request == null) {
request = new GenericRequest<A, T, Z, R>();
}
request.init();//初始化操作
return request;
requestTracker.runRequest:
public void runRequest(Request request) {
//将当前request加入到set<Request> 集合当中
requests.add(request);
//对当前状态进行判断,当前是有Request请求进入else ,将当前Request加入pending悬挂中的集合中
if (!isPaused) {
request.begin();
} else {
pendingRequests.add(request);
}
}
request.begin():{
…
//判断前面是否调用了overrideWidth 限定宽高的方法,直接执行onSizeReady()
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
onSizeReady(overrideWidth, overrideHeight);
} else {
//没有限定款高通过getSize()计算宽高
target.getSize(this);
}
//当前状态是否已经完成,图片是否不是失败状态进行判断
if (!isComplete() && !isFailed() && canNotifyStatusChanged()) {
//设定图片(获取占位图)
target.onLoadStarted(getPlaceholderDrawable());
...
}
target.getSize——>
public void getSize(SizeReadyCallback cb) {
sizeDeterminer.getSize(cb);
}
getSize():内部根据ImageView宽高进行再次计算后再执行onSizeReady()
public void getSize(SizeReadyCallback cb) {
//1、获取宽高
int currentWidth = getViewWidthOrParam();
int currentHeight = getViewHeightOrParam();
//3、当View绘制完时候就会进入onSizeReady()方法
if (isSizeValid(currentWidth) && isSizeValid(currentHeight)) {
cb.onSizeReady(currentWidth, currentHeight);
}
…
//2、当前View没有被测量完,会被添加到viewTreeObserver观察者中
final ViewTreeObserver observer = view.getViewTreeObserver();
layoutListener = new SizeDeterminerLayoutListener(this);
observer.addOnPreDrawListener(layoutListener);
1、通过LoadProvider()方法获取ModelLoader和Transcoder
2、根据ModelLoader获取DataFetcher
3、engine.load 进行实际图片加载
ModelLoader:从数据源中获取原始数据,一般是输入流inputStream,在Glide中被封装成Data
DataFetcher:将Data原始数据转换成能直接用的不同形式的图片数据类型
ResourceTranscoder:将原始数据解码,将io输入流解码成bitmap的解码工具对象
Resource:解码后的资源
onSizeReady(int width, int height) {:
…
ModelLoader<A, T> modelLoader
= loadProvider.getModelLoader();
//通过loadProvider接口获取到dataFetcher、loadProvider、transcoder
final DataFetcher<T> dataFetcher = modelLoader.getResourceFetcher(model, width, height);
…
loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder,
priority, isMemoryCacheable, diskCacheStrategy, this);
}
LoadProvider():GenericRequest的成员变量
public interface LoadProvider<A, T, Z, R> extends DataLoadProvider<T, Z> {
ModelLoader<A, T> getModelLoader();
ResourceTranscoder<Z, R> getTranscoder();
LoadProvider初始化:在GenericRequest的实现类
DrawableTypeRequest(...) {
super(context, modelClass,
buildProvider(glide, streamModelLoader, fileDescriptorModelLoader,)
...
}
buildProvider的返回值就是LoadProvider,FixedLoadProvider是LoadProvider的实现类:获取原始图片,进行编解码、转码等功能
private static <A, Z, R> FixedLoadProvider<A, ImageVideoWrapper, Z, R> buildProvider(Glide glide,
ModelLoader<A, InputStream> streamModelLoader,
ModelLoader<A, ParcelFileDescriptor> fileDescriptorModelLoader...) {
//判断来自不同数据的Loader
if (streamModelLoader == null && fileDescriptorModelLoader == null) {
return null;
}
//ResourceTranscoder是否为空,为null则通过Glide创建新的Transcoder
ps:对原始数据进行解码
if (transcoder == null) {
transcoder = glide.buildTranscoder(resourceClass, transcodedClass);
}
//DataLoadProvider对图片进行编解码的接口,实现类中LoadProvider
DataLoadProvider<ImageVideoWrapper, Z> dataLoadProvider
= glide.buildDataProvider(ImageVideoWrapper.class,resourceClass);
//封装了两个ModelLoader
ImageVideoModelLoader<A> modelLoader = new
ImageVideoModelLoader<A>(streamModelLoader,fileDescriptorModelLoader);
return new FixedLoadProvider<A, ImageVideoWrapper, Z, R>
(modelLoader, transcoder, dataLoadProvider);
}
DataLoadProvider:负责编解码
Data:从数据源获取的数据
Resource:解码后的资源
解码:Data————>Resource 资源
编码:将Data、Resource———>持久化到本地的过程 用于实现Glide的磁盘缓存功能
engine.load():
engine:负责图片加载,管理正在使用和处于缓存的图片类
LoadStatus load(){
…
//加载图片的唯一标识id,比如加载网络图片的话就是一个网络图片的url地址
final String id = fetcher.getId();
//传入唯一标识id ,其他很多参数依然可以决定EngineKey的值,构建Glide缓存中的一个key值
EngineKey key = keyFactory.buildKey(id,…);
...
//从缓存中获取图片
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
//若缓存图片是空的则调用loadFromActiveResources
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
…
//若两者都没有获取到就自己开启一个runnable 从磁盘或是网络进行图片的获取
EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
jobs.put(key, engineJob);
engineJob.addCallback(cb);
engineJob.start(runnable);
}
GLide中的内存缓存策略
GLide 构建内存缓存的组成部分:cache、active
A、内存缓存读取操作原理
1、 LruCache算法:近期最少使用算法,将最近使用的对象的强引用存储在LinkHashMap上;并且把最近最少使用的对象,在缓存池达到预设值之前从内存中移除
2、弱引用缓存机制
Glide中的内存缓存对象:Engine类中的 Load()函数
MemoryCache——loadFromCache(EngineKey,boolean isMemoryCacheable)
cached :最近使用过的而当前不在使用的EngineResource,LoadFromCache内部使用linkHashMap当内存达到阀值会通过LruCache算法进行清除操作
loadFromCache():1、从内存缓存MemoryCache中获取图片
private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
//配置Glide 的时候使用的isMemoryCacheable,跳过内存缓存操作skipMemoryCache(true)
默认情况下为true 的时候开启缓存模式
if (!isMemoryCacheable) {
return null;
}
//获取实际缓存对象
EngineResource<?> cached = getEngineResourceFromCache(key);
//ps:2、在该方法中会使用缓存的key值 从cache中获取值并调用remove函数把这个图片从MemoryCahce缓存中移除掉
//Resource cached = this.cache.remove(key);
if (cached != null) {
cached.acquire();
//写入一个弱引用的 缓存
activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue()));
}
return cached;
}
ps:使用弱引用的好处,使得该部分缓存不会被Lru算法给回收掉资源
loadFromActiveResources():3、调用该方法从正在使用的部分中继续获取图片
ActiveResource——loadFromActiveResources(EngineKey,boolean isMemoryCacheable)——HashMap<key,WeakReference<EngineResource>>
active:保存当前正在使用的EngineResource对象
ps: 会以EngineKey为键,以EngineResource的弱引用为值
HashMap的Get()、 Put()函数对应着 缓存的读和取 的操作
//4、若前两步都没有获取到图片资源,就创建一个Engine runnbale对象加载,从磁盘或是网络获取图片
EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);
DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation,
transcoder, diskCacheProvider, diskCacheStrategy, priority);
EngineRunnablerunnable = new EngineRunnable(engineJob, decodeJob, priority);
jobs.put(key, engineJob);
B、内存缓存写入操作原理
if (cached != null) {
cb.onResourceReady(cached);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return null;
}
cb:callback回调
onResourceReady :ResourceCallback的具体实现
在onResourceReady()中:定义一个handler发送message,将执行逻辑切回到主线程中操作
public void onResourceReady(final Resource<?> resource) {
this.resource = resource;
MAIN_THREAD_HANDLER.obtainMessage(
MSG_COMPLETE, this).sendToTarget();
}
其中MAIN_THREAD_HANDLER是MainThreadCallback回调定义:
private static final Handler MAIN_THREAD_HANDLER =
new Handler(Looper.getMainLooper(),
new MainThreadCallback());
MainThreadCallback():接受Message信息并处理的callback回调,通过名字可以知道该消息是在主线程进行结果回调
private static class MainThreadCallbackimplements Handler.Callback {
@Override
public boolean handleMessage(Message message) {
if (MSG_COMPLETE == message.what || MSG_EXCEPTION == message.what) {
EngineJob job = (EngineJob) message.obj;
if (MSG_COMPLETE == message.what) {
job.handleResultOnMainThread();
} else {
job.handleExceptionOnMainThread();
}
...
}
job.handleResultOnMainThread():
private void handleResultOnMainThread() {
//1、如果任务取消则回收资源 recycle
if (isCancelled) {
resource.recycle();
return;
//2、若callback集合为null 空 则抛出异常
} else if (cbs.isEmpty()) {
throw new IllegalStateException("Received a resource without any callbacks to notify");
}
//3、通过工厂累创建包含图片资源的engineResource对象
engineResource = engineResourceFactory.build(resource, isCacheable);
...
//3、将对象回调到onEngineJobComplete()当中
engineResource.acquire();
//4、acquire():记录图片被引用的次数,同时在该函数结尾通过 ++this.acquire 来进行图片次数的+1 操作;当acquired >0 表示有图片正在使用中,也就是应该把图片放入到activeResource 这个弱引用缓存中
listener.onEngineJobComplete(key, engineResource);
for (ResourceCallback cb : cbs) {
if (!isInIgnoredCallbacks(cb)) {
engineResource.acquire();
cb.onResourceReady(engineResource);
}
}
//5、release() 当图片等于0的时候表示图片没有在使用了就调用onResourceReleased释放资源操作,该实现在Engine()函数中
engineResource.release();
}
onEngineJobComplete():
public void onEngineJobComplete(Key key, EngineResource<?> resource) {
Util.assertMainThread();
if (resource != null) {
resource.setResourceListener(key, this);
if (resource.isCacheable()) {
//最终调用activeResources.put()这个函数进行了缓存的写入操作
ps:这里写入的是弱引用的缓存
activeResources.put(key, new ResourceWeakReference(key, resource, getReferenceQueue()));
}
}
// TODO: should this check that the engine job is still current?
jobs.remove(key);
}
onResourceReleased():
public void onResourceReleased(Key cacheKey, EngineResource resource) {
Util.assertMainThread();
//1、把整个缓存图片从activeResources从缓存图片中删除
activeResources.remove(cacheKey);
if (resource.isCacheable()) {
//2、通过put()加入到LruCache算法中,
ps:实现正在使用的图片通过弱引用进行缓存,而不在使用的利用LruCache进行缓存
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
磁盘缓存读写 待补充
五、Glide常用
5.1 、Target函数的使用- Glide获取 Bitmap资源本身
Target其实就是整个图片的加载的生命周期,通过它在图片加载完成之后获取Bitmap
以 SimpleTarget 为例:
private SimpleTarget<Bitmap> mSimpleTarget= new SimleTarget<Bitmap> (可添加宽*高 尺寸限定){
@Override
public void onResourceReady( Bitmap resource,
GlideAnimation< ? super Bitmap>animation) {
ImageView.setImageBitmap(resource);
}
};
private void loadImageSimpleTarget(){
Glide .with (1 ). load(url ) . asBitmap() . into ( mSimpleTarget );
}
在这里如果SimpletTarget 使用匿名内部类的方式创建 SimpleTarget 的对象,这样会增大该对象在 Glide 完成图片请求之前就被回收的可能性。
(1):这里如果传入的是Target ,可能独立于with的生命周期之外,所以最好给with里传入参数context .getApplicationContext(),让Glide持有整个app的生命周期。
5.2、ViewTarget函数的使用
在自定义View的时候,Glide有时候并不支持加载图片到自定义View中的时候,这时候就需要ViewTarget
public void loadImageTarget(Context context){
CustomView mCustomView = (CustomView) findViewById(R.id.custom_view); ViewTarget viewTarget =
new ViewTarget<CustomView,GlideDrawable>( mCustomView) {
@Override
public void onResourceReady(GlideDrawable resource,
GlideAnimation<? super GlideDrawable> glideAnimation) { this.view.setImage(resource);
}
};
Glide.with(context) .load(mUrl) .into(viewTarget);
}
onResourceReady回调方法中使用了自定义view自己的方法设置图片,可以看到在创建ViewTarget的时候传入了CustomView 对象
5.3、Transformations函数的使用
如果需要对图片进行图片切圆角、灰阶处理 等功能 就需要 通过 Transformations来操作bitmap 。通过修改尺寸、范围、颜色、像素、位置等达到需求。
ps:若是对图片进行常规的bitmap转换的话,可以使用抽象类 BitmapTransformation 进行
对图片切圆角操作 :getId()是图片的唯一标识符必须唯一
public class RoundTransformation extends BitmapTransformation {
private float radius = 0f;
public RoundTransformation(Context context) {
this(context, 4);
}
public RoundTransformation(Context context, int px) {
super(context);
this.radius = px;
}
@Override
protected Bitmap transform(BitmapPool pool, Bitmap toTransform,
int outWidth, int outHeight) {
return roundCrop(pool, toTransform);
}
private Bitmap roundCrop(BitmapPool pool, Bitmap source) {
if (source == null)
return null;
Bitmap result = pool.get(source.getWidth(),
source.getHeight(), Bitmap.Config.ARGB_8888);
if (result == null) {
result = Bitmap.createBitmap(source.getWidth(),
source.getHeight(), Bitmap.Config.ARGB_8888);
}
Canvas canvas = new Canvas(result);
Paint paint = new Paint();
paint.setShader(new BitmapShader(source, BitmapShader.TileMode.CLAMP,
BitmapShader.TileMode.CLAMP));
paint.setAntiAlias(true);
RectF rectF = new RectF(0f, 0f, source.getWidth(),
source.getHeight()); canvas.drawRoundRect(rectF, radius,radius, paint);
return result;
}
@Override
public String getId() {
return getClass().getName() + Math.round(radius);
}
}
下面是transform的调用:
Glide.with(context)
.load(mUrl)
.transform(new RoundTransformation(context , 20))
//.bitmapTransform( new RoundTransformation(context , 20) ) .into(mImageView);
ps:若需要同时执行多个transformation的话 ,不能再像这样通过链式调用的形式多次调用.transform() 或是.bitmapTransform() 方法。上面的transform会被最后一个覆盖掉。
这时候可以这样做:
Glide.with(context)
.load(mUrl)
.transform(new RoundTransformation(context , 20)
, new RotateTransformation(context , 90f))
.into(mImageView);
对图片旋转操作
public class RotateTransformationextends 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 getClass().getName() + Math.round(rotateRotationAngle);
}
}
ps:这里需要注意一点 .centerCrop() 和 .fitCenter() 也都是 Transformation 所以也是遵循同时使用多个 Transformation 的规则的,即:当你使用了自定义转换后你就不能使用 .centerCrop() 或 .fitCenter() 了。
这里有一个 GLide Transformations 的库,它提供了很多 Transformation 的实现,非常值得去看,不必重复造轮子对吧!
5.4、Animate动画函数的使用
图片间切换的平滑过渡 是非常重要的!!所以Glide又一个标准动画去柔化Ui中的改变,但是如果是设置自己的动画的话:
一个小例子:
<set
xmlns:android="http://schemas.android.com/apk/res/android" android:fillAfter="true”>
<scale
android:duration="@android:integer/
config_longAnimTime”
android:fromXScale="0.1”
android:fromYScale="0.1”
android:pivotX="50%”
android:pivotY="50%”
android:toXScale="1”
android:toYScale="1"/>
</set>
XML 动画缩放动画,图片刚开始小的,然后逐渐增大到原尺寸。我们现在要应用到 Glide 加载图片中去,调用 .animate() 方法传入 XML 动画的 id 即可。
Glide.with(context)
.load(mUrl)
.transform(new RoundTransformation(this , 20))
.animate( R.anim.zoom_in )
.into(mImageView);
animate在Target中的使用:
ViewPropertyAnimation.Animatoranimator
= new ViewPropertyAnimation.Animator() {
@Override
public void animate(View view) {
view.setAlpha( 0f );
ObjectAnimator fadeAnim
= ObjectAnimator.ofFloat( view, "alpha", 0f, 1f );
fadeAnim.setDuration( 2500 );
fadeAnim.start();
}
};
Glide.with(context)
.load(mUrl)
.animate( animator )
.into(viewTarget);
5.5、Modules函数的使用
Glide 的Module 是一个可以全局改变Glide 的行为的东西。为了实现需求需要去实现 interface GlideModule 来实现功能
public class ExampleModule implements GlideModule{
@Override
public void applyOptions(Context context, GlideBuilder builder) {
// todo
}
@Override
public void registerComponents(Context context, Glide glide) {
// todo
} }
主要使用的是 applyOptions(Context context, GlideBuilder builder) , 我们自己的需要重新定义的代码写在该方法里就可以了。然后我们还需要去 AndroidManifest.xml 中使用 meta 声明我们上面实现的 Module:
<application>
<meta-data android:name
="com.mrtrying.demoglide.module.ExampleModule" android:value="GlideModule" />
... </application>
到这里我们就完成了 ExampleModule 的声明,Glide 将会在工作是使用我们所定义的 Module
TIPS
-
我们需要将 android:name 属性改成 包名+类名 的形式,这样的引用才是正确的。如果你想删掉 Glide Module,只需要删除在 AndroidManifest.xml 中的声明就可以了。Java 类可以保存,说不定以后会用呢。如果它没有在 AndroidManifest.xml 中被引用,那它不会被加载或被使用。
-
定制 module 的话 Glide 会有这样一个优点:你可以同时声明多个 Glide module。Glide 将会(没有特定顺序)得到所有的声明 module。因为你当前不能定义顺序,请确保定制不会引起冲突!
applyOptions(Context context, GlideBuilder builder) 中有两个参数, 我们通过使用 GlideBuilder 来实现我们的需求。先看看 GlideBuilder 中可用的方法
- .setMemoryCache(MemoryCache memoryCache)
- .setBitmapPool(BitmapPool bitmapPool)
- .setDiskCache(DiskCache.Factory diskCacheFactory)
- .setDiskCacheService(ExecutorService service)
- .setResizeService(ExecutorService service)
- .setDecodeFormat(DecodeFormat decodeFormat)
可以看到,这个 GlideBuilder 对象给你访问了 Glide 重要的核心组件。接下来我们就要试着去使用这些方法
增加 Glide 的图片质量
在 Android 中有两个主要的方法对图片进行解码:ARGB_8888 和 RGB_565 。前者为每个像素使用4个字节,后者每个像素仅使用2个字节。ARGB_8888 的有时就是图像质量更高以及能储存一个 alpha 通道。 Picasso 使用的就是 ARGB_8888 , Glide 默认使用低质量的 RGB_565 ,但是现在你就可以使用 Glide module 来改变图片解码规则。就象这样
public class QualityModule implements GlideModule{
@Override
public void applyOptions(Context context , GlideBuilder builder){
builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888);
}
@Override
public void registerComponents(Context context , Glide glide){
// nothing to do here
}
}
这样我们就简单的增加了 Glide 的图片质量。
往往我们还会遇到一些情况,希望 Glide 可以使用我们自己的网络框架,我们就需要做一些事情来实现这个需求了。Glide 的开发者不强制设置网络库给你,所以Glide可以说和 HTTPS 无关。理论上,它可以与任何的网络库实现,只要覆盖了基本的网络能力就行。同样是需要实现 Glide 的 ModuleLoader 的接口,为了让我们更加易用,Glide 为 OkHttp 和 Volley 两个网络库提供了实现。
假设我要集成 OkHttp 作为 Glide 的网络库,我可以手动实现一个 GlideModule 也可以在 build.gradle 中添加依赖:
dependencies{
//...
// Glide
compile 'com.github.bumptech.glide:glide:3.7.0'
// Glide's OkHttp Integration
compile 'com.github.bumptech.glide:okhttp-integration:1.4.0@aar'
compile 'com.squareup.okhttp:okhttp:3.2.0'
}
Gradle 会自动合并必要的 GlideModule 到你的 AndroidManifest.xml , Glide 会认可在 manifest 中存在,然后使用 OkHttp 做到的所有网络连接!!
作者:MrTrying
來源:简书
以上一小节选取了 该文章的内容多谢分享!
六、Gilde相关知识补充-bitmap &oom & 优化bitmap
4.6.1 bitmap 导致OOM
a、在使用list类View
一、加载大量View组件又没有合理的处理缓存的情况下,大量加载bitmap很容易造成OOM。
解决方法:
(1)三级缓存
(2)设置listView的监听事件,当滑动的时候不进行图片的加载,当停止滑动的时候再加载
二、图片分辨率越来越高,消耗的内存越来越大
注意:图片Bitmap所占用的内存 = 图片长度 * 图片宽度 * 一个像素点占用的字节数
三、VM值上限dalvik.vm.heapgrowthlimit
限定了每个app 可用的最大内存,如果超过这个值就会出现OOM现象
4.6.2 bitmap的4种优化策略
一、对图片质量进行压缩
private Bitmap compressImage(Bitmap image) {
//1、创建字节输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
//2、Bitmap.CompressFormat进行压缩(类型,值越小代表压缩比例越大,100表示不压缩,压缩好的图片放在字节输出流)
image.compress(Bitmap.CompressFormat.JPEG
, 100, baos);
int options = 100;
//3、循环判断,压缩好的图片大小是否大于100kb,大于就进一步压缩
while (baos.toByteArray().length / 1024 > 100) {
//3.1、清空输出流
baos.reset();
//3.2、再次压缩
image.compress(Bitmap.CompressFormat.JPEG, options, baos);
//3.3、每次减少10options 数值
options -= 10;
}
//4、将压缩好的数据放入到字节输入流中
ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());
//5、通过BitmapFactory.decodeStream完成bitmap的重建
Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);
return bitmap;
}
二、对图片按比例进行压缩
/**
* 图片比例压缩
*/
private Bitmap comBitmapInSize(Bitmap image) {
//1、创建字节数组输入输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ByteArrayInputStream isBm;
//2、创建BitmapFactory的Options内部类对象,通过该对象可以对bimtap进行宽高、内存等的控制
BitmapFactory.Options newOpts = new BitmapFactory.Options();
//3、解码bitmap只返回它的宽高数值,而不必为他申请内存,通过设为true可以节省内存空间
newOpts.inJustDecodeBounds = true;
Bitmap bitmap;
int w = newOpts.outWidth;
int h = newOpts.outHeight;
float hh = 1290f; //设置高度为1280f
float ww = 720f; //设置宽度为720f
//4、创建be缩放比例
int be = 1;
if (w > h && w > ww) {
//4.1、如果宽大于高的话,就根据宽的大小进行缩放
be = (int) (newOpts.outWidth / ww);
} else if (w < h && h > hh) {
//4.2、如果宽小于高的话,就根据高的大小进行缩放
be = (int) (newOpts.outHeight / hh);
}
if (be <= 0)
be = 1;
//5、将计算好的比例数字be 赋值给Options中的缩放比例变量inSamplesize
newOpts.inSampleSize = be;
//6、将Options的inJustDecodeBounds设置为false表示需要将图片加载到内存中
newOpts.inJustDecodeBounds = false;
isBm = new ByteArrayInputStream(baos.toByteArray());
//7、重建bitmap
bitmap = BitmapFactory.decodeStream(isBm, null, newOpts);
return bitmap;
}
三、关于bitmap的recycle方法(含有争议话题)
/**
* Free the native object associated with this bitmap, and clear the
* reference to the pixel data. This will not free the pixel data synchronously;
* it simply allows it to be garbage collected if there are no other references.
* The bitmap is marked as "dead", meaning it will throw an exception if
* getPixels() or setPixels() is called, and will draw nothing. This operation
* cannot be reversed, so it should only be called if you are sure there are no
* further uses for the bitmap. This is an advanced call, and normally need
* not be called, since the normal GC process will free up this memory when
* there are no more references to this bitmap.
*/
/**
* 当释放这个bitmap相关联的native对象的时候,它会清除像素数据。ps:不会同步的释放像素数据,只是根据在没有其他引用的情况下收集 该GC垃圾,同时将状态标记为dead状态。这时候意味着调用 setPixels() 或是getPixels()时候都会抛出异常。而不回去绘制任务东西。通常情况下不需要手动的调用,当其没有引用这个bitmap的时候,正常的垃圾回收进程会释放掉该部分内存。
*
*/
public void recycle() {
...
}
注解: 手动的调用recycle()本身可能并没有被释放资源。所以硬要调用recycle的时候要将bitmap设置为null,好让GC和垃圾回收器回收这部分的对象。
四、捕获异常
针对OOM 的区域进行 异常捕获
public void handleBitmapCrash() {
Bitmap bitmap = null;
String path = "xx/xx/xx";
try {
//实例化bitmap
bitmap = BitmapFactory.decodeFile(path);
} catch (OutOfMemoryError e) {
e.printStackTrace();
}
}
七、Gilde相关知识补充-三级缓存 /LruChache算法
Android的缓存策略-三级缓存
内存 - 本地 - 网络
意义:
第一次从网络获取 ,之后将获取的图片保存到本地和内存各一份,当程序再次用到该图片的时候首先会从内存中判断是否有缓存,有的话直接从内存中获取,没有的话再从本地SD卡中进行获取。如果内存和本地均没有的话,再次从网络上获取。
思考:内存缓存是如何实现的呢?
缓存主要是通过添加 、获取、删除 进行操作的。
不论内存缓存还是硬盘缓存,缓存的大小都是固定的,有个max限制。当该部分缓存用满之后其他缓存就无法再添加,所以需要进行删除无用缓存的操作。这时候就需要一个删除旧缓存的算法,也就说常说的LRU(Least Recently Used)近期最少使用缓存算法
LruChache算法
LRU核心
当缓存已满的时候会优先淘汰掉最近使用最少的缓存对象
整个Lru算法的核心是LinkedHashMap,其继承自HashMap,其构造函数中会有一个boolean类型的值,尤其控制插入的顺序,a是Lru顺序,b是正常顺序
public class LruCache<T, Y> {
private final LinkedHashMap<T, Y> cache= new LinkedHashMap<T, Y>(100, 0.75f(负载因子), true(当为true时候说明整个顺序是通过Lru算法顺序删除元素的));
int size : 当前缓存的大小
int maxSize : 当前缓存的最大值
int putCount : put方法调用的次数
int createCount : create方法调用的次数
int evictionCount : 当需要淘汰一些缓存变量时候的计数器
int hitCount : 缓存对象使用到hitCount +1 计数
int missCount : 缓存未命中 计数 +1
Get():
传入key值 获取相应的item,若是无缓存会创建并返回相应的item,相应的item会被移动到队列的尾部。
public final V get(K key){
if(key == null ) {
throw new NullPointerException(“key==null"):
}
V mapValue;
//利用LinkedHashMap的get方法获取相应的value
synchronized(this){
mapValue = map.get(key);
if(mapValue !=null){
//获取到缓存 hitCount+1 并返回Value值
hitCount++;
return mapValue;
}
missCount++ ;
}
//未命中,调用create()创建一个对象
V createdValue = create(key);
if(createdValue == null) {
return null;
}
synchronized (this ){
createCount++;
//覆盖原有Value值并添加到mapValue中
mapValue = map.put(key , createdValue);
if(mapValue !=null){
//如果该值不为空,将重新创建好的cratedValue又覆盖成原来的mapValue,逆推上一步操作
map.put(key,mapValue); //插入的新的对象会存储到列表的尾端
}else{
//加入新创建的对象需要重新创建缓存size大小
size += safeSizeOf(key ,createValue);
}
if(mapValue != null){
entryRemoved(false, key,createdValue,mapValue);
return mapValue;
}else {
//新加入对象都会调用trimToSize(),来查看对象是否需要被回收。根据传入的缓存的最大容量调整缓存的大小。传入 -1 表示清空所有缓存对象
trimToSize(maxSize);
return createdValue;
}
}
Put():
public final V put (K key , V value){
if(key == null || value == null) {
throw new NullPointerException (“key ==null ||value ==null ")
}
V previos ;
synchronized (this ){
//调用put方法每次计数+1
putCount ++;
// 每次put都会造成整个缓存大小增大,所以需要自增当前缓存
size += safeSizeOf (key ,value );
previous= map .put(key , value );
//如果之前存在key为键值的对象,这时候当前的缓存size 需要减掉这个对象的大小
if( previous !=null){
size -= safeSizeOf (key ,previous);
}
}
if(previous !=null ){
entryRemoved (false ,key ,previous ,value);
}
//调整内存缓存大小,插入的新对象会存储在列表的尾端
trimToSize (maxSize);
return previous;
}
- LruCache中将LinkedHashMap的顺序设置为LRU顺序来实现LRU缓存
- 每次调用get则将该对象移到链表的尾端
- 调用put插入新的对象也是存储在链表尾端
- 当内存缓存达到设定的最大值时候,将链表头部的对象(近期最少使用到的)移除
来源:https://www.cnblogs.com/cold-ice/p/9303485.html