一、Glide定义 Glide也是基于Flash的,并且它并没有模仿Windows或其它类似的桌面环境,取而代之的是它提供了独特的界面。它允许用户上传多达30GB的文件,用户能够阅读RSS feed,管理书签,约会,聊天,创建文档,浏览图片等。Glide的设计非常不错,然而某些方面跟Desktoptwo有些类似。首先,某些应用程序以弹出窗口方式打开,似乎并不是非要这么做不可。此外,其中有些应用程序没有像其它界面一样经过美化。有些应用程序,如日历就完全不能正常运行,一点击就会出错。
二、Glide特点
- 支持Memory和Disk图片缓存。
- 支持gif和webp格式图片。
- 根据Activity/Fragment生命周期自动管理请求。
- 使用Bitmap Pool可以使Bitmap复用。
- 对于回收的Bitmap会主动调用recycle,减小系统回收压力。
- Picasso.with(这里只能传入上下文) .
- Glide.with,后面可以传入上下文,activity实例,FragmentActivity实例,Fragement.传入的对象要比前者多.
Picasso采用的ARGB-8888,Glide采用的是RGB-565相对而言,Picasso加载的是全图,
图片质量和清晰对要比Glide的要高,但是,因为加载的采样率过高,导致,出现OOM异常的概率要比Glide要大很多.
文章图片
3.加载Gif图片(备注:Gif图片消耗太对内存,尽量谨慎使用):
- Picasso不能加载git图片
- Glide可以加载缓存图片
- Picasso缓存的是全尺寸,而 Glide的缓存的更ImageView的尺寸相同.讲ImageView调整为不同的大小,不管大小如何设置,Picasso只缓存一个全尺寸的,Glide则不同,他会为每种大小不一致的ImageView都缓存一次.
- Glide的这个特点,让加载显得特别的快,而Picasso则因为需要在显示之前重新调整大小而导致一些延迟,(即便是添加了noFade)
安卓图片显示的质量配置主要分为四种:
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
【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,同样会占用那么多内存,这就是为什么需要做图片压缩的原因了
文章图片
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 super A, R> 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核心技术进阶、实战笔记。干我们这行,啥时候懈怠,就意味着长进的停止,长进的停止就意味着被淘汰,只能往前冲,直到凤凰涅槃的一天!
推荐阅读
- Android性能优化|微信图片高效传输,原来用的是——Bitmap压缩方案
- Android开发|别人的H5页面为什么飞快(之 Webview 优化)
- Android性能优化|卡顿优化的重中之重“垃圾回收算法“,万文肝货
- Android开发|我的Android网络优化为什么不行()
- Android开发|为什么做内存优化,优化着手点在哪里()
- Android布局|如何实战一个Android UI布局,这篇带你玩转UI
- ClickHouse 极简教程-图文详解原理系列ClickHouse 主键索引的存储结构与查询性能优化...
- 网站优化-WEBSITE|性能优化 = 改改代码(- 学习/实践)
- Android|Android 性能优化之——高性能使用图片全面总结