腾讯T10最喜欢的Glide巨图加载机制,入坑APP深度优化必学原理分析

一、Glide定义

Glide也是基于Flash的,并且它并没有模仿Windows或其它类似的桌面环境,取而代之的是它提供了独特的界面。它允许用户上传多达30GB的文件,用户能够阅读RSS feed,管理书签,约会,聊天,创建文档,浏览图片等。Glide的设计非常不错,然而某些方面跟Desktoptwo有些类似。首先,某些应用程序以弹出窗口方式打开,似乎并不是非要这么做不可。此外,其中有些应用程序没有像其它界面一样经过美化。有些应用程序,如日历就完全不能正常运行,一点击就会出错。

二、Glide特点

  1. 支持Memory和Disk图片缓存。
  2. 支持gif和webp格式图片。
  3. 根据Activity/Fragment生命周期自动管理请求。
  4. 使用Bitmap Pool可以使Bitmap复用。
  5. 对于回收的Bitmap会主动调用recycle,减小系统回收压力。

三、Glide和Picasso他们的对比的优缺点

1.Picasso和Glide的withi后面的参数不同

  • Picasso.with(这里只能传入上下文) .
  • Glide.with,后面可以传入上下文,activity实例,FragmentActivity实例,Fragement.传入的对象要比前者多.

2.加载后图片质量不同

Picasso采用的ARGB-8888,Glide采用的是RGB-565相对而言,Picasso加载的是全图,

图片质量和清晰对要比Glide的要高,但是,因为加载的采样率过高,导致,出现OOM异常的概率要比Glide要大很多.
在这里插入图片描述

3.加载Gif图片(备注:Gif图片消耗太对内存,尽量谨慎使用):

  • Picasso不能加载git图片
  • Glide可以加载缓存图片

4.缓存策略和加载速度.

  • Picasso缓存的是全尺寸,而 Glide的缓存的更ImageView的尺寸相同.讲ImageView调整为不同的大小,不管大小如何设置,Picasso只缓存一个全尺寸的,Glide则不同,他会为每种大小不一致的ImageView都缓存一次.
  • Glide的这个特点,让加载显得特别的快,而Picasso则因为需要在显示之前重新调整大小而导致一些延迟,(即便是添加了noFade)

四、Glide图片加载原理分析

4.1图片质量分类

安卓图片显示的质量配置主要分为四种:

ARGB_8888 :32位图,带透明度,每个像素占4个字节
ARGB_4444 :16位图,带透明度,每个像素占2个字节
RGB_565 :16位图,不带透明度,每个像素占2个字节
ALPHA_8 :32位图,只有透明度,不带颜色,每个像素占4个字节
(A代表透明度,RGB代表红绿蓝:即颜色)
在这里插入图片描述

4.2图片默认质量

Picasso的默认质量是 ARGB_8888
Glide的默认质量则为 RGB_565

Glide默认的图片质量比Picasso稍微差一些。

加载一张4000 * 2000(一般手机拍摄的都超过这个像素)的图片

4.3占用内存

Picasso需要占用的内存为: 32MB

4000 * 2000 * 4 / 1024 / 1024 = 30 (MB)

Glide需要占用的内存为: 16MB

4000 * 2000 * 2 / 1024 / 1024 = 15 (MB)

也就是说只要同时加载几张图片,你的应用就会OOM(内存溢出了),最恐怖的是就算你的ImageView的宽高只有10px,同样会占用那么多内存,这就是为什么需要做图片压缩的原因了
在这里插入图片描述

4.4加载图片分析

1、with

// Glide.java
  public static RequestManager with(FragmentActivity activity) {
    
    
        RequestManagerRetriever retriever = RequestManagerRetriever.get();
        return retriever.get(activity);
    }
//  RequestManagerRetriever.java
public static RequestManagerRetriever get() {
    
     return INSTANCE; }
 
// Visible for testing. RequestManagerRetriever() { handler = new Handler(Looper.getMainLooper(), this /* Callback */); }

RequestManagerRetriever是一个单例模式,get方法返回RequestManager,get方法可以传递acitivity fragment context等

    public RequestManager get(FragmentActivity activity) {
    
    
        if (Util.isOnBackgroundThread()) {
    
    
            return get(activity.getApplicationContext());
        } else {
    
    
            assertNotDestroyed(activity);
            FragmentManager fm = activity.getSupportFragmentManager();
            return supportFragmentGet(activity, fm);
        }
    }
    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);
    }

如果传递的是Application,那么通过getApplicationManager()方法创建一个RequestManager对象并返回,这是因为application的生命周期比较长,只要应用程序没有被杀死,Glide就可以工作,加载图片

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

如果传递的是非application,会在当前的activity创建一个隐藏的fragment,这是因为Glide无法知道activity fragment的生命周期,如正在网络加载图片时,activity被销毁,这个时候应该退出加载图片,因为fragment的生命周期同acitivity,activity退出后,fragment收到通知,停止加载图片

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

2、load

public class RequestManager implements LifecycleListener {
    
    

// RequestManager
    public DrawableTypeRequest<String> load(String string) {
    
    
        return (DrawableTypeRequest<String>) fromString().load(string);
    }
public DrawableTypeRequest<String> fromString() {
    
    
        return loadGeneric(String.class);
    }
    private <T> DrawableTypeRequest<T> loadGeneric(Class<T> modelClass) {
    
    
        ModelLoader<T, InputStream> streamModelLoader = Glide.buildStreamModelLoader(modelClass, context);
        ModelLoader<T, ParcelFileDescriptor> fileDescriptorModelLoader =
                Glide.buildFileDescriptorModelLoader(modelClass, context);
        if (modelClass != null && streamModelLoader == null && fileDescriptorModelLoader == null) {
    
    
            throw new IllegalArgumentException("Unknown type " + modelClass + ". You must provide a Model of a type for"
                    + " which there is a registered ModelLoader, if you are using a custom model, you must first call"
                    + " Glide#register with a ModelLoaderFactory for your custom model class");
        }
 
        return optionsApplier.apply(
                new DrawableTypeRequest<T>(modelClass, streamModelLoader, fileDescriptorModelLoader, context,
                        glide, requestTracker, lifecycle, optionsApplier));
    }
    public DrawableRequestBuilder<ModelType> load(ModelType model) {
    
    
        super.load(model);
        return this;
    }
    public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> load(ModelType model) {
    
    
        this.model = model;
        isModelSet = true;
        return this;
    }

其中继承关系是GenericRequestBuilder<-DrawbleRequestBuilder<-DrawbleTypeRequest,load方法最终会返回一个DrawbleTypeRequest对象

// DrawbleTyperequest.java
    public BitmapTypeRequest<ModelType> asBitmap() {
    
    
        return optionsApplier.apply(new BitmapTypeRequest<ModelType>(this, streamModelLoader,
                fileDescriptorModelLoader, optionsApplier));
    }
// DrawbleTypeRequest.java
    public GifTypeRequest<ModelType> asGif() {
    
    
        return optionsApplier.apply(new GifTypeRequest<ModelType>(this, streamModelLoader, optionsApplier));
    }

指定静态图片返回BitmapTypeRequest,动态图片返回GifTypeRequest,不指定默认返回DrawbleTypeRequest,总之,load方法返回一个DrawbleTypeRequest对象

3、into

调用父类GenericRequestBuilder的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));
}
public 
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;
}

Request是用来发出网络加载图片请求的,是Glide非常重要的一个组件,看一下如何构建Requst

    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);
        }
    }
 
    private Request obtainRequest(Target<TranscodeType> target, float sizeMultiplier, Priority priority,
            RequestCoordinator requestCoordinator) {
    
    
        return GenericRequest.obtain(
                loadProvider,
                model,
                signature,
                context,
                priority,
                target,
                sizeMultiplier,
                placeholderDrawable,
                placeholderId,
                errorPlaceholder,
                errorId,
                fallbackDrawable,
                fallbackResource,
                requestListener,
                requestCoordinator,
                glide.getEngine(),
                transformation,
                transcodeClass,
                isCacheable,
                animationFactory,
                overrideWidth,
                overrideHeight,
                diskCacheStrategy);
    }

最后会调到 GenericRequest的obtain()方法,它的参数有一些是我们之前在load方法中设置进去的,如errorplaceholder,diskCacheStrategy,placeholderId等

  public final class GenericRequest<A, T, Z, R> implements Request, SizeReadyCallback,
        ResourceCallback {
    
        
    public static <A, T, Z, R> GenericRequest<A, T, Z, R> obtain(
            LoadProvider<A, T, Z, R> loadProvider,
            A model,
            Key signature,
            Context context,
            Priority priority,
            Target<R> target,
            float sizeMultiplier,
            Drawable placeholderDrawable,
            int placeholderResourceId,
            Drawable errorDrawable,
            int errorResourceId,
            Drawable fallbackDrawable,
            int fallbackResourceId,
            RequestListener<? super A, R> requestListener,
            RequestCoordinator requestCoordinator,
            Engine engine,
            Transformation<Z> transformation,
            Class<R> transcodeClass,
            boolean isMemoryCacheable,
            GlideAnimationFactory<R> animationFactory,
            int overrideWidth,
            int overrideHeight,
            DiskCacheStrategy diskCacheStrategy) {
    
    
        @SuppressWarnings("unchecked")
        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(loadProvider,
                model,
                signature,
                context,
                priority,
                target,
                sizeMultiplier,
                placeholderDrawable,
                placeholderResourceId,
                errorDrawable,
                errorResourceId,
                fallbackDrawable,
                fallbackResourceId,
                requestListener,
                requestCoordinator,
                engine,
                transformation,
                transcodeClass,
                isMemoryCacheable,
                animationFactory,
                overrideWidth,
                overrideHeight,
                diskCacheStrategy);
        return request;
    }

obtain()方法实际上获得的就是一个GenericRequest对象,到这里我们获得了一个request对象,然后看一下这个request对象是怎么执行的,requestTracker.runRequest()方法来去执行这个Request

/**
 * Starts tracking the given request.
 */
public void runRequest(Request request) {
    requests.add(request);
    if (!isPaused) {
        request.begin();
    } else {
        pendingRequests.add(request);
    }
}

request.begin(),request是一个接口,它的实现类是 GenericRequest中的begin()方法

@Override
public void begin() {
    startTime = LogTime.getLogTime();
    if (model == null) { // model是图片的url
        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));
    }
}
@Override
public void onException(Exception e) {
    if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "load failed", e);
    }

    status = Status.FAILED;
    //TODO: what if this is a thumbnail request?
    if (requestListener == null || !requestListener.onException(e, model, target, isFirstReadyResource())) {
        setErrorPlaceholder(e);
    }
}
private void setErrorPlaceholder(Exception e) {
    if (!canNotifyStatusChanged()) {
        return;
    }

    Drawable error = model == null ? getFallbackDrawable() : null;
    if (error == null) {
      error = getErrorDrawable();
    }
    if (error == null) {
        error = getPlaceholderDrawable();
    }
    target.onLoadFailed(e, error);
}

在onLoadFailed中将显示异常(错误)占位图,即url为空时显示异常或者placeholder占位图

public abstract class ImageViewTarget<Z> extends ViewTarget<ImageView, Z> implements GlideAnimation.ViewAdapter {
    
    
 
    ...
 
    @Override
    public void onLoadStarted(Drawable placeholder) {
    
    
        view.setImageDrawable(placeholder);
    }
 
    @Override
    public void onLoadFailed(Exception e, Drawable errorDrawable) {
    
    
        view.setImageDrawable(errorDrawable);
    }
 
    ...
}

在begin方法中还会调到onSizeReady方法,传入宽高,可能是用户override指定的,也可能是通过imageview计算的

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

此处省略几万行。。。。。。最后onSizeReady进入到真正的的网络请求代码

private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl,
    Map<String, String> headers) throws IOException {
  if (redirects >= MAXIMUM_REDIRECTS) {
    throw new HttpException("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 HttpException("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(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;
  }
  final int statusCode = urlConnection.getResponseCode();
  if (isHttpOk(statusCode)) {
    return getStreamForSuccessfulRequest(urlConnection);
  } else if (isHttpRedirect(statusCode)) {
    String redirectUrlString = urlConnection.getHeaderField("Location");
    if (TextUtils.isEmpty(redirectUrlString)) {
      throw new HttpException("Received empty or null redirect url");
    }
    URL redirectUrl = new URL(url, redirectUrlString);
    // Closing the stream specifically is required to avoid leaking ResponseBodys in addition
    // to disconnecting the url connection below. See #2352.
    cleanup();
    return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
  } else if (statusCode == INVALID_STATUS_CODE) {
    throw new HttpException(statusCode);
  } else {
    throw new HttpException(urlConnection.getResponseMessage(), statusCode);
  }
}

服务器返回InputStream,Glide进行解析,最终得到bitmap->drawable并显示。

五、总结

Glide的架构扩展性高,但是难以理解,各种接口、泛型,需要一定的学习才能熟练运用。

android开发大量进阶技术知识,于Android核心技术进阶、实战笔记。干我们这行,啥时候懈怠,就意味着长进的停止,长进的停止就意味着被淘汰,只能往前冲,直到凤凰涅槃的一天!

猜你喜欢

转载自blog.csdn.net/Androidxiaofei/article/details/125223500