热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

Android源码分析:这是一份详细的图片加载库Glide源码讲解攻略

前言Glide,该功能非常强大Android图片加载开源框架相信大家并不陌生正由于他的功能强大,所以它的源码非常复杂,这导致很多人望而却步本人尝试将Glide的

前言
  • Glide,该功能非常强大 Android 图片加载开源框架 相信大家并不陌生
    // 步骤3:若是普通静图,就调用decodeBitmapWrapper()解码
    if (result == null) {
    ImageVideoWrapper forBitmapDecoder = new ImageVideoWrapper(bis, source.getFileDescriptor());
    result = decodeBitmapWrapper(forBitmapDecoder, width, height);
    // ->>分析23
    }
    return result;
    }

    <-- 分析23:decodeBitmapWrapper() -->
    private GifBitmapWrapper decodeBitmapWrapper(ImageVideoWrapper toDecode, int width, int height) throws IOException {
    GifBitmapWrapper result = null;
    Resource bitmapResource = bitmapDecoder.decode(toDecode, width, height);
    // bitmapDecoder是一个ImageVideoBitmapDecoder对象
    // 即调用ImageVideoBitmapDecoder对象的decode()->>分析24
    if (bitmapResource != null) {
    result = new GifBitmapWrapper(bitmapResource, null);
    }
    return result;
    }

    ...
    }

    <-- 分析24:ImageVideoBitmapDecoder.decode() -->
    public class ImageVideoBitmapDecoder implements ResourceDecoder<ImageVideoWrapper, Bitmap> {

    ...

    @Override
    public Resource decode(ImageVideoWrapper source, int width, int height) throws IOException {
    Resource result = null;
    InputStream is = source.getStream();
    // 步骤1:获取到服务器返回的InputStream
    if (is != null) {
    try {
    result = streamDecoder.decode(is, width, height);
    // 步骤2:调用streamDecoder.decode()进行解码
    // streamDecode是一个StreamBitmapDecoder对象 ->>分析25

    } catch (IOException e) {
    ...
    }

    <-- 分析25:StreamBitmapDecoder.decode() -->
    public class StreamBitmapDecoder implements ResourceDecoder<InputStream, Bitmap> {

    ...

    @Override
    public Resource decode(InputStream source, int width, int height) {
    Bitmap bitmap = downsampler.decode(source, bitmapPool, width, height, decodeFormat);
    // Downsampler的decode() ->>分析26

    // 从分析26回来看这里:
    return BitmapResource.obtain(bitmap, bitmapPool);
    // 作用:将分析26中返回的Bitmap对象包装成Resource对象
    // 因为decode()返回的是一个Resource对象;而从Downsampler中得到的是一个Bitmap对象,需要进行类型的转换
    // 经过这样一层包装后,如果还需要获取Bitmap,只需要调用Resource的get()即可
    // 接下来,我们需要一层层地向上返回(请向下看直到跳出该代码块)
    }

    ...
    }

    <-- 分析26:downsampler.decode() -->
    // 主要作用:读取服务器返回的InputStream & 加载图片
    // 其他作用:对图片的压缩、旋转、圆角等逻辑处理
    public abstract class Downsampler implements BitmapDecoder<InputStream> {

    ...

    @Override
    public Bitmap decode(InputStream is, BitmapPool pool, int outWidth, int outHeight, DecodeFormat decodeFormat) {
    final ByteArrayPool byteArrayPool = ByteArrayPool.get();
    final byte[] bytesForOptiOns= byteArrayPool.getBytes();
    final byte[] bytesForStream = byteArrayPool.getBytes();
    final BitmapFactory.Options optiOns= getDefaultOptions();
    // Use to fix the mark limit to avoid allocating buffers that fit entire images.
    RecyclableBufferedInputStream bufferedStream = new RecyclableBufferedInputStream(
    is, bytesForStream);
    // Use to retrieve exceptions thrown while reading.
    // TODO(#126): when the framework no longer returns partially decoded Bitmaps or provides a way to determine
    // if a Bitmap is partially decoded, consider removing.
    ExceptionCatchingInputStream exceptiOnStream=
    ExceptionCatchingInputStream.obtain(bufferedStream);
    // Use to read data.
    // Ensures that we can always reset after reading an image header so that we can still attempt to decode the
    // full image even when the header decode fails and/or overflows our read buffer. See #283.
    MarkEnforcingInputStream invalidatingStream = new MarkEnforcingInputStream(exceptionStream);
    try {
    exceptionStream.mark(MARK_POSITION);
    int orientation = 0;
    try {
    orientation = new ImageHeaderParser(exceptionStream).getOrientation();
    } catch (IOException e) {
    if (Log.isLoggable(TAG, Log.WARN)) {
    Log.w(TAG, "Cannot determine the image orientation from header", e);
    }
    } finally {
    try {
    exceptionStream.reset();
    } catch (IOException e) {
    if (Log.isLoggable(TAG, Log.WARN)) {
    Log.w(TAG, "Cannot reset the input stream", e);
    }
    }
    }
    options.inTempStorage = bytesForOptions;
    final int[] inDimens = getDimensions(invalidatingStream, bufferedStream, options);
    final int inWidth = inDimens[0];
    final int inHeight = inDimens[1];
    final int degreesToRotate = TransformationUtils.getExifOrientationDegrees(orientation);
    final int sampleSize = getRoundedSampleSize(degreesToRotate, inWidth, inHeight, outWidth, outHeight);
    final Bitmap downsampled =
    downsampleWithSize(invalidatingStream, bufferedStream, options, pool, inWidth, inHeight, sampleSize,
    decodeFormat);
    // BitmapFactory swallows exceptions during decodes and in some cases when inBitmap is non null, may catch
    // and log a stack trace but still return a non null bitmap. To avoid displaying partially decoded bitmaps,
    // we catch exceptions reading from the stream in our ExceptionCatchingInputStream and throw them here.
    final Exception streamException = exceptionStream.getException();
    if (streamException != null) {
    throw new RuntimeException(streamException);
    }
    Bitmap rotated = null;
    if (downsampled != null) {
    rotated = TransformationUtils.rotateImageExif(downsampled, pool, orientation);
    if (!downsampled.equals(rotated) && !pool.put(downsampled)) {
    downsampled.recycle();
    }
    }
    return rotated;
    } finally {
    byteArrayPool.releaseBytes(bytesForOptions);
    byteArrayPool.releaseBytes(bytesForStream);
    exceptionStream.release();
    releaseOptions(options);
    }
    }

    private Bitmap downsampleWithSize(MarkEnforcingInputStream is, RecyclableBufferedInputStream bufferedStream,
    BitmapFactory.Options options, BitmapPool pool, int inWidth, int inHeight, int sampleSize,
    DecodeFormat decodeFormat) {
    // Prior to KitKat, the inBitmap size must exactly match the size of the bitmap we're decoding.
    Bitmap.Config cOnfig= getConfig(is, decodeFormat);
    options.inSampleSize = sampleSize;
    options.inPreferredCOnfig= config;
    if ((options.inSampleSize == 1 || Build.VERSION_CODES.KITKAT <= Build.VERSION.SDK_INT) && shouldUsePool(is)) {
    int targetWidth = (int) Math.ceil(inWidth / (double) sampleSize);
    int targetHeight = (int) Math.ceil(inHeight / (double) sampleSize);
    // BitmapFactory will clear out the Bitmap before writing to it, so getDirty is safe.
    setInBitmap(options, pool.getDirty(targetWidth, targetHeight, config));
    }
    return decodeStream(is, bufferedStream, options);
    }

    /**
    * A method for getting the dimensions of an image from the given InputStream.
    *
    * @param is The InputStream representing the image.
    * @param options The options to pass to
    * {@link BitmapFactory#decodeStream(InputStream, android.graphics.Rect,
    * BitmapFactory.Options)}.
    * @return an array containing the dimensions of the image in the form {width, height}.
    */

    public int[] getDimensions(MarkEnforcingInputStream is, RecyclableBufferedInputStream bufferedStream,
    BitmapFactory.Options options) {
    options.inJustDecodeBounds = true;
    decodeStream(is, bufferedStream, options);
    options.inJustDecodeBounds = false;
    return new int[] { options.outWidth, options.outHeight };
    }

    private static Bitmap decodeStream(MarkEnforcingInputStream is, RecyclableBufferedInputStream bufferedStream,
    BitmapFactory.Options options) {
    if (options.inJustDecodeBounds) {
    // This is large, but jpeg headers are not size bounded so we need something large enough to minimize
    // the possibility of not being able to fit enough of the header in the buffer to get the image size so
    // that we don't fail to load images. The BufferedInputStream will create a new buffer of 2x the
    // original size each time we use up the buffer space without passing the mark so this is a maximum
    // bound on the buffer size, not a default. Most of the time we won't go past our pre-allocated 16kb.
    is.mark(MARK_POSITION);
    } else {
    // Once we've read the image header, we no longer need to allow the buffer to expand in size. To avoid
    // unnecessary allocations reading image data, we fix the mark limit so that it is no larger than our
    // current buffer size here. See issue #225.
    bufferedStream.fixMarkLimit();
    }

    final Bitmap result = BitmapFactory.decodeStream(is, null, options);
    return result;
    // decode()方法执行后会返回一个Bitmap对象
    // 此时图片已经被加载出来
    // 接下来的工作是让加载了的Bitmap显示到界面上
    // 请回到分析25
    }

    ...
    }


    步骤3:返回图片资源

    加载完图片后,需要一层层向上返回

    • 返回路径
      StreamBitmapDecoder(分析25)-> ImageVideoBitmapDecoder(分析24)-> GifBitmapWrapperResourceDecoder``decodeBitmapWrapper()(分析23)

    • 由于隔得太远,我重新把(分析23)decodeBitmapWrapper()贴出

    <-- 分析23:decodeBitmapWrapper -->
    private GifBitmapWrapper decodeBitmapWrapper(ImageVideoWrapper toDecode, int width, int height) throws IOException {
    GifBitmapWrapper result = null;
    Resource bitmapResource = bitmapDecoder.decode(toDecode, width, height);
    if (bitmapResource != null) {
    result = new GifBitmapWrapper(bitmapResource, null);
    // 将Resource封装到了一个GifBitmapWrapper对象
    }
    return result;
    // 最终返回的是一个GifBitmapWrapper对象:既能封装GIF,又能封装Bitmap,从而保证了不管是什么类型的图片,Glide都能加载
    // 接下来我们分析下GifBitmapWrapper() ->>分析27
    }

    <-- 分析27:GifBitmapWrapper() -->
    // 作用:分别对gifResource和bitmapResource做了一层封装
    public class GifBitmapWrapper {
    private final Resource gifResource;
    private final Resource bitmapResource;

    public GifBitmapWrapper(Resource bitmapResource, Resource gifResource) {
    if (bitmapResource != null && gifResource != null) {
    throw new IllegalArgumentException("Can only contain either a bitmap resource or a gif resource, not both");
    }
    if (bitmapResource == null && gifResource == null) {
    throw new IllegalArgumentException("Must contain either a bitmap resource or a gif resource");
    }
    this.bitmapResource = bitmapResource;
    this.gifResource = gifResource;
    }

    /**
    * Returns the size of the wrapped resource.
    */

    public int getSize() {
    if (bitmapResource != null) {
    return bitmapResource.getSize();
    } else {
    return gifResource.getSize();
    }
    }

    /**
    * Returns the wrapped {@link Bitmap} resource if it exists, or null.
    */

    public Resource getBitmapResource() {
    return bitmapResource;
    }

    /**
    * Returns the wrapped {@link GifDrawable} resource if it exists, or null.
    */

    public Resource getGifResource() {
    return gifResource;
    }
    }
    • 然后该GifBitmapWrapper对象会一直向上返回
    • 直到返回到GifBitmapWrapperResourceDecoder的decode()时(分析20),会对GifBitmapWrapper对象再做一次封装,如下所示:
      此处将上面的分析20再次粘贴过来
    <--分析20:GifBitmapWrapperResourceDecoder对象的decode()  -->
    public class GifBitmapWrapperResourceDecoder implements ResourceDecoder<ImageVideoWrapper, GifBitmapWrapper> {

    ...
    @Override
    public Resource decode(ImageVideoWrapper source, int width, int height) throws IOException {

    try {
    wrapper = decode(source, width, height, tempBytes);
    } finally {
    pool.releaseBytes(tempBytes);
    }

    // 直接看这里
    return wrapper != null ? new GifBitmapWrapperResource(wrapper) : null;
    // 将GifBitmapWrapper封装到一个GifBitmapWrapperResource对象中(Resource类型) 并返回
    // 该GifBitmapWrapperResource和上述的BitmapResource类似- 实现了Resource接口,可通过get()来获取封装的具体内容
    // GifBitmapWrapperResource()源码分析 - >>分析28
    }

    <-- 分析28: GifBitmapWrapperResource()-->
    // 作用:经过这层封装后,我们从网络上得到的图片就能够以Resource接口的形式返回,并且还能同时处理Bitmap图片和GIF图片这两种情况。
    public class GifBitmapWrapperResource implements Resource<GifBitmapWrapper> {
    private final GifBitmapWrapper data;

    public GifBitmapWrapperResource(GifBitmapWrapper data) {
    if (data == null) {
    throw new NullPointerException("Data must not be null");
    }
    this.data = data;
    }

    @Override
    public GifBitmapWrapper get() {
    return data;
    }

    @Override
    public int getSize() {
    return data.getSize();
    }

    @Override
    public void recycle() {
    Resource bitmapResource = data.getBitmapResource();
    if (bitmapResource != null) {
    bitmapResource.recycle();
    }
    Resource gifDataResource = data.getGifResource();
    if (gifDataResource != null) {
    gifDataResource.recycle();
    }
    }
    }

    继续返回到DecodeJobdecodeFromSourceData()(分析19)中:

    <-- 分析19:decodeFromSourceData()()  -->
    private Resource<T> decodeFromSourceData(A data) throws IOException {

    decoded = loadProvider.getSourceDecoder().decode(data, width, height);

    return decoded;
    // 该方法返回的是一个`Resource<T>`对象,其实就是Resource<GifBitmapWrapper>对象
    }
    • 继续向上返回,最终返回到DecodeJobdecodeFromSource()中(分析15)
    • 如下所示:
    <-- 分析15:DecodeJob的decodeFromSource()  -->
    class DecodeJob<A, T, Z> {
    ...

    public Resource decodeFromSource() throws Exception {
    Resource decoded = decodeSource();
    // 返回到这里,最终得到了这个Resource对象,即Resource对象
    return transformEncodeAndTranscode(decoded);
    // 作用:将该Resource对象 转换成 Resource对象 -->分析29
    }


    <--分析29:transformEncodeAndTranscode() -->
    private Resource transformEncodeAndTranscode(Resource decoded) {

    Resource result = transcode(transformed);
    // 把Resource对象转换成Resource对象 ->>分析30
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
    logWithTimeAndKey("Transcoded transformed from source", startTime);
    }
    return result;
    }

    <-- 分析30:transcode(transformed) -->

    private Resource transcode(Resource transformed) {
    if (transformed == null) {
    return null;
    }
    return transcoder.transcode(transformed);
    // 调用了transcoder的transcode()
    // 这里的transcoder就是第二步load()中的GifBitmapWrapperDrawableTranscoder对象(回看下第2步生成对象的表)
    // 接下来请看 ->>分析31
    }

    <-- 分析31:GifBitmapWrapperDrawableTranscoder.transcode(transformed) -->
    // 作用:转码,即从Resource中取出GifBitmapWrapper对象,然后再从GifBitmapWrapper中取出Resource对象。
    // 因为GifBitmapWrapper是无法直接显示到ImageView上的,只有Bitmap或者Drawable才能显示到ImageView上。

    public class GifBitmapWrapperDrawableTranscoder implements ResourceTranscoder<GifBitmapWrapper, GlideDrawable> {

    ...

    @Override
    public Resource transcode(Resource toTranscode) {
    GifBitmapWrapper gifBitmap = toTranscode.get();
    // 步骤1:从Resource中取出GifBitmapWrapper对象(上面提到的调用get()进行提取)
    Resource bitmapResource = gifBitmap.getBitmapResource();
    // 步骤2:从GifBitmapWrapper中取出Resource对象

    final Resource extends GlideDrawable> result;


    // 接下来做了一个判断:

    // 1. 若Resource不为空
    if (bitmapResource != null) {
    result = bitmapDrawableResourceTranscoder.transcode(bitmapResource);
    // 则需要再做一次转码:将Bitmap转换成Drawable对象
    // 因为要保证静图和动图的类型一致性,否则难以处理->>分析32
    } else {

    // 2. 若Resource为空(说明此时加载的是GIF图)
    // 那么直接调用getGifResource()方法将图片取出
    // 因为Glide用于加载GIF图片是使用的GifDrawable这个类,它本身就是一个Drawable对象
    result = gifBitmap.getGifResource();
    }
    return (Resource) result;
    }

    ...
    }

    <-- 分析32:bitmapDrawableResourceTranscoder.transcode(bitmapResource)-->
    // 作用:再做一次转码:将Bitmap转换成Drawable对象
    public class GlideBitmapDrawableTranscoder implements ResourceTranscoder<Bitmap, GlideBitmapDrawable> {

    ...

    @Override
    public Resource transcode(Resource toTranscode) {

    GlideBitmapDrawable drawable = new GlideBitmapDrawable(resources, toTranscode.get());
    // 创建GlideBitmapDrawable对象,并把Bitmap封装到里面

    return new GlideBitmapDrawableResource(drawable, bitmapPool);
    // 对GlideBitmapDrawable再进行一次封装,返回Resource对象
    }

    }
    • 此时,无论是静图的 Resource 对象,还是动图的Resource 对象,它们都属于父类Resource对象
    • 因此transcode()返回的是Resource对象,即转换过后的Resource

    所以,分析15DecodeJob的decodeFromSource()中,得到的Resource对象 是 Resource对象


    步骤4:在主线程显示图片

    继续向上返回,最终返回到 EngineRunnablerun() 中(分析12)

    重新贴出这部分代码

    <--分析12:EngineRunnable的run() -->
    @Override
    public void run() {

    try {
    resource = decode();
    // 最终得到了Resource对象
    // 接下来的工作:将该图片显示出来

    } catch (Exception e) {
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
    Log.v(TAG, "Exception decoding", e);
    }
    exception = e;
    }
    if (isCancelled) {
    if (resource != null) {
    resource.recycle();
    }
    return;
    }
    if (resource == null) {
    onLoadFailed(exception);
    } else {
    onLoadComplete(resource);
    // 表示图片加载已经完成 ->>分析33
    }
    }

    <-- 分析33: onLoadComplete(resource) -->
    private void onLoadComplete(Resource resource) {
    manager.onResourceReady(resource);
    // 该manager即EngineJob对象
    // 实际上调用的是EngineJob的onResourceReady() - >>分析34
    }

    <-- 分析34:EngineJob的onResourceReady() : -->
    class EngineJob implements EngineRunnable.EngineRunnableManager {
    ...

    private static final Handler MAIN_THREAD_HANDLER = new Handler(Looper.getMainLooper(), new MainThreadCallback());
    // 创建线程,并绑定主线程的Looper
    private final List cbs = new ArrayList();

    @Override
    public void onResourceReady(final Resource resource) {
    this.resource = resource;
    MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();
    // 使用Handler发出一条 MSG_COMPLETE 消息
    // 那么在MainThreadCallback的handleMessage()方法中就会收到这条消息 ->>分析35
    // 从此处开始,所有逻辑又回到主线程中进行了,即更新UI

    }

    <-- 分析35:MainThreadCallback的handleMessage()-->
    private static class MainThreadCallback implements 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();
    // 调用 EngineJob的handleResultOnMainThread() ->>分析36
    } else {
    job.handleExceptionOnMainThread();
    }
    return true;
    }
    return false;
    }
    }

    ...
    }

    <-- 分析36:handleResultOnMainThread() -->
    private void handleResultOnMainThread() {

    // 通过循环,调用了所有ResourceCallback的onResourceReady()
    for (ResourceCallback cb : cbs) {
    if (!isInIgnoredCallbacks(cb)) {
    engineResource.acquire();
    cb.onResourceReady(engineResource);
    // ResourceCallback 是在addCallback()方法当中添加的->>分析37
    }
    }
    engineResource.release();
    }

    <-- 分析37:addCallback() -->
    //
    public void addCallback(ResourceCallback cb) {
    Util.assertMainThread();
    if (hasResource) {
    cb.onResourceReady(engineResource);
    // 会向cbs集合中去添加ResourceCallback
    } else if (hasException) {
    cb.onException(exception);
    } else {
    cbs.add(cb);
    }
    }

    // 而addCallback()是在分析11:Engine的load()中调用的:
    <-- 上面的分析11:Engine的load() -->
    public class Engine implements EngineJobListener,
    MemoryCache.ResourceRemovedListener,
    EngineResource.ResourceListener {


    ...

    public LoadStatus load(Key signature, int width, int height, DataFetcher fetcher,
    DataLoadProvider loadProvider, Transformation transformation, ResourceTranscoder transcoder, Priority priority,
    boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {

    engineJob.addCallback(cb);
    // 调用addCallback()注册了一个ResourceCallback
    // 上述参数cb是load()传入的的最后一个参数
    // 而load()是在GenericRequest的onSizeReady()调用的->>回到分析9(下面重新贴多了一次)
    return new LoadStatus(cb, engineJob);
    }

    ...
    }

    <-- 上面的分析9:onSizeReady() -->
    public void onSizeReady(int width, int height) {

    ...
    loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder,
    priority, isMemoryCacheable, diskCacheStrategy, this);
    // load()最后一个参数是this
    // 所以,ResourceCallback类型参数cb是this
    // 而GenericRequest本身实现了ResourceCallback接口
    // 因此,EngineJob的回调 = cb.onResourceReady(engineResource) = 最终回调GenericRequest的onResourceReady() -->>分析6

    }
    }

    <-- 分析38:GenericRequest的onResourceReady() -->
    // onResourceReady()存在两个方法重载

    // 重载1
    public void onResourceReady(Resource resource) {
    Object received = resource.get();
    // 获取封装的图片对象(GlideBitmapDrawable对象 或 GifDrawable对象

    onResourceReady(resource, (R) received);
    // 然后将该获得的图片对象传入到了onResourceReady()的重载方法中 ->>看重载2
    }


    // 重载2
    private void onResourceReady(Resource resource, R result) {

    ...

    target.onResourceReady(result, animation);
    // Target是在第3步into()的最后1行调用glide.buildImageViewTarget()方法来构建出的Target:GlideDrawableImageViewTarget对象
    // ->>分析39

    }

    <-- 分析39:GlideDrawableImageViewTarget.onResourceReady -->
    public class GlideDrawableImageViewTarget extends ImageViewTarget<GlideDrawable> {

    @Override
    public void onResourceReady(GlideDrawable resource, GlideAnimationsuper GlideDrawable> animation) {
    if (!resource.isAnimated()) {
    float viewRatio = view.getWidth() / (float) view.getHeight();
    float drawableRatio = resource.getIntrinsicWidth() / (float) resource.getIntrinsicHeight();
    if (Math.abs(viewRatio - 1f) <= SQUARE_RATIO_MARGIN
    && Math.abs(drawableRatio - 1f) <= SQUARE_RATIO_MARGIN) {
    resource = new SquaringDrawable(resource, view.getWidth());
    }
    }
    super.onResourceReady(resource, animation);
    // 若是静态图片,就调用父类的.onResourceReady() 将GlideDrawable显示到ImageView上
    // GlideDrawableImageViewTarget的父类是ImageViewTarget ->>分析40
    this.resource = resource;
    resource.setLoopCount(maxLoopCount);
    resource.start();
    // 如果是GIF图片,就调用resource.start()方法开始播放图片
    }

    @Override
    protected void setResource(GlideDrawable resource) {
    view.setImageDrawable(resource);
    }

    ...
    }

    <-- 分析40:ImageViewTarget.onResourceReady() -->
    public abstract class ImageViewTarget<Z> extends ViewTarget<ImageView, Z> implements GlideAnimation.ViewAdapter {

    ...

    @Override
    public void onResourceReady(Z resource, GlideAnimationsuper Z> glideAnimation) {
    if (glideAnimation == null || !glideAnimation.animate(resource, this)) {
    setResource(resource);
    // 继续往下看
    }
    }

    protected abstract void setResource(Z resource);
    // setResource()是一个抽象方法
    // 需要在子类具体实现:请回看上面分析39子类GlideDrawableImageViewTarget类重写的setResource():调用view.setImageDrawable(),而这个view就是ImageView
    // 即setResource()的具体实现是调用ImageView的setImageDrawable() 并 传入图片,于是就实现了图片显示。

    }

    终于,静图 / Gif图 成功显示出来

    总结

    至此,Glide的基本功能 图片加载的全功能 解析完毕。


    5. 总结

    一图总结Glide的基本功能 图片加载的全过程

    示意图

    • 下面我将继续对 Glide 的其他功能进行源码分析 ,有兴趣可以继续关注Carson_Ho的安卓开发笔记

    帮顶 / 评论点赞!因为你的鼓励是我写作的最大动力!


推荐阅读
  • 深入理解OAuth认证机制
    本文介绍了OAuth认证协议的核心概念及其工作原理。OAuth是一种开放标准,旨在为第三方应用提供安全的用户资源访问授权,同时确保用户的账户信息(如用户名和密码)不会暴露给第三方。 ... [详细]
  • 深入解析Android自定义View面试题
    本文探讨了Android Launcher开发中自定义View的重要性,并通过一道经典的面试题,帮助开发者更好地理解自定义View的实现细节。文章不仅涵盖了基础知识,还提供了实际操作建议。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 1.如何在运行状态查看源代码?查看函数的源代码,我们通常会使用IDE来完成。比如在PyCharm中,你可以Ctrl+鼠标点击进入函数的源代码。那如果没有IDE呢?当我们想使用一个函 ... [详细]
  • 本文详细介绍了如何使用 Yii2 的 GridView 组件在列表页面实现数据的直接编辑功能。通过具体的代码示例和步骤,帮助开发者快速掌握这一实用技巧。 ... [详细]
  • Android 渐变圆环加载控件实现
    本文介绍了如何在 Android 中创建一个自定义的渐变圆环加载控件,该控件已在多个知名应用中使用。我们将详细探讨其工作原理和实现方法。 ... [详细]
  • 本文介绍如何通过Windows批处理脚本定期检查并重启Java应用程序,确保其持续稳定运行。脚本每30分钟检查一次,并在需要时重启Java程序。同时,它会将任务结果发送到Redis。 ... [详细]
  • 本章将深入探讨移动 UI 设计的核心原则,帮助开发者构建简洁、高效且用户友好的界面。通过学习设计规则和用户体验优化技巧,您将能够创建出既美观又实用的移动应用。 ... [详细]
  • RecyclerView初步学习(一)
    RecyclerView初步学习(一)ReCyclerView提供了一种插件式的编程模式,除了提供ViewHolder缓存模式,还可以自定义动画,分割符,布局样式,相比于传统的ListVi ... [详细]
  • 苹果新专利或将引领无边框手机时代
    苹果公司最近公布了一项新的专利技术,该技术能够在设备屏幕中嵌入光线传感器,这标志着苹果在实现无边框手机设计上迈出了重要一步。这一创新将极大提升手机的屏占比,并可能为未来的iPhone带来革命性的变化。 ... [详细]
  • 在macOS环境下使用Electron Builder进行应用打包时遇到签名验证失败的问题,具体表现为签名后spctl命令检测到应用程序未通过公证(Notarization)。本文将详细探讨该问题的原因及解决方案。 ... [详细]
  • 本文详细介绍 GBase ADO.NET 中 FillSchema 方法的多个重载函数,该方法用于填充 DataSet 或 DataTable 的架构,并根据指定的 SchemaType 配置架构。通过这些重载函数,开发人员可以灵活地控制数据架构的生成方式。 ... [详细]
  • CentOS 7 磁盘与文件系统管理指南
    本文详细介绍了磁盘的基本结构、接口类型、分区管理以及文件系统格式化等内容,并提供了实际操作步骤,帮助读者更好地理解和掌握 CentOS 7 中的磁盘与文件系统管理。 ... [详细]
  • 本文详细记录了在基于Debian的Deepin 20操作系统上安装MySQL 5.7的具体步骤,包括软件包的选择、依赖项的处理及远程访问权限的配置。 ... [详细]
  • Windows服务与数据库交互问题解析
    本文探讨了在Windows 10(64位)环境下开发的Windows服务,旨在定期向本地MS SQL Server (v.11)插入记录。尽管服务已成功安装并运行,但记录并未正确插入。我们将详细分析可能的原因及解决方案。 ... [详细]
author-avatar
手机用户2502856985
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有