转载请注明出处:https://blog.csdn.net/u011195398/article/details/82802085
前言
一直想找一个Android图片框架来研究,经过思考还是将目前使用最广泛的图片框架Glide来进行研究。目前网上的GlideV3和V4版本源码解析文章确实很多,但是看起来确实很费劲,目前Glide的版本已经到了V4.8.0了,很多思想已经很成熟,但是作为程序员都知道写框架是一个沉淀和积累的过程其实也跟程序员本人的功底和当时的整个IT大环境有关。所以,本人也想着应该从最低版本入手沿着框架的成长路线从2.0版本->3.0版本->4.0版本
逐步的去解析源码。
这样也可以看到一个小框架到成熟框架完善的过程,所以,这篇文章我就从GlideV2.0.0的稳定版本开始解读。
本章目标
其实这次文章的目标就是弄清楚他的基本流程的调用,对于一些非必要细节,就不做细致的说明了,后面会相继把我觉得重要的代码细节讲解出来。
Glide.load("http://obw8x60pr.bkt.clouddn.com/7moor/active_album/item_photo_01.png")
.into((ImageView)findViewById(R.id.image));
上面这行代码就就是我们从网络中获取图片然后设置到对应的图片控件的基本用法,短短的两行代码,其实他背后就做了很多操作,下面我们来解析下后面的原理。说句题外话,2.0版本的设计就使用了Builder
模式+流式调用,Android响应式编程思想都是从这个时候开始的吧,本人做Android多年所谓温故而知新,现在莫名有种感叹。
准备工作
既然是要阅读Glide的源码,那么我们自然需要先将Glide的源码下载下来。其实如果你是使用在build.gradle中添加依赖的方式将Glide引入到项目中的,那么源码自动就已经下载下来了,在Android Studio中就可以直接进行查看。
不过,使用添加依赖的方式引入的Glide,我们只能看到它的源码,但不能做任何的修改,如果你还需要修改它的源码的话,可以到GitHub上面将它的完整源码下载下来。
Glide的GitHub主页的地址是:https://github.com/bumptech/glide
今天我们就从他的tag中找到2.0版讲解,这个地址进行下载:https://github.com/bumptech/glide/tree/v2.0.0
流程解析
load方法()
public static Request<String> load(String string) {
return new Request<String>(string);
}
load直接实例化一个Request对象,由于这里是load是一个静态方法,Request目前是在Glide中,根据语法我们也需要将内部类也写为静态内部类。
/**
*
* 设置各种类型独立选项,包括调整大小,动画和占位符。
* 负责为给定目标构建或检索ImagePresenter,并将ImagePresenter传递给给定模型。
*
* @param <T> 模型的类型,将会被加载到目标中。
*/
@SuppressWarnings("unused") //public api
public static class Request<T> {
private Target target;
private ModelLoaderFactory<T> modelLoaderFactory;
private final T model;
private int animationId = -1;
private int placeholderId = -1;
private int errorId = -1;
private Request(T model) {
this(model, GLIDE.getFactory(model));
}
private Request(T model, ModelLoaderFactory<T> factory) {
if (model == null ) {
throw new IllegalArgumentException("Model can't be null");
}
this.model = model;
if (factory == null) {
throw new IllegalArgumentException("No ModelLoaderFactory registered for class=" + model.getClass());
}
this.modelLoaderFactory = factory;
}
...
}
可以看将两个构造函数,我们这里只需要看第一个构造函数,通过我们的GLIDE.getFactory(model)
获取到对应的工厂类。下面来看一段核心代码。
public class GenericLoaderFactory {
private Map<Class, ModelLoaderFactory> factories = new HashMap<Class, ModelLoaderFactory>();
...
@SuppressWarnings("unchecked")
public <T> ModelLoaderFactory<T> getFactory(Class<T> modelClass) {
ModelLoaderFactory result = factories.get(modelClass);
if (result == null) {
for (Class registeredModelClass : factories.keySet()) {
if (registeredModelClass.isAssignableFrom(modelClass)) {
result = factories.get(registeredModelClass);
break;
}
}
}
return result;
}
...
}
可以看见,这里就是从factories
通过class字节码获取到对应工厂类。那么我们又是在什么地方去对factories
进行put操作的的呢?阅读源码发现是在Glide的构造方法中,他就做好了ModelLoader
模版的初始化操作,这样便于后面理解,其实在Android编程过程中都喜欢在程序的入口处进行一些数据的初始化操作,比如我们通常在Application#onCreate中进行一些App的初始化操作,这里的思想是一样的,下面代码我做了精简,说明了工厂模版的注册来源。
protected Glide() {
loaderFactory.register(File.class, new FileLoader.Factory());
loaderFactory.register(Integer.class, new ResourceLoader.Factory());
loaderFactory.register(String.class, new StringLoader.Factory());
loaderFactory.register(Uri.class, new UriLoader.Factory());
Class.forName("com.bumptech.glide.volley.VolleyUrlLoader$Factory");
loaderFactory.register(URL.class, new VolleyUrlLoader.Factory());
}
看上面的代码,我们是通过String.class
字节码获取到了StringLoader
对象。我们来看一下StringLoader
的生成过程中,一些调用流程,这里的调用流程就有点意思了。
- StringLoader
public class StringLoader implements ModelLoader<String> {
...
public static class Factory implements ModelLoaderFactory<String> {
@Override
public ModelLoader<String> build(Context context, GenericLoaderFactory factories) {
return new StringLoader(factories.buildModelLoader(Uri.class, context));
}
...
}
...
}
- UriLoader
public class UriLoader implements ModelLoader<Uri> {
public static class Factory implements ModelLoaderFactory<Uri> {
@Override
public ModelLoader<Uri> build(Context context, GenericLoaderFactory factories) {
return new UriLoader(context, factories.buildModelLoader(URL.class, context));
}
}
...
}
- VolleyUrlLoader
/**
* A simple model loader for fetching images for a given url
*/
public class VolleyUrlLoader implements ModelLoader<URL> {
...
public static class Factory implements ModelLoaderFactory<URL> {
@Override
public ModelLoader<URL> build(Context context, GenericLoaderFactory factories) {
return new VolleyUrlLoader(getRequestQueue(context));
}
...
}
- GenericLoaderFactory#buildModelLoader
public <T> ModelLoader<T> buildModelLoader(Class<T> modelClass, Context context) {
final ModelLoaderFactory<T> factory = getFactory(modelClass);
if (factory == null) {
throw new IllegalArgumentException("No ModelLoaderFactory registered for class=" + modelClass);
}
return factory.build(context, this);
}
这里只得一提的是他的工厂模式d的编写,我们可以看见每一个内部类Factory
中都有一个build
方法结合了前面的factory
的模版缓存,就层层的生成ModelLoader了最后想要的一个VolleyUrlLoader类,很巧妙,算是责任链和工厂模式的一种结合吧有兴趣的可以自行学习一下写法。
Request的构建基本已经完成了,下面我们来说下一个重点的into方法,背后做了很多事情。
into方法()
其实into方法才是本章的重点之重点。网络访问,图片流的处理,缓存处理都在这个入口方法开始,下面还是从代码入手。重载的into
方法有两个,我这里就选择我代码中的into方法说起。
- into
/**
* 开始加载图像到目标控件当中
* <p>
* 注意 - 此方法将调用{@link ImageView#setTag(Object)}并可以静默覆盖可能已在视图上设置的任何标记。
* </p>
* @see ImagePresenter#setModel(Object)
* @param imageView 将展示图片的视图控件
*/
public void into(ImageView imageView) {
final ViewGroup.LayoutParams layoutParams = imageView.getLayoutParams();
if (layoutParams != null &&
(layoutParams.width == ViewGroup.LayoutParams.WRAP_CONTENT ||
layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT)) {
downsampler = Downsampler.NONE;
}
finish(imageView.getContext(), GLIDE.getImageViewTargetOrSet(imageView));
}
- finish
private void finish(Context context, Target target) {
this.context = context;
this.target = target;
ImagePresenter<T> imagePresenter = getImagePresenter(target);
imagePresenter.setModel(model);
}
其实到这里,我们就清楚的看见了核心的业务逻辑都在ImagePresenter
类中,下面我们来看看ImagePresenter
类定义和一段Glide的buildImagePresenter
方法
ImagePresenter
类定义
/**
* 包装{@link Target}以显示任意位图,并提供一个框架,用于在回收目标时正确获取和加载位图。
* 在给特定模型的{@link StreamLoader}模型之间使用{@link ModelLoader},
* 为给特定模型使用{@link StreamLoader}下载或以其他方式获得{@link java.io.InputStream},
* 以及{@link ImageLoader}从给定的{@link java.io.InputStream}加载位图。
* 此类还确定包装的宽度和高度{@link android.widget.ImageView}在运行时将该信息传递给提供的{@link ModelLoader}和
* {@link ImageLoader}。
*
* @param <T> 类型的模型包含显示图像所必需的信息。可以与包含路径或复杂数据类型的字符串一样简单。
*/
- Glide的
buildImagePresenter
方法
private ImagePresenter<T> buildImagePresenter(Target target) {
TransformationLoader<T> transformationLoader = getFinalTransformationLoader();
ImagePresenter.Builder<T> builder = new ImagePresenter.Builder<T>()
.setTarget(target, context)
.setModelLoader(modelLoaderFactory.build(context, GLIDE.loaderFactory))
.setImageLoader(new ImageManagerLoader(context, downsampler))
.setTransformationLoader(transformationLoader);
if (animationId != -1) {
final Animation animation = AnimationUtils.loadAnimation(context, animationId);
builder.setImageReadyCallback(new ImageReadyCallback() {
@Override
public void onImageReady(Target target, boolean fromCache) {
if (!fromCache) {
target.startAnimation(animation);
}
}
});
}
if (placeholderId != -1) {
builder.setPlaceholderResource(placeholderId);
}
if (errorId != -1) {
builder.setErrorResource(errorId);
}
return builder.build();
}
上面的ImagePresenter.Builder
的构建构成中,可以大致的看清楚了ImagePresenter的类中的核心主要核心类就是Builder时候的四个类。本章我们主要了解的是基本调用流程,还是要从网络相关的调用说起—ImageManagerLoader
类。
A base class for loaders that user ImageManager. Primarily responsible for keeping track of bitmaps for recycling purposes.
这段类注释就说明了用于ImageManager的加载器的基类,主要负责跟踪位图以便回收利用。
其实这里可以看出他的命名就是根据Imageloader
和imageManger
类命名的,顾名思义他的职责就是管理和加载,可以看看他的类定义注释。
A base class for loaders that user ImageManager. Primarily responsible for keeping track of bitmaps for recycling purposes.
从字面上看,是ImageManager的加载器的基类,主要负责跟踪位图以便回收利用,这里就和我们猜想得一样了,具体代码:
public class ImageManagerLoader implements ImageLoader {
protected final ImageManager imageManager;
private final Downsampler downsampler;
private Bitmap acquired;
private ImageManager.ImageManagerJob loadToken;
...
public ImageManagerLoader(ImageManager imageManager) {
this(imageManager, Downsampler.AT_LEAST);
}
@Override
public Object fetchImage(String id, StreamLoader streamLoader, Transformation transformation, int width, int height, final ImageReadyCallback cb) {
if (!isHandled(width, height)) {
throw new IllegalArgumentException(getClass() + " cannot handle width=" + width + " and/or height =" +
height);
}
loadToken = imageManager.getImage(id, streamLoader, transformation, downsampler, width, height, new LoadedCallback() {
@Override
public void onLoadCompleted(Bitmap loaded) {
onImageReady(loaded, cb.onImageReady(loaded));
}
@Override
public void onLoadFailed(Exception e) {
cb.onException(e);
}
});
return loadToken;
}
...
}
代码精简还是从网络入手,fetchImage
方法就是他的核心代码,我们跟进去看下。
public ImageManagerJob getImage(String id, StreamLoader streamLoader, Transformation transformation, Downsampler downsampler, int width, int height, LoadedCallback cb) {
if (shutdown) return null;
final String key = getKey(id, transformation.getId(), downsampler, width, height);
ImageManagerJob job = null;
if (!returnFromCache(key, cb)) {
ImageManagerRunner runner = new ImageManagerRunner(key, streamLoader, transformation, downsampler, width, height, cb);
runner.execute();
job = new ImageManagerJob(runner, streamLoader, transformation, downsampler, cb);
}
return job;
}
我们可以看见这里显示从缓存中通过key的查找获取缓存中是否有该图片,若没有,我们则从网络中获取,今天我们就不讲缓存的情况了(这里的key留个心,后面的版本中对这里进行了封装,通过编写这里我们也可以对token动态改变的编写我们自己的规则二),
直接跳入网络获取图片的逻辑。从线程池中走ImageManagerRunner#run()方法。
- ImageManagerRunner#run()
@Override
public void run() {
Bitmap result = getFromDiskCache(key);
if (result == null) {
try {
resizeWithPool();
} catch (Exception e) {
handleException(e);
}
} else {
finishResize(result, true);
}
}
还是直接查看网络方面的逻辑
- resizeWithPool()
private void resizeWithPool() {
future = executor.submit(new Runnable() {
@Override
public void run() {
final StreamLoader streamLoader = slRef.get();
if (streamLoader == null) {
return;
}
streamLoader.loadStream(new StreamLoader.StreamReadyCallback() {
@Override
public void onStreamReady(final InputStream is) {
if (cancelled) {
return;
}
//this callback might be called on some other thread,
//we want to do resizing on our thread, especially if we're called
//back on the main thread, so we will resubmit
future = executor.submit(new Runnable() {
@Override
public void run() {
try {
final Downsampler downsampler = dRef.get();
final Transformation transformation = tRef.get();
if (downsampler != null && transformation != null) {
final Bitmap result = resizeIfNotFound(is, downsampler, transformation);
finishResize(result, false);
}
} catch (Exception e) {
handleException(e);
}
}
});
}
@Override
public void onException(Exception e) {
handleException(e);
}
});
}
});
}
没有Rxjava思想的出现,回调看起来有点多,但是这里的代码还是很简单的,主要是两个方面
- 1.一个通过Volley网络框架获取图片流。
- 2.处理图片输入流的逻辑。
网络获取图片流的逻辑
streamLoader.loadStream(new StreamLoader.StreamReadyCallback() {
@Override
public void onStreamReady(final InputStream is) {
if (cancelled) {
return;
}
}
@Override
public void onException(Exception e) {
handleException(e);
}
});
流程就是回调监听下载成功和失败,这里需要注意的是streamLoader是走的volley框架所以streamLoader实际是子类VolleyStreamLoader
类,逻辑还是走的volley,代码简单就不暂开了。
图片输入流的处理逻辑。
//this callback might be called on some other thread,
//we want to do resizing on our thread, especially if we're called
//back on the main thread, so we will resubmit
future = executor.submit(new Runnable() {
@Override
public void run() {
try {
final Downsampler downsampler = dRef.get();
final Transformation transformation = tRef.get();
if (downsampler != null && transformation != null) {
final Bitmap result = resizeIfNotFound(is, downsampler, transformation);
finishResize(result, false);
}
} catch (Exception e) {
handleException(e);
}
}
});
核心方法,resizeIfNotFound
和resizeIfNotFound
都需要暂开了说。
resizeIfNotFound调用流程
- resizeIfNotFound
private Bitmap resizeIfNotFound(InputStream is, Downsampler downsampler, Transformation transformation) {
return resizer.load(is, width, height, downsampler, transformation);
}
- ImageResizer#load(InputStream is, int outWidth, int outHeight, Downsampler downsampler, Transformation transformation)
public Bitmap load(InputStream is, int outWidth, int outHeight, Downsampler downsampler, Transformation transformation) {
byte[] tempBytesForBis = getTempBytes();
byte[] tempBytesForOptions = getTempBytes();
BitmapFactory.Options options = getOptions();
options.inTempStorage = tempBytesForOptions;
RecyclableBufferedInputStream bis = new RecyclableBufferedInputStream(is, tempBytesForBis);
final Bitmap initial = downsampler.downsample(bis, options, bitmapPool, outWidth, outHeight);
final Bitmap result = transformation.transform(initial, bitmapPool, outWidth, outHeight);
if (initial != result) {
bitmapPool.put(initial);
}
releaseTempBytes(tempBytesForBis);
releaseTempBytes(tempBytesForOptions);
return result;
}
上面就是我们图片网络处理的核心逻辑了,在这里我们合一看见几个核心的核心的图片处理类。RecyclableBufferedInputStream
,Downsampler
,Transformation
。我们创建一个的临时缓存流用于对图片的操作,然后通过Downsampler#downsample
对图片进行了系列的处理。
逻辑大致如下五步:
- 1.判断图片的方向。
- 2.充值流的到初始位置。
- 3.获取sampleSize。
- 4.通过sampleSize来获取图片。
- 5.通过制定orientation的参数生成对应的图片。
public Bitmap downsample(RecyclableBufferedInputStream bis, BitmapFactory.Options options, BitmapPool pool, int outWidth, int outHeight) {
bis.mark(MARK_POSITION);
int orientation = 0;
try {
orientation = new ExifOrientationParser(bis).getOrientation();//ImageResizer.getOrientation(bis);
} catch (IOException e) {
e.printStackTrace();
}
try {
bis.reset();
} catch (IOException e) {
e.printStackTrace();
}
final int[] inDimens = getDimensions(bis, options);
final int inWidth = inDimens[0];
final int inHeight = inDimens[1];
final int degreesToRotate = ImageResizer.getExifOrientationDegrees(orientation);
final int sampleSize;
if (degreesToRotate == 90 || degreesToRotate == 270) {
//if we're rotating the image +-90 degrees, we need to downsample accordingly so the image width is
//decreased to near our target's height and the image height is decreased to near our target width
sampleSize = getSampleSize(inHeight, inWidth, outWidth, outHeight);
} else {
sampleSize = getSampleSize(inWidth, inHeight, outWidth, outHeight);
}
final Bitmap downsampled = downsampleWithSize(bis, options, pool, inWidth, inHeight, sampleSize);
final Bitmap rotated = ImageResizer.rotateImageExif(downsampled, pool, orientation);
if (downsampled != rotated) {
pool.put(downsampled);
}
return rotated;
}
其实这个类核心图片处理方法还是走的Android最原始的那图片处理流程。也就是通常的BitmapFacotory.Option
public static Bitmap decodeBitmap(byte[] source,int reqesutWidth,int requestHeight){
Bitmap resultBitmap = null;
BitmapFactory.Options option = new BitmapFactory.Options();
//设置option.inJustDecodeBounds 为true获取图片宽高
option.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(source,0,source.length,option);
//通过BitmapFactory.Options.inSampleSize的值保存压缩比例值
option.inSampleSize = caulateInSampleSize(option,reqesutWidth,requestHeight);
option.inJustDecodeBounds = false;
resultBitmap = BitmapFactory.decodeByteArray(source,0,source.length,option);
return resultBitmap;
}
/**
* 计算压缩比例
* 通过BitmapFactory.Options.inSampleSize的值保存该值
* */
private static int caulateInSampleSize(BitmapFactory.Options option, int reqesutWidth, int requestHeight)
{
//inSampleSize为1即图片原本大小 如:4 即宽高比为原来1/4
int inSampleSize = 1; //原图片宽高
int width = option.outWidth;
int height = option.outHeight; //原图片宽高其中一个大于要求的宽高才压缩比例
if(height > requestHeight || width > reqesutWidth)
{
inSampleSize = Math.min(Math.round((float)height / requestHeight), Math.round((float)width / reqesutWidth));
}
return inSampleSize;
}
相信没用框架之前,我们都是通过上面的这段代码来处理图片的已经很熟悉了。在这里GlideV2版本也是用的这种方式本质还是一样的。
ImageManager#finishResize(final Bitmap result, boolean isInDiskCache)
private void finishResize(final Bitmap result, boolean isInDiskCache) {
if (result != null) {
if (!isInDiskCache) {
putInDiskCache(key, result);
}
mainHandler.post(new Runnable() {
@Override
public void run() {
final LoadedCallback cb = cbRef.get();
bitmapReferenceCounter.initBitmap(result);
putInMemoryCache(key, result);
if (cb != null) {
bitmapReferenceCounter.markPending(result);
cb.onLoadCompleted(result);
}
}
});
} else {
handleException(null);
}
}
前面调用的时候,1.因为是第一次从网络中获取,所以我们会将图片添加到硬盘缓存中,2.图片新增加入bitmapReferenceCounter的引用计数器。3.放入内存缓存中。4.进行下载的成功回调。这里的方法主要就是做一些缓存的处理,这章就不多做暂开了。我们回到ImageLoader类中,图片下载成功后最终就走此回调ImagePresenter#fetchImage
中。
imageToken = imageLoader.fetchImage(id, sl, t, width, height, new ImageLoader.ImageReadyCallback() {
@Override
public boolean onImageReady(Bitmap image) {
if (loadCount != currentCount || !canSetImage() || image == null) return false;
if (imageReadyCallback != null)
imageReadyCallback.onImageReady(target, loadedFromCache);
target.onImageReady(image);
isImageSet = true;
return true;
}
@Override
public void onException(Exception e) {
final boolean relevant = loadCount == currentCount;
if (exceptionHandler != null) {
exceptionHandler.onException(e, model, relevant);
}
if (relevant && canSetPlaceholder() && errorDrawable != null) {
target.setPlaceholder(errorDrawable);
}
}
});
target.onImageReady(image),前面我们看见在ImagePresenter#buildImagePresenter
中,我们调用了setTarget的方法。这里就是target的来源。V2版本的target远没有后来版本这么丰富,这里就不展开继承关系直接看ImageViewTarget类的onImageReady方法。
@Override
public void onImageReady(Bitmap bitmap) {
imageView.setImageBitmap(bitmap);
}
最终,我们获取到的位图就加载到我们的imageview的控件中了。
本章我们的基本流程到此就已经讲解完毕了,整体看来从低版本来看调用流程还是比较简单清晰的,远没有高版本阅读代码这么费力。这里的网络框架也是直接使用的volley我们可以省去很多篇幅讲解volley的架构。再者目前的缓存和图片处理其实都很简单。图片的处理就是基本android原生的处理方式。缓存的处理是对内存缓存和硬盘缓存两个方面的针对。后面我们都会讲到。