Glide-源码详解

Posted 夏雨_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Glide-源码详解相关的知识,希望对你有一定的参考价值。

前言:

之前的文章中,笔者介绍了很多Glide的使用方法,但是由于Glide框架封装得太好了,很多人在使用的时候,只是知其然不知其所以然,为了不要仅仅成为”cv工程师”,只会复制粘贴,所以这篇文章我们就一起来研究一下Glide的源码,看看Glide到底是怎么将一张图片加载出来的~

Glide 系列目录

前方高能预警,本文篇幅较长,阅读需要耐心

本文基于Glide 3.7.0版本

一.Glide的构造

//Glide.java

 Glide(Engine engine, MemoryCache memoryCache, BitmapPool bitmapPool, Context context, DecodeFormat decodeFormat) 
              ...
    
Glide是通过GlideBuilder中的createGlide方法生成的(核心代码如下)
//GlideBuilder.java

Glide createGlide() 
        ...
        return new Glide(engine, memoryCache, bitmapPool, context, decodeFormat);
    
Glide的构造参数主要有四个,都是通过createGlide生成的.
  • MemoryCache 内存缓存

  • BitmapPool 图片池

  • DecodeFormat 图片格式

  • Engine 引擎类

1.MemoryCache :内存缓存 LruResourceCache

//MemorySizeCalculator.java

final int maxSize = getMaxSize(activityManager);

private static int getMaxSize(ActivityManager activityManager) 
    //每个进程可用的最大内存
    final int memoryClassBytes = activityManager.getMemoryClass() * 1024 * 1024;

    //判断是否低配手机
    final boolean isLowMemoryDevice = isLowMemoryDevice(activityManager);

    return Math.round(memoryClassBytes
            * (isLowMemoryDevice ? LOW_MEMORY_MAX_SIZE_MULTIPLIER : MAX_SIZE_MULTIPLIER));

最大内存:如果是低配手机,就每个进程可用的最大内存乘以0.33,否则就每个进程可用的最大内存乘以0.4
//MemorySizeCalculator.java

int screenSize = screenDimensions.getWidthPixels() * screenDimensions.getHeightPixels()
                * BYTES_PER_ARGB_8888_PIXEL;(宽*高*4)
int targetPoolSize = screenSize * BITMAP_POOL_TARGET_SCREENS;(宽*高*4*4)
int targetMemoryCacheSize = screenSize * MEMORY_CACHE_TARGET_SCREENS;(宽*高*4*2)

//判断是否超过最大值,否则就等比缩小
if (targetMemoryCacheSize + targetPoolSize <= maxSize) 
    memoryCacheSize = targetMemoryCacheSize;
    bitmapPoolSize = targetPoolSize;
 else 
    int part = Math.round((float) maxSize / (BITMAP_POOL_TARGET_SCREENS + MEMORY_CACHE_TARGET_SCREENS));
    memoryCacheSize = part * MEMORY_CACHE_TARGET_SCREENS;
    bitmapPoolSize = part * BITMAP_POOL_TARGET_SCREENS;

targetPoolSize 和 targetMemoryCacheSize 之和不能超过maxSize 否则就等比缩小
//GlideBuilder.java

memoryCache = new LruResourceCache(calculator.getMemoryCacheSize());
内存缓存用的是targetMemoryCacheSize (即一般是缓存大小是屏幕的宽 * 高 * 4 * 2)

2.BitmapPool 图片池 LruBitmapPool

int size = calculator.getBitmapPoolSize();
bitmapPool = new LruBitmapPool(size);
图片池用的是targetPoolSize(即一般是缓存大小是屏幕的宽*高*4*4)

3.DecodeFormat 图片格式

DecodeFormat DEFAULT = PREFER_RGB_565
默认是RGB_565

4.Engine 引擎类

//GlideBuilder.java

engine = new Engine(memoryCache, diskCacheFactory, diskCacheService, sourceService);
engine 里面主要参数
  • 内存缓存 memoryCache
  • 本地缓存 diskCacheFactory
  • 处理源资源的线程池 sourceService
  • 处理本地缓存的线程池 diskCacheService

(1)memoryCache:内存缓存 LruBitmapPool

上面已经做了介绍

(2)diskCacheFactory:本地缓存 DiskLruCacheFactory

//DiskCache.java

/** 250 MB of cache. */
        int DEFAULT_DISK_CACHE_SIZE = 250 * 1024 * 1024;
        String DEFAULT_DISK_CACHE_DIR = "image_manager_disk_cache";
默认大小:250 MB
默认目录:image_manager_disk_cache

(3)sourceService 处理源资源的线程池 (ThreadPoolExecutor的子类)

  final int cores = Math.max(1, Runtime.getRuntime().availableProcessors());//获得可用的处理器个数
  sourceService = new FifoPriorityThreadPoolExecutor(cores);
线程池的核心线程数量等于获得可用的处理器个数

(4)diskCacheService 处理本地缓存的线程池 (ThreadPoolExecutor的子类)

diskCacheService = new FifoPriorityThreadPoolExecutor(1);
线程池的核心线程数量为1

二.with方法

with方法有很多重载,最后会返回一个RequestManager
//Glide.java

/**
 * @see #with(android.app.Activity)
 * @see #with(android.app.Fragment)
 * @see #with(android.support.v4.app.Fragment)
 * @see #with(android.support.v4.app.FragmentActivity)
 *
 * @param context Any context, will not be retained.
 * @return A RequestManager for the top level application that can be used to start a load.
 */
public static RequestManager with(Context context) 
    RequestManagerRetriever retriever = RequestManagerRetriever.get();
    return retriever.get(context);


就算你传入的是Context ,这里也会根据你Context 实际的类型,走不同的分支
//RequestManagerRetriever.java

public RequestManager get(Context context) 
    if (context == null) 
        throw new IllegalArgumentException("You cannot start a load on a null Context");
     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());
        
    
    return getApplicationManager(context);

这里以FragmentActivity为例,最后会创建一个无界面的Fragment,即SupportRequestManagerFragment ,让请求和你的activity的生命周期同步
//RequestManagerRetriever.java

public RequestManager get(FragmentActivity activity) 
    if (Util.isOnBackgroundThread()) 
        return get(activity.getApplicationContext());
     else 
        assertNotDestroyed(activity);
        FragmentManager fm = activity.getSupportFragmentManager();
        return supportFragmentGet(activity, fm);
    

RequestManager supportFragmentGet(Context context, FragmentManager fm) 
    SupportRequestManagerFragment current = getSupportRequestManagerFragment(fm);
    RequestManager requestManager = current.getRequestManager();
    if (requestManager == null) 
        requestManager = new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());
        current.setRequestManager(requestManager);
    
    return requestManager;

这里需要注意一下,如果你是在子线程调用with方法,或者传入的Context是Application的话,请求是跟你的Application的生命周期同步
//RequestManagerRetriever.java

private RequestManager getApplicationManager(Context context) 
    // Either an application context or we're on a background thread.
    if (applicationManager == null) 
        synchronized (this) 
            if (applicationManager == null) 
                // Normally pause/resume is taken care of by the fragment we add to the fragment or activity.
                // However, in this case since the manager attached to the application will not receive lifecycle
                // events, we must force the manager to start resumed using ApplicationLifecycle.
                applicationManager = new RequestManager(context.getApplicationContext(),
                        new ApplicationLifecycle(), new EmptyRequestManagerTreeNode());
            
        
    
    return applicationManager;

三.load方法

这里方法也有很多重载
//RequestManager.java

public DrawableTypeRequest<String> load(String string) 
    return (DrawableTypeRequest<String>) fromString().load(string);

但是最后都会返回一个DrawableTypeRequest (继承了DrawableRequestBuilder)
DrawableRequestBuilder就是支持链式调用的一个类,我们平时有类似的需求的时候也可以模仿这样的处理方式,把一些非必须参数用链式调用的方式来设置

四.into方法

//GenericRequestBuilder.java    

public Target<TranscodeType> into(ImageView view) 
        Util.assertMainThread();
        if (view == null) 
            throw new IllegalArgumentException("You must pass in a non null View");
        
        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.
            
        
        return into(glide.buildImageViewTarget(view, transcodeClass));
    
这里有三点需要注意的:
  • 1.Util.assertMainThread();这里会检查是否主线程,不是的话会抛出异常,所以into方法必须在主线程中调用.

  • 2.当你没有调用transform方法,并且你的ImageView设置了ScaleType,那么他会根据你的设置,对图片做处理(具体处理可以查看DrawableRequestBuilder的applyCenterCrop或者applyFitCenter方法,我们自己自定义BitmapTransformation也可以参考这里的处理).

  • 3.view在这里被封装成一个Target.

    //GenericRequestBuilder.java        
    
    public <Y extends Target<TranscodeType>> Y into(Y target) 
        Util.assertMainThread();
        if (target == null) 
            throw new IllegalArgumentException("You must pass in a non null Target");
        
        if (!isModelSet) 
            throw new IllegalArgumentException("You must first set a model (try #load())");
        
        Request previous = target.getRequest();
        if (previous != null) 
            previous.clear();
            requestTracker.removeRequest(previous);
            previous.recycle();
        
        Request request = buildRequest(target);
        target.setRequest(request);
        lifecycle.addListener(target);
        requestTracker.runRequest(request);
        return target;
    
    
这里可以看到控件封装成的Target能够获取自身绑定的请求,当发现之前的请求还在的时候,会把旧的请求清除掉,绑定新的请求,这也就是为什么控件复用时不会出现图片错位的问题(这点跟我在Picasso源码中看到的处理方式很相像).
接着在into里面会调用buildRequest方法来创建请求
//GenericRequestBuilder.java    

private Request buildRequest(Target<TranscodeType> target) 
        if (priority == null) 
            priority = Priority.NORMAL;
        
        return buildRequestRecursive(target, null);
    

//GenericRequestBuilder.java

private Request buildRequestRecursive(Target<TranscodeType> target, ThumbnailRequestCoordinator parentCoordinator) 
        if (thumbnailRequestBuilder != null) 
           ...
            Request fullRequest = obtainRequest(target, sizeMultiplier, priority, coordinator);
           ...
            Request thumbRequest = thumbnailRequestBuilder.buildRequestRecursive(target, coordinator);
           ...
            coordinator.setRequests(fullRequest, thumbRequest);
            return coordinator;
         else if (thumbSizeMultiplier != null)            
            ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(parentCoordinator);
            Request fullRequest = obtainRequest(target, sizeMultiplier, priority, coordinator);
            Request thumbnailRequest = obtainRequest(target, thumbSizeMultiplier, getThumbnailPriority(), coordinator);
            coordinator.setRequests(fullRequest, thumbnailRequest);
            return coordinator;
         else 
            // Base case: no thumbnail.
            return obtainRequest(target, sizeMultiplier, priority, parentCoordinator);
        
    
1.这里就是请求的生成,buildRequestRecursive里面if有三个分支,这里是根据你设置thumbnail的情况来判断的,第一个是设置缩略图为新的请求的情况,第二个是设置缩略图为float的情况,第三个就是没有设置缩略图的情况.
前两个设置了缩略图的是有两个请求的,fullRequest和thumbnailRequest,没有设置缩略图则肯定只有一个请求了.
2.请求都是通过obtainRequest方法生成的(这个简单了解一下就行)
//GenericRequestBuilder.java

private Request obtainRequest(Target<TranscodeType> target, float sizeMultiplier, Priority priority,
            RequestCoordinator requestCoordinator) 
        return GenericRequest.obtain(...);
    
REQUEST_POOL是一个队列,当队列中有,那么就从队列中取,没有的话就新建一个GenericRequest
//GenericRequest.java

public static <A, T, Z, R> GenericRequest<A, T, Z, R> 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;
    
回到into方法:当创建了请求后runRequest会调用Request的begin方法,即调用GenericRequest的begin方法
//GenericRequestBuilder.java

 public <Y extends Target<TranscodeType>> Y into(Y target) 

        Request request = buildRequest(target);
      ...
        requestTracker.runRequest(request);
      ...
    

//GenericRequest.java

 public void begin() 
    ...
      if (Util.isValidDimensions(overrideWidth, overrideHeight)) 
            onSizeReady(overrideWidth, overrideHeight);
         else 
            target.getSize(this);
        
    ...
    
最终会调用Engine的load方法
//GenericRequest.java

public void onSizeReady(int width, int height) 
    ...
    loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder,
                priority, isMemoryCacheable, diskCacheStrategy, this);
    ...
    
我们先看load方法的前面一段:
1.首先会尝试从cache里面取,这里cache就是Glide的构造函数里面的MemoryCache(是一个LruResourceCache),如果取到了,就从cache里面删掉,然后加入activeResources中
2.如果cache里面没取到,就会从activeResources中取,activeResources是一个以弱引用为值的map,他是用于存储使用中的资源.之所以在内存缓存的基础上又多了这层缓存,是为了当内存不足而清除cache中的资源中,不会影响使用中的资源.
//Engine.java   

public <T, Z, R> LoadStatus load(...) 
        ...
        EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
        if (cached != null) 
            cb.onResourceReady(cached);
            if (Log.isLoggable(TAG, Log.VERBOSE)) 
                logWithTimeAndKey("Loaded resource from cache", startTime, key);
            
            return null;
        
        EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
        if (active != null) 
            cb.onResourceReady(active);
            if (Log.isLoggable(TAG, Log.VERBOSE)) 
                logWithTimeAndKey("Loaded resource from active resources", startTime, key);
            
            return null;
        
        ...
    
load方法接着会通过EngineJobFactory创建一个EngineJob,里面主要管理里两个线程池,diskCacheService和sourceService,他们就是Glide构造函数中Engine里面创建的那两个线程池.
//Engine.java

public <T, Z, R> LoadStatus load(...) 
            ...
            EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);
           ...
        

//Engine.java
static class EngineJobFactory 
    private final ExecutorService diskCacheService;
    private final ExecutorService sourceService;
    private final EngineJobListener listener;

    public EngineJobFactory(ExecutorService diskCacheService, ExecutorService sourceService,
            EngineJobListener listener) 
        this.diskCacheService = diskCacheService;
        this.sourceService = sourceService;
        this.listener = listener;
    

    public EngineJob build(Key key, boolean isMemoryCacheable) 
        return new EngineJob(key, diskCacheService, sourceService, isMemoryCacheable, listener);
    

接着说load方法,前面创建了EngineJob,接着调用EngineJob的start方法,并将EngineRunnable放到diskCacheService(处理磁盘缓存的线程池里面运行),接着线程池就会调用EngineRunnable的run方法.
//Engine.java

public <T, Z, R> LoadStatus load(...) 
        ...
        EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
        jobs.put(key, engineJob);
        engineJob.addCallback(cb);
        engineJob.start(runnable);
       ...
    

//EngineJob.java

public void start(EngineRunnable engineRunnable) 
    this.engineRunnable = engineRunnable;
    future = diskCacheService.submit(engineRunnable);


//EngineRunnable.java

public void run() 
       ...
        try 
            resource = decode();
         catch (Exception e) 
            if (Log.isLoggable(TAG, Log.VERBOSE)) 
                Log.v(TAG, "Exception decoding", e);
            
            exception = e;
        
       ...
        if (resource == null) 
            onLoadFailed(exception);
         else 
            onLoadComplete(resource);
        
    
run里面调用的是decode()方法,里面会尝试先从磁盘缓存中读取,如果不行就从源资源中读取
//EngineRunnable.java   

private Resource<?> decode() throws Exception 
    if (isDecodingFromCache()) 
        //第一次会走这
        return decodeFromCache();//从磁盘缓存中读取

     else 

        return decodeFromSource();//从源资源中读取

    

我们先来看从磁盘中读取的策略
//EngineRunnable.java

private Resource<?> decodeFromCache() throws Exception 
    Resource<?> result = null;
    try 
        result = decodeJob.decodeResultFromCache();
     catch (Exception e) 
        if (Log.isLoggable(TAG, Log.DEBUG)) 
            Log.d(TAG, "Exception decoding result from cache: " + e);
        
    

    if (result == null) 
        result = decodeJob.decodeSourceFromCache();
    
    return result;

我们可以看到这里先尝试读取处理后的图片(Result),然后再尝试读取原图,但是这里面具体逻辑会根据你设置的磁盘缓存策略来决定是否真的会读取处理图和原图
那么我们再回到EngineRunnable的run()方法中
public void run() 
           ...
            try 
                resource = decode();
             catch (Exception e) 
                if (Log.isLoggable(TAG, Log.VERBOSE)) 
                    Log.v(TAG, "Exception decoding", e);
                
                exception = e;
            
           ...
            if (resource == null) 
                onLoadFailed(exception);
             else 
                onLoadComplete(resource);
            
        
第一次走decode的时候会先尝试从磁盘中获取,如果获取的为null,那么在onLoadFailed方法里面又会把这个run再次放入线程池中,但是这次是放入sourceService(处理源资源的线程池)
//EngineRunnable.java

private void onLoadFailed(Exception e) 
    if (isDecodingFromCache()) 
        stage = Stage.SOURCE;
        manager.submitForSource(this);
     else 
        manager.onException(e);
    


//EngineJob.java

@Override
public void submitForSource(EngineRunnable runnable) 
    future = sourceService.submit(runnable);

接着sourceService里面又会调用调用EngineRunnable的run方法,这次decode里面会走从源资源读取的那条分支
//EngineRunnable.java   

private Resource<?> decode() throws Exception 
    if (isDecodingFromCache()) 
        //第一次会走这
        return decodeFromCache();//从磁盘缓存中读取

     else 
        //第二次会走这
        return decodeFromSource();//从源资源读取

    


//DecodeJob.java

public Resource<Z> decodeFromSource() throws Exception 
    Resource<T> decoded = decodeSource();//获取数据,并解码
    return transformEncodeAndTranscode(decoded);//处理图片

里面主要做了两件事,一个是获取图片,一个是处理图片
1.我们先来看获取图片的decodeSource方法
//DecodeJob.java

private Resource<T> decodeSource() throws Exception 
       ...
        //拉取数据
        final A data = fetcher.loadData(priority);
       ...
        //解码,并保存源资源到磁盘
        decoded = decodeFromSourceData(data);
       ...
    return decoded;


//DecodeJob.java
private Resource<T> decodeFromSourceData(A data) throws IOException 
    final Resource<T> decoded;
    if (diskCacheStrategy.cacheSource()) 
        //解码并保存源资源(图片)到磁盘缓存中
        decoded = cacheAndDecodeSourceData(data);
     else 
        long startTime = LogTime.getLogTime();
        decoded = loadProvider.getSourceDecoder().decode(data, width, height);
        if (Log.isLoggable(TAG, Log.VERBOSE)) 
            logWithTimeAndKey("Decoded from source", startTime);
        
    
    return decoded;

这里调用了DataFetcher的loadData方法来获取数据,DataFetcher有很多实现类,一般来说我们都是从网络中读取数据,我们这边就以HttpUrlFetcher为例
//HttpUrlFetcher.java

@Override
public InputStream loadData(Priority priority) throws Exception 
    return loadDataWithRedirects(glideUrl.toURL(), 0 /*redirects*/, null /*lastUrl*/, glideUrl.getHeaders());


private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl, Map<String, String> headers)
        throws IOException 
    if (redirects >= MAXIMUM_REDIRECTS) 
        throw new IOException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
     else 
        // Comparing the URLs using .equals performs additional network I/O and is generally broken.
        // See http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html.
        try 
            if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) 
                throw new IOException("In re-direct loop");
            
         catch (URISyntaxException e) 
            // Do nothing, this is best effort.
        
    
    urlConnection = connectionFactory.build(url);
    for (Map.Entry<String, String> headerEntry : headers.entrySet()) 
      urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
    
    urlConnection.setConnectTimeout(2500);
    urlConnection.setReadTimeout(2500);
    urlConnection.setUseCaches(false);
    urlConnection.setDoInput(true);

    // Connect explicitly to avoid errors in decoders if connection fails.
    urlConnection.connect();
    if (isCancelled) 
        return null;
    
    final int statusCode = urlConnection.getResponseCode();
    if (statusCode / 100 == 2) 
        //请求成功
        return getStreamForSuccessfulRequest(urlConnection);
     else if (statusCode / 100 == 3) 
        String redirectUrlString = urlConnection.getHeaderField("Location");
        if (TextUtils.isEmpty(redirectUrlString)) 
            throw new IOException("Received empty or null redirect url");
        
        URL redirectUrl = new URL(url, redirectUrlString);
        return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
     else 
        if (statusCode == -1) 
            throw new IOException("Unable to retrieve response code from HttpUrlConnection.");
        
        throw new IOException("Request failed " + statusCode + ": " + urlConnection.getResponseMessage());
    

2.看完了获取图片的方法,我们再来看看处理图片的方法transformEncodeAndTranscode
//DecodeJob.java

private Resource<Z> transformEncodeAndTranscode(Resource<T> decoded) 
   ...
    //对图片做剪裁等处理
    Resource<T> transformed = transform(decoded);
  ...
    //将处理后的图片写入磁盘缓存(会根据配置来决定是否写入)
    writeTransformedToCache(transformed);
 ...
    //转码,转为需要的类型
    Resource<Z> result = transcode(transformed);
 ...
    return result;

Glide的整个加载流程就基本上走完了,整个篇幅还是比较长的,第一次看得时候可能会有点懵逼,需要结合着源码多走几遍才能够更加熟悉.

阅读源码并不是我们最终的目的,我们阅读源码主要有两个目的

一个是能够更加熟悉这个框架,那么使用的时候就更加得心应手了,比如我从源码中发现了很多文章都说错了默认的缓存策略
一个是学习里面的设计模式和思想,应用到我们自己的项目中,比如说面对接口编程,对于不同的数据,用DataFetcher的不同实现类来拉取数据

热门文章

以上是关于Glide-源码详解的主要内容,如果未能解决你的问题,请参考以下文章

Glide-源码详解

如何使用 glide 从 android 中的音频 url 获取缩略图

缩略图在 RecyclerView (Android) 中保持空白。我正确使用 Glide 吗?

Glide高级详解—缓存与解码复用

Glide 4.0.0 RC0 使用详解

PHP生成缩略图--等比缩略图