Glide is a fast and efficient open source media management and image loading framework for Android that wraps media decoding, memory and disk caching, and resource pooling into a simple and easy to use interface, 这里引用Github上的一段话,高效的媒体管理和图片加载框架,一句话简单明了,事实上当你git下来编译完成之后,源码没有想象中那么简单,甚至有点复杂。除了okhttp,volley和annotation这些module外还有一些例子,你可以尝试运行一下,不过最主要的还是library这个module; 在阅读源码之前你应该去阅读Github上的官方文档,点击打开链接,文档中说明了glide大多数基本的用法和一些使用技巧,应该重视。

        关于glide的基本使用,官方文档已经说明,这里我们讨论实际使用的另一种场景,我们需要对加载图片或动图做一下简单的封装,让代码使用起来更加方便,而不是每次使用都调用Glide.with(xx).load(xx), 例如下面这段代码:

 RequestManager manager = getRequestManager(object);        if (manager != null) {            String suffix = "";            if (path.contains(".")) {                suffix = path.substring(path.lastIndexOf("."), path.length());            }            RequestBuilder builder = null;            if (".gif".equalsIgnoreCase(suffix)) {                builder = manager.asGif().load(path);            } else {                builder = manager.asBitmap().load(path);            }            RequestOptions options;            if (fileType == FileInfo.FILE_TYPE_ICON) {                options = iconOptions.clone();                options.signature(new MediaStoreSignature(MediaStore.Images.ImageColumns.MIME_TYPE, Constant.BITMAP_MODIFY_TIME, 0));            } else {                options = normalOptions.clone();            }            options.error(defaultResId).placeholder(defaultResId);            if (width > 0 || height > 0) {                options.override(width, height);            }            builder.apply(options);            builder.into(imageView);                  }
    Glide采用的是Builder设计模式,这种模式应该都比较熟悉,上面这段代码中有三个类需要注意RequestManager、RequestOptions和RequestBuilder,首先来说RequestManager,Glide.with(...)这个方法返回的就是这个类的对象,源码说道RequestManager是用来管理和请求的类,它管理的是你传入view的生命周期,我们都知道Glide可以绑定view的生命周期从而添加和释放资源,那么如何管理,在Glide.with(...)的方法里面顺着往下看,RequestManagerRetriever,这个类里面有个方法
private RequestManagerFragment getRequestManagerFragment(final android.app.FragmentManager fm,android.app.Fragment parentHint, boolean isParentVisible) {    RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);    if (current == null) {      current = pendingRequestManagerFragments.get(fm);      if (current == null) {        current = new RequestManagerFragment();        current.setParentFragmentHint(parentHint);        if (isParentVisible) {          current.getGlideLifecycle().onStart();        }        pendingRequestManagerFragments.put(fm, current);        fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();        handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();      }    }    return current;  }

       很好奇为什么Glide.with(...)初始化要创建一个RequestManagerFragment,打开这个类就恍然大悟,有点巧借东风之意,通过传入的activity或fragment创建一个fragment,回调新的fragment的生命周期,就可以绑定activity或fragment的生命周期,不过需要监听多个view的状态,在RequestManager的构造方法里通过Lifecycle这个接口来添加,ActivityFragmentLifecycle这个类具体实现了Lifecycle的方法,用Set集合来保存和销毁这些监听;再来看RequestBuilder,可以把它理解为资源设置的类,as(...)、load(....)还有apply(...)这些方法都是用来设置你要显示资源的属性,apply方法用到RequestOptions这个类,你可以自定义你想要的显示效果,比如默认显示错误的图片,占位符,变换方式,缓存策略(例如只从本地读取),设置缩略图,举个例子,加载头像时需要显示的头像大小相同,可以采用CenterCrop这个内置变换方式缩放到你设置的大小范围;这里不会对基本使用的方法做很详细的分析,相信阅读文档之后,每个人都能熟练使用glide和了解这些方法的功能。

    重要的事说三遍,文档!文档!文档!

    重点说一下into(...)的内部实现

options = options.autoClone();Request request = buildRequest(target, targetListener, options);..........requestManager.clear(target);target.setRequest(request);requestManager.track(target, request);
   首先把传入的view对象转换成Target对象,然后创建Request对象,最后三行代码是重点,绑定当前请求的Request和Target然后回调RequestManager的track方法,下面两行代码说明了track的用意,TargetTracker实现了LifecycleListener同步生命周期,同时用Set集合保存这些Target,runRequest方法发起请求,这就明白了了为什么要转成Target对象,当然是同步请求,回调请求各种结果;
targetTracker.track(target);requestTracker.runRequest(request);

  runRequest调用Request的begin方法开始请求,Request是一个接口,Glide大量使用了接口,这样类之间和耦合性比较低,SingleRequest实现了Request这个接口,来看它的begin方法:

    assertNotCallingCallbacks();    stateVerifier.throwIfRecycled();    startTime = LogTime.getLogTime();    if (model == null) {      if (Util.isValidDimensions(overrideWidth, overrideHeight)) {        width = overrideWidth;        height = overrideHeight;      }      // Only log at more verbose log levels if the user has set a fallback drawable, because      // fallback Drawables indicate the user expects null models occasionally.      int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;      onLoadFailed(new GlideException("Received null model"), logLevel);      return;    }    if (status == Status.RUNNING) {      throw new IllegalArgumentException("Cannot restart a running request");    } ..... if (status == Status.COMPLETE) {      onResourceReady(resource, DataSource.MEMORY_CACHE);      return;    }    // Restarts for requests that are neither complete nor running can be treated as new requests    // and can run again from the beginning.    status = Status.WAITING_FOR_SIZE;    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {      onSizeReady(overrideWidth, overrideHeight);    } else {      target.getSize(this);    }    if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)        && canNotifyStatusChanged()) {      target.onLoadStarted(getPlaceholderDrawable());    }    if (IS_VERBOSE_LOGGABLE) {      logV("finished run method in " + LogTime.getElapsedMillis(startTime));    }
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);    if (cached != null) {      cb.onResourceReady(cached, DataSource.MEMORY_CACHE);      if (VERBOSE_IS_LOGGABLE) {        logWithTimeAndKey("Loaded resource from cache", startTime, key);      }      return null;    }    EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);    if (current != null) {      current.addCallback(cb);      if (VERBOSE_IS_LOGGABLE) {        logWithTimeAndKey("Added to existing load", startTime, key);      }      return new LoadStatus(cb, current);    }

    这段代码有点长,但我们看主要的东西,SingleRequest内部有个枚举类设置了Request的八种状态,曾经有个面试官问我什么时候开始Request,只能说学艺不精,原来在这里OnSizeReady(...),这个方法最终调用Engine这个类的load方法;load方法很长,但主要的逻辑还是很简单,先从缓存去查,包括内存缓存和本地缓存,如果查到就直接返回,否则启动线程池去请求,见EngineJob的start方法。

EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);    if (cached != null) {      cb.onResourceReady(cached, DataSource.MEMORY_CACHE);      if (VERBOSE_IS_LOGGABLE) {        logWithTimeAndKey("Loaded resource from cache", startTime, key);      }      return null;    }    EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);    if (current != null) {      current.addCallback(cb);      if (VERBOSE_IS_LOGGABLE) {        logWithTimeAndKey("Added to existing load", startTime, key);      }      return new LoadStatus(cb, current);    }........jobs.put(key, engineJob);engineJob.addCallback(cb);engineJob.start(decodeJob);
    这里先来看有缓存的情况,进入onResourceReady这个方法,这个方法里会回调每个listener的onResourceReady方法,这里主要看dataSource是如何被加载到ImageView上的,一点点看,target.onResourceReady这个方法里,似乎找到了一点蛛丝马迹,我们知道ImageViewTarget继承Target,在ImageViewTarget里面找到了onResourceReady的实现,最后调用setResource方法,这个方式是个抽象方法,在ImageViewTarget的子类BitmapImageViewTarget找到了它的实现,最终调用到ImageView的setImageBitmap方法,至此完成了图片的加载。
isCallingCallbacks = true;    try {      boolean anyListenerHandledUpdatingTarget = false;      if (requestListeners != null) {        for (RequestListener listener : requestListeners) {          anyListenerHandledUpdatingTarget |=              listener.onResourceReady(result, model, target, dataSource, isFirstResource);        }      }      anyListenerHandledUpdatingTarget |=          targetListener != null              && targetListener.onResourceReady(result, model, target, dataSource, isFirstResource);      if (!anyListenerHandledUpdatingTarget) {        Transition<? super R> animation =            animationFactory.build(dataSource, isFirstResource);        target.onResourceReady(result, animation);      }    } finally {      isCallingCallbacks = false;    }    notifyLoadSuccess();
   最后,也是比较重要的部分,没有缓存情况下的加载,这部分的代码我看了半天也有点迷糊,好在流程和思想还是理清楚了,DecodeJob是一个继承了Runable接口的类,run方法里面处理的情况比较复杂,主要有两种,加载本地资源和网络资源,根据加载类型和是否第一次加载,返回不同类型的生成器,run的源码这里就不复制了,主要看不同generator的startNext方法,以ResourceCacheGenerator为例,modelLoader的DataFetcher类的loadData加载数据;
loadData = null;    boolean started = false;    while (!started && hasNextModelLoader()) {      ModelLoader modelLoader = modelLoaders.get(modelLoaderIndex++);      loadData = modelLoader.buildLoadData(cacheFile,          helper.getWidth(), helper.getHeight(), helper.getOptions());      if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {        started = true;        loadData.fetcher.loadData(helper.getPriority(), this);      }    } 

     DataUriFetcher、StreamLocalUriFetcher,HttpUriFetcher的loadData实现不同,从名称就可以看出加载数据的方式,以HttpUriFetcher的loadData方法为例,调用loadDataWithRedirects方法,这个方法的实现如下,好吧,终于找到网络请求的地方了;

.....urlConnection = connectionFactory.build(url);    for (Map.Entry headerEntry : headers.entrySet()) {      urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());    }    urlConnection.setConnectTimeout(timeout);    urlConnection.setReadTimeout(timeout);    urlConnection.setUseCaches(false);    urlConnection.setDoInput(true);    // Stop the urlConnection instance of HttpUrlConnection from following redirects so that    // redirects will be handled by recursive calls to this method, loadDataWithRedirects.    urlConnection.setInstanceFollowRedirects(false);    // Connect explicitly to avoid errors in decoders if connection fails.    urlConnection.connect();    // Set the stream so that it's closed in cleanup to avoid resource leaks. See #2352.    stream = urlConnection.getInputStream();    if (isCancelled) {      return null;    }........

loadData里最后回调到ResourceCacheGenerator的onDataFetcherReady方法,关键代码如下:

this.currentSourceKey = sourceKey;    this.currentData = data;    this.currentFetcher = fetcher;    this.currentDataSource = dataSource;    this.currentAttemptingKey = attemptedKey;    if (Thread.currentThread() != currentThread) {      runReason = RunReason.DECODE_DATA;      callback.reschedule(this);    } else {      GlideTrace.beginSection("DecodeJob.decodeFromRetrievedData");      try {        decodeFromRetrievedData();      } finally {        GlideTrace.endSection();      }    }

最终调用到DecodePath类decode方法,然后通过ResourceTranscoder的transcode返Resource,DrawableBytesTranscoder的transcode方法为例,根据图片或动态图返回Resource;

public Resource transcode(@NonNull Resource toTranscode,      @NonNull Options options) {    Drawable drawable = toTranscode.get();    if (drawable instanceof BitmapDrawable) {      return bitmapBytesTranscoder.transcode(          BitmapResource.obtain(((BitmapDrawable) drawable).getBitmap(), bitmapPool), options);    } else if (drawable instanceof GifDrawable) {      return gifDrawableBytesTranscoder.transcode(toGifDrawableResource(toTranscode), options);    }    return null;  }

    告一段落,非缓存资源的加载这块的代码确实比较复杂,我也没有深入去仔细的阅读,不过发现一个比较好的思想,解耦。当业务种类比较多的时候,代码往往是比较复杂的,恰好glide这里采用了抽象基类的方式,把请求、显示、初始化甚至加载器这些工厂化,我觉得是一个比较不错的写法,虽然说代码读起来有点难以深入,但是架构清晰;现实中我们经常写出很容易读懂的代码,但是代码量巨大,维护和解决问题相当麻烦。



更多相关文章

  1. Fragment.setArguments()方法向fragment对象传递数据的重要作用
  2. Android——自定义顶部标题栏
  3. 精确获取android软键盘高度
  4. OpenGl-ES2.0 For Android(安卓)读书笔记(一)
  5. Android中button实现onclicklistener事件的两种方法
  6. Android——View.setTag()
  7. Android工程方法数超过65535
  8. Android使用SurfaceView画图
  9. Android学习笔记(12)————利用SQLiteOpenHelper来管理SQLite

随机推荐

  1. android各阶段目标与要求
  2. Android(安卓)Studio中Button等控件的Tex
  3. 更改android avd emulator 按键不可用
  4. Android开发——应用程序生命周期
  5. ViewPager 一屏显示多个效果
  6. AndroidManifest---定义Android清单
  7. Android数字签名解析(二)
  8. 【读书笔记】【Android(安卓)开发艺术探
  9. android 对listview数据的增删改查
  10. Android(安卓)webView 缓存 Cache + HTML