Android性能优化|腾讯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要大很多.
Android性能优化|腾讯T10最喜欢的Glide巨图加载机制,入坑APP深度优化必学原理分析
文章图片

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代表红绿蓝:即颜色)
Android性能优化|腾讯T10最喜欢的Glide巨图加载机制,入坑APP深度优化必学原理分析
文章图片

4.2图片默认质量
Picasso的默认质量是 ARGB_8888
Glide的默认质量则为 RGB_565
【Android性能优化|腾讯T10最喜欢的Glide巨图加载机制,入坑APP深度优化必学原理分析】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,同样会占用那么多内存,这就是为什么需要做图片压缩的原因了
Android性能优化|腾讯T10最喜欢的Glide巨图加载机制,入坑APP深度优化必学原理分析
文章图片

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 load(String string) { return (DrawableTypeRequest) fromString().load(string); } public DrawableTypeRequest fromString() { return loadGeneric(String.class); } private DrawableTypeRequest loadGeneric(Class modelClass) { ModelLoader streamModelLoader = Glide.buildStreamModelLoader(modelClass, context); ModelLoader 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(modelClass, streamModelLoader, fileDescriptorModelLoader, context, glide, requestTracker, lifecycle, optionsApplier)); } public DrawableRequestBuilder load(ModelType model) { super.load(model); return this; } public GenericRequestBuilder load(ModelType model) { this.model = model; isModelSet = true; return this; }

其中继承关系是GenericRequestBuilder<-DrawbleRequestBuilder<-DrawbleTypeRequest,load方法最终会返回一个DrawbleTypeRequest对象
// DrawbleTyperequest.java public BitmapTypeRequest asBitmap() { return optionsApplier.apply(new BitmapTypeRequest(this, streamModelLoader, fileDescriptorModelLoader, optionsApplier)); } // DrawbleTypeRequest.java public GifTypeRequest asGif() { return optionsApplier.apply(new GifTypeRequest(this, streamModelLoader, optionsApplier)); }

指定静态图片返回BitmapTypeRequest,动态图片返回GifTypeRequest,不指定默认返回DrawbleTypeRequest,总之,load方法返回一个DrawbleTypeRequest对象
3、into
调用父类GenericRequestBuilder的into方法,
public Target 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 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 target) { if (priority == null) { priority = Priority.NORMAL; } return buildRequestRecursive(target, null); } private Request buildRequestRecursive(Target 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 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 implements Request, SizeReadyCallback, ResourceCallback { public static GenericRequest obtain( LoadProvider loadProvider, A model, Key signature, Context context, Priority priority, Target target, float sizeMultiplier, Drawable placeholderDrawable, int placeholderResourceId, Drawable errorDrawable, int errorResourceId, Drawable fallbackDrawable, int fallbackResourceId, RequestListener requestListener, RequestCoordinator requestCoordinator, Engine engine, Transformation transformation, Class transcodeClass, boolean isMemoryCacheable, GlideAnimationFactory animationFactory, int overrideWidth, int overrideHeight, DiskCacheStrategy diskCacheStrategy) { @SuppressWarnings("unchecked") GenericRequest request = (GenericRequest) REQUEST_POOL.poll(); if (request == null) { request = new GenericRequest(); } 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 extends ViewTarget 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 modelLoader = loadProvider.getModelLoader(); final DataFetcher dataFetcher = modelLoader.getResourceFetcher(model, width, height); if (dataFetcher == null) { onException(new Exception("Failed to load model: \'" + model + "\'")); return; } ResourceTranscoder 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 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 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核心技术进阶、实战笔记。干我们这行,啥时候懈怠,就意味着长进的停止,长进的停止就意味着被淘汰,只能往前冲,直到凤凰涅槃的一天!

    推荐阅读