
Glide源码剖析系列
小结:支持图片格式多;Bitmap复用和主动回收;生命周期感应;优秀的缓存策略;加载速度快(Bitmap默认格式RGB565)
Glide.with(this)
.load("https://t7.baidu.com/it/u=3779234486,1094031034&fm=193&f=GIF")
.into(imageView);
通过Android Glide源码解析系列(一)图片加载请求如何感知组件生命周期一文我们知道,with()方法返回的是一个RequestManager对象,说明load()方法是在RequestManager类当中的,所以我们首先要看的就是RequestManager这个类。

load()方法支持多种形式的图片来源,本文以RequestManager类中的 load(String string) 方法为例进行深入分析。
@NonNull
@CheckResult
@Override
public RequestBuilder<Drawable> load(@Nullable String string) {
return asDrawable().load(string);
}
@NonNull
@CheckResult
public RequestBuilder<Drawable> asDrawable() {
return as(Drawable.class);
}
@NonNull
@CheckResult
public <ResourceType> RequestBuilder<ResourceType> as(
@NonNull Class<ResourceType> resourceClass) {
return new RequestBuilder<>(glide, this, resourceClass, context);
}
为了使逻辑更清晰,把上面三个方法合并成一行代码来分析
RequestManager#load(String string)
//相当于
new RequestBuilder<>(glide, RequestManager.this, Drawable.class, context).load(string);
load(String string)最终被分为两步执行:
Drawable.class为参数创建RequestBuilder实例 protected RequestBuilder(
@NonNull Glide glide,
RequestManager requestManager,
Class<TranscodeType> transcodeClass,
Context context) {
this.glide = glide;
this.requestManager = requestManager;
this.transcodeClass = transcodeClass; //重要变量赋值为Drawable.class,构建ImageViewTarget的时候使用
this.context = context;
this.transitionOptions = requestManager.getDefaultTransitionOptions(transcodeClass);
this.glideContext = glide.getGlideContext();
initRequestListeners(requestManager.getDefaultRequestListeners()); //初始化默认请求监听
apply(requestManager.getDefaultRequestOptions()); //应用默认配置信息
}
public RequestBuilder<TranscodeType> load(@Nullable String string) {
return loadGeneric(string);
}
@NonNull
private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
if (isAutoCloneEnabled()) {
return clone().loadGeneric(model);
}
this.model = model; //String变量赋值给model
isModelSet = true;
return selfOrThrowIfLocked();
}
RequestBuilder重要成员变量:
RequestManager#load(String string)分析到现在,似乎都是一些准备工作,真正的图片加载还没有出现。既然“千呼万唤不出来”,那咱们就“打破砂锅查到底”,继续查看RequestBuilder#into(ImageView view)方法
#RequestBuilder.java
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
Util.assertMainThread(); //判断是否在主线程执行
Preconditions.checkNotNull(view);
//把ImageView的scaleType写进requestOptions
//省略代码
return into(
//注释1:构建新的ImageViewTarget
glideContext.buildImageViewTarget(view, transcodeClass),
/*targetListener=*/ null,
requestOptions, //请求选项
Executors.mainThreadExecutor()); //主线程池,切回到主线程显示图片
}
private <Y extends Target<TranscodeType>> Y into(
@NonNull Y target,
@Nullable RequestListener<TranscodeType> targetListener,
BaseRequestOptions<?> options,
Executor callbackExecutor) {
Preconditions.checkNotNull(target);
if (!isModelSet) {
throw new IllegalArgumentException("You must call #load() before calling #into()");
}
//注释2:构建新的加载请求Request
Request request = buildRequest(target, targetListener, options, callbackExecutor);
Request previous = target.getRequest(); //获取目标View上已经存在的旧请求
if (request.isEquivalentTo(previous) //两次的请求相同 && !(跳过内存缓存 && 旧请求已完成)
&& !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
if (!Preconditions.checkNotNull(previous).isRunning()) { //如果旧请求未开始
previous.begin(); //启动旧请求
}
return target;
}
//取消Glide为target准备的所有加载请求,并释放已经
//加载的资源(例如Bitmap),以便它们可以被重用。
requestManager.clear(target);
//利用View.setTag把request请求绑定到target指向的View
target.setRequest(request);
//注释3
requestManager.track(target, request);
return target;
}
private boolean isSkipMemoryCacheWithCompletePreviousRequest(
BaseRequestOptions<?> options, Request previous) {
return !options.isMemoryCacheable() && previous.isComplete();
}
小结:
glideContext.buildImageViewTarget(view, transcodeClass)创建新的ImageViewTarget两次的请求相同 && !(跳过内存缓存 && 旧请求已完成),into()方法直接返回target满足2.1且 && 请求未开始,先启动旧请求再返回target代码注释1分析:
@NonNull
public <X> ViewTarget<ImageView, X> buildImageViewTarget(
@NonNull ImageView imageView, @NonNull Class<X> transcodeClass) {
//transcodeClas = Drawable.class
return imageViewTargetFactory.buildTarget(imageView, transcodeClass);
}
public class ImageViewTargetFactory {
@NonNull
@SuppressWarnings("unchecked")
public <Z> ViewTarget<ImageView, Z> buildTarget(
@NonNull ImageView view, @NonNull Class<Z> clazz) {
if (Bitmap.class.equals(clazz)) {
return (ViewTarget<ImageView, Z>) new BitmapImageViewTarget(view);
} else if (Drawable.class.isAssignableFrom(clazz)) { //transcodeClas = Drawable.class
return (ViewTarget<ImageView, Z>) new DrawableImageViewTarget(view);
} else {
throw new IllegalArgumentException(
"Unhandled class: " + clazz + ", try .as*(Class).transcode(ResourceTranscoder)");
}
}
}
根据transcodeClass类型创建对应的ImageViewTarget,即Drawable.class -> DrawableImageViewTarget
代码注释2分析:Request创建过程比较复杂,还要处理缩略图加载请求、加载错误图片加载请求等等,因此我们只分析主线buildRequest -> buildRequestRecursive -> buildThumbnailRequestRecursive -> obtainRequest -> SingleRequest.obtain -> new SingleRequest()。最终创建出一个SingleRequest实例
代码注释3分析:
#RequestManager
synchronized void track(@NonNull Target<?> target, @NonNull Request request) {
targetTracker.track(target);
requestTracker.runRequest(request);
}
主要做了两件事:
targetTracker和requestTracker都是RequestManager的成员变量,分别负责管理target和request
TargetTracker源码:
/**
* TargetTracker 职责是维护一个Target列表,统一管理所有Target的onStart()、
* onStop()和onDestroy()方法回调事件
*/
public final class TargetTracker implements LifecycleListener {
//维护target列表
private final Set<Target<?>> targets =
Collections.newSetFromMap(new WeakHashMap<Target<?>, Boolean>());
public void track(@NonNull Target<?> target) {
targets.add(target);
}
public void untrack(@NonNull Target<?> target) {
targets.remove(target);
}
@Override
public void onStart() {
for (Target<?> target : Util.getSnapshot(targets)) {
target.onStart();
}
}
@Override
public void onStop() {
for (Target<?> target : Util.getSnapshot(targets)) {
target.onStop();
}
}
@Override
public void onDestroy() {
for (Target<?> target : Util.getSnapshot(targets)) {
target.onDestroy();
}
}
@NonNull
public List<Target<?>> getAll() {
return Util.getSnapshot(targets);
}
public void clear() {
targets.clear();
}
}
RequestTracker部分源码:
public class RequestTracker {
private static final String TAG = "RequestTracker";
// 如果Set直接持有request强引用,可能会发生内存泄漏,因此将request作为key存储在WeakHashMap里
//面,如果这些key不再使用,WeakHashMap会自动异常这些key,从而避免内存泄漏。
private final Set<Request> requests =
Collections.newSetFromMap(new WeakHashMap<Request, Boolean>());
//直接持有未完成和等待开始的request强引用对象,防止在运行过程中被GC
private final Set<Request> pendingRequests = new HashSet<>();
//所有请求是否已经被暂停
private boolean isPaused;
/** Starts tracking the given request. 把请求添加到requests和pendingRequests*/
public void runRequest(@NonNull Request request) {
requests.add(request);
if (!isPaused) {
request.begin();
} else {
request.clear();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Paused, delaying request");
}
pendingRequests.add(request);
}
}
/** Stops any in progress requests. 停止正在运行的请求*/
public void pauseRequests() {
isPaused = true;
for (Request request : Util.getSnapshot(requests)) {
if (request.isRunning()) {
// Avoid clearing parts of requests that may have completed (thumbnails) to avoid blinking
// in the UI, while still making sure that any in progress parts of requests are immediately
// stopped.
request.pause();
pendingRequests.add(request);
}
}
}
/** Starts any not yet completed or failed requests. 开启未完成或失败的请求*/
public void resumeRequests() {
isPaused = false;
for (Request request : Util.getSnapshot(requests)) {
// We don't need to check for cleared here. Any explicit clear by a user will remove the
// Request from the tracker, so the only way we'd find a cleared request here is if we cleared
// it. As a result it should be safe for us to resume cleared requests.
if (!request.isComplete() && !request.isRunning()) {
request.begin();
}
}
pendingRequests.clear();
}
//省略其他方法
}
图片加载请求的管理流程END
Glide管理图片加载请求的方式并不复杂:使用一个请求管理类RequestTracker 维护所有请求列表,并且提供统一操作所有请求的方法(即所有请求的开启、暂停和重启等等方法),RequestManager只要通过一个RequestTracker 对象就能轻轻松松控制图片加载请求。
下一篇文章我们将会学习Glide如何启动图片加载请求,以及图片资源是如何经历重重改造最终显示到界面上,敬请期待!
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
总的来说,我对ruby还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用
我正在使用i18n从头开始构建一个多语言网络应用程序,虽然我自己可以处理一大堆yml文件,但我说的语言(非常)有限,最终我想寻求外部帮助帮助。我想知道这里是否有人在使用UI插件/gem(与django上的django-rosetta不同)来处理多个翻译器,其中一些翻译器不愿意或无法处理存储库中的100多个文件,处理语言数据。谢谢&问候,安德拉斯(如果您已经在rubyonrails-talk上遇到了这个问题,我们深表歉意) 最佳答案 有一个rails3branchofthetolkgem在github上。您可以通过在Gemfi
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚
Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack
在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/
我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为