android面试(15)-图片加载框架glide

图片加载框架有很多,但是glide是google官方推荐的,而且使用起来非常的方便而且功能十分强大;

1.使用方法:

Glide.with(this).load("http://baidu.com").into(new ImageView(this));

简简单单的一句代码,就可以将url转换成图片放置在相应的控件中,很神奇。

2.源码解析:

从with方法开始:

public static RequestManager with(Activity activity) {
    RequestManagerRetriever retriever = RequestManagerRetriever.get();
    return retriever.get(activity);
}

先看这个RequestManager:从名字我们就可以看出,这是一个图片网络请求的管理类;

public class RequestManager implements LifecycleListener

它实现了LifecycleListener接口,这是一个与Context的生命周期绑定的接口,将request与生命周期绑定,这样就可以通过context的生命周期去操作网络请求的开始,暂停等;

再看retriever的get方法

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

这里做了一个判断,如果Glide在子线程中使用或者传入的context是ApplicationContext,那么就与全局的ApplicationContext绑定,如果不是那么创建一个无界面的fragment,即SupportRequestManagerFragment,让请求和你的activity的生命周期同步;

在with方法中,主要是创建了一个requestManager的类,这个是网络请求的管理类,主要负责将所有的类的生命周期与调用其的组件的生命周期相绑定,这也是Glide与其他图片加载框架不一样的地方,它是和组件的生命周期相绑定的。

再看load方法:

load方法比较简单了,它的重载方法有很多,对应了加载图片的不同方式,可以传入String类型的值去加载,也可以通过url地址去加载,还可以通过传入本地文件去加载,但最后都会返回一个DrawableTypeRequest(继承了DrawableRequestBuilder);这个类就是支持链式调用的类,主要是进行一些初始化的操作;

最后看Glide的最核心的方法,into:

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方法并且自己对图片的scaletype进行了设置,那么他会根据你的设置去加载图片;

(3)最后它调用into的方法:

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不好理解,其实target就是对View的一层封装,就把他当成一个view就可以了,代码逻辑很好理解,主要就是对之前请求的彻底清除和对新请求的建立与绑定生命周期,这也就是控件复用时图片不会错位的原因;

关于新建请求是调用buildRequest的方法

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

private Request buildRequestRecursive(Target<TranscodeType> target, ThumbnailRequestCoordinator parentCoordinator) {
    if (thumbnailRequestBuilder != null) {
        if (isThumbnailBuilt) {
            throw new IllegalStateException("You cannot use a request as both the main request and a thumbnail, "
                    + "consider using clone() on the request(s) passed to thumbnail()");
        }
        // Recursive case: contains a potentially recursive thumbnail request builder.
        if (thumbnailRequestBuilder.animationFactory.equals(NoAnimation.getFactory())) {
            thumbnailRequestBuilder.animationFactory = animationFactory;
        }

        if (thumbnailRequestBuilder.priority == null) {
            thumbnailRequestBuilder.priority = getThumbnailPriority();
        }

        if (Util.isValidDimensions(overrideWidth, overrideHeight)
                && !Util.isValidDimensions(thumbnailRequestBuilder.overrideWidth,
                        thumbnailRequestBuilder.overrideHeight)) {
          thumbnailRequestBuilder.override(overrideWidth, overrideHeight);
        }

        ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(parentCoordinator);
        Request fullRequest = obtainRequest(target, sizeMultiplier, priority, coordinator);
        // Guard against infinite recursion.
        isThumbnailBuilt = true;
        // Recursively generate thumbnail requests.
        Request thumbRequest = thumbnailRequestBuilder.buildRequestRecursive(target, coordinator);
        isThumbnailBuilt = false;
        coordinator.setRequests(fullRequest, thumbRequest);
        return coordinator;
    } else if (thumbSizeMultiplier != null) {
        // Base case: thumbnail multiplier generates a thumbnail request, but cannot recurse.
        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);
    }
}

这里使用了递归,判断三个if,第一个if是设置缩略图是新的情况,第二个设置缩略图是float的情况,第三个设置没有缩略图的情况;

不管是什么缩略图,都需要请求网络,而请求网络的都是调用obtainRequest方法

这个方法没什么好讲的,就是对一些网络请求参数的初始化;

回到into方法中,当新建好请求后,就会runRequest;

requestTracker.runRequest(request);
public void runRequest(Request request) {
    requests.add(request);
    if (!isPaused) {
        request.begin();
    } else {
        pendingRequests.add(request);
    }
}

在其中调用了begin

@Override
public void begin() {
    startTime = LogTime.getLogTime();
    if (model == null) {
        onException(null);
        return;
    }

    status = Status.WAITING_FOR_SIZE;
    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
        onSizeReady(overrideWidth, overrideHeight);
    } else {
        target.getSize(this);
    }

    if (!isComplete() && !isFailed() && canNotifyStatusChanged()) {
        target.onLoadStarted(getPlaceholderDrawable());
    }
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logV("finished run method in " + LogTime.getElapsedMillis(startTime));
    }
}

首先它会计算开始的时间,然后将图片加载状态置为等待,然后会对图片大小的合法性进行判断,最后会判断图片的加载状态,如果没有加载完成而且没有加载失败,那么就开始加载图片;

我们先来看看onSizeReady()方法

@Override
public void onSizeReady(int width, int height) {
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));
    }
    if (status != Status.WAITING_FOR_SIZE) {
        return;
    }
    status = Status.RUNNING;

    width = Math.round(sizeMultiplier * width);
    height = Math.round(sizeMultiplier * height);

    ModelLoader<A, T> modelLoader = loadProvider.getModelLoader();
    final DataFetcher<T> dataFetcher = modelLoader.getResourceFetcher(model, width, height);

    if (dataFetcher == null) {
        onException(new Exception("Failed to load model: \'" + model + "\'"));
        return;
    }
    ResourceTranscoder<Z, R> transcoder = loadProvider.getTranscoder();
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime));
    }
    loadedFromMemoryCache = true;
    loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder,
            priority, isMemoryCacheable, diskCacheStrategy, this);
    loadedFromMemoryCache = resource != null;
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime));
    }
}
这个方法让我们接触到glide的最核心的类,Engine类

 loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder,
            priority, isMemoryCacheable, diskCacheStrategy, this);

/**
 * Responsible for starting loads and managing active and cached resources.
 */

从他的注释就可以看出,Engine就是负责开始加载图片和管理活动或缓存中的资源的引擎类;

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";
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
默认大小:250 MB
默认目录:image_manager_disk_cache

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

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

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

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

看它的核心方法load方法:

public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher,
        DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder,
        Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {
    Util.assertMainThread();
    long startTime = LogTime.getLogTime();

    final String id = fetcher.getId();
    EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
            loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
            transcoder, loadProvider.getSourceEncoder());

    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;
    }

    EngineJob current = jobs.get(key);
    if (current != null) {
        current.addCallback(cb);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Added to existing load", startTime, key);
        }
        return new LoadStatus(cb, current);
    }

    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);
    EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
    jobs.put(key, engineJob);
    engineJob.addCallback(cb);
    engineJob.start(runnable);

    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Started new load", startTime, key);
    }
    return new LoadStatus(cb, engineJob);
}

这里首先会从缓存中加载图片,找到的话,就将他加入到活动资源中并从缓存删去,缓存中没有再从活动资源(activeResource)中找,activeResource是一个以弱引用为值的map,它是用于存储使用中的资源,之所以在内存缓存的基础上又多了这层缓存,是为了当内存不足而清除cache中的资源中,不会影响使用中的资源;

然后load方法会通过engineJobFactory获取到EngineJob对象,里面主要管理两个线程池,diskCacheService和sourceService,最后会创建一个EngineRunnable的子线程,将子线程丢到线程池中;

我们来看看这个子线程的run方法

@Override
public void run() {
    if (isCancelled) {
        return;
    }

    Exception exception = null;
    Resource resource = null;
    try {
        resource = decode();
    } 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);
    }
}
通过decode方法尝试读取图片资源,

private Resource decode() throws Exception {
    if (isDecodingFromCache()) {
        return decodeFromCache();
    } else {
        return decodeFromSource();
    }
}

首先会尝试从缓存中读取:

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;
}

通过decodeResultFromCache方法首先尝试读取处理后的图片,如果读不到,那么就读取原图片;

如果从缓存中读取不到,那么就调用decodeFromSource方法从SourceService(处理原图片的线程池)中读取;

public Resource<Z> decodeFromSource() throws Exception {
    Resource<T> decoded = decodeSource();
    return transformEncodeAndTranscode(decoded);
}

这里做了两件事,第一去获取原图片,第二是处理原图片:

private Resource<T> decodeSource() throws Exception {
    Resource<T> decoded = null;
    try {
        long startTime = LogTime.getLogTime();
        final A data = fetcher.loadData(priority);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Fetched data", startTime);
        }
        if (isCancelled) {
            return null;
        }
        decoded = decodeFromSourceData(data);
    } finally {
        fetcher.cleanup();
    }
    return decoded;
}

这里会调用loadData()方法去加载图片,根据DataFetcher的不同,加载方式也不同,最常用的就是从网络中加载图片,所以看看HttpUrlFetcher

private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl, Map, 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> 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());
    }
}

这里是通过android原生的HttpUrlConnection进行网络请求;

再看处理图片的方法:

private Resource<Z> transformEncodeAndTranscode(Resource<T> decoded) {
    long startTime = LogTime.getLogTime();
    Resource<T> transformed = transform(decoded);
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Transformed resource from source", startTime);
    }

    writeTransformedToCache(transformed);

    startTime = LogTime.getLogTime();
    Resource<Z> result = transcode(transformed);
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Transcoded transformed from source", startTime);
    }
    return result;
}

至此,Glide加载图片流程就完成了,这里总结一下:

(1)首先,with方法是获取requestManager类,这个类的主要作用是将glide与整个组件的生命周期绑定在一起;

(2)然后,通过load方法初始化一些参数,这里传入的参数给后面的加载方式提供了依据;

(3)最后,通过into这个核心方法将图片获取并通过一系列处理后加载到相应控件中,这部分逻辑最为复杂,建立请求,然后通过Engine引擎类将请求进行统一的处理,里面开启了两个线程池(diskcacheService)磁盘缓存线程池和(SourceService)源线程池,并且进行了两层缓存,除了我们常见的内存缓存外,还进行了活动资源的缓存,其中内部的网络请求是通过android原生的httpurlConnection去完成的;

你可能感兴趣的:(android,android面试题)