【笔记整理】Glide 4.9.0 执行流程源码解析

Glide 源码解析与原理总结——Glide.with Glide.with 原理:Glide 初始化并且生成RequestManager,RequestManager 与 空白Fragment 的 lifecycle 生命周期绑定来管理后续的请求。 阅读详情

对于源码的分析,是基于 Glide 最简单的使用的流程进行的:

Glide.with(MainActivity.this).load("url of image resource").into(view);
1. with() 方法

with() 方法会传入当前 activity、fragment 等,目的就是为了获取对应的 context,然后根据该 context 来得到 RequestManagerRetriever 对象,再进一步得到 RequestManager 对象。并且 Glide 会根据我们传入 with() 方法的参数来确定图片加载的生命周期。

这里,对于 context 只有两种情况,Application 类型的与非 Application 类型的。

对于 Application 类型的,会得到 RequestManagerRetriever#applicationManager(虽然实质上也是 RequestManager 对象,只不过比较特殊一点,因为 Application 对象的生命周期即应用程序的生命周期,因此 Glide 并不需要做什么特殊的处理,它自动就是和应用程序的生命周期是同步的,如果应用程序关闭的话,Glide 的加载也会同时终止)。而非 Applicatuion 类型的则会得到普通的 RequestManager 对象。
在内部,先得到 Glide#requestManagerRetriever,然后通过 requestManagerRetriever#get() 方法得到 RequestManager 对象。

在 requestManagerRetriever#get() 执行的过程中,会向当前的 Activity 或者 Fragment 添加一个隐藏的 Fragment(名为 SupportRequestManagerFragment 或者 RequestManagerFragment),这个 Fragment 是为了监听 Activity 或者 Fragment 的生命周期,使得能够感知其销毁,从而停止图片的加载。

public RequestManager get(@NonNull Fragment fragment) {
  Preconditions.checkNotNull(fragment.getActivity(),
        "You cannot start a load on a fragment before it is attached or after it is destroyed");
  if (Util.isOnBackgroundThread()) {
    return get(fragment.getActivity().getApplicationContext());
  } else {
    FragmentManager fm = fragment.getChildFragmentManager();
    // 调用 supportFragmentGet() 来得到 RequestManager 对象
    return supportFragmentGet(fragment.getActivity(), fm, fragment, fragment.isVisible());
  }
}

private RequestManager supportFragmentGet(
    @NonNull Context context,
    @NonNull FragmentManager fm,
    @Nullable Fragment parentHint,
    boolean isParentVisible) {
    
  // 进一步调用 getSupportRequestManagerFragment() 来获取 SupportRequestManagerFragment
  SupportRequestManagerFragment current =
      getSupportRequestManagerFragment(fm, parentHint, isParentVisible);
  // 然后得到 SupportRequestManagerFragment 中的 RequestManager 对象(如果为 null 则构建一个并
  // 设置)
  RequestManager requestManager = current.getRequestManager();
  if (requestManager == null) {
    // TODO(b/27524013): Factor out this Glide.get() call.
    Glide glide = Glide.get(context);
    requestManager =
        factory.build(
            glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
    current.setRequestManager(requestManager);
  }
  return requestManager;
}

// 在 getSupportRequestManagerFragment() 中,会根据 FragmentManager 来得到对应的 
// SupportRequestManagerFragment(如果为 null 则实例化一个并添加)
private SupportRequestManagerFragment getSupportRequestManagerFragment(
    @NonNull final FragmentManager fm, @Nullable Fragment parentHint, boolean isParentVisible) {
    
  SupportRequestManagerFragment current =
      (SupportRequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
  if (current == null) {
    current = pendingSupportRequestManagerFragments.get(fm);
    if (current == null) {
      current = new SupportRequestManagerFragment();
      current.setParentFragmentHint(parentHint);
      if (isParentVisible) {
        current.getGlideLifecycle().onStart();
      }
      pendingSupportRequestManagerFragments.put(fm, current);
      // 添加 SupportRequestManagerFragment 
      fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
      handler.obtainMessage(ID_REMOVE_SUPPORT_FRAGMENT_MANAGER, fm).sendToTarget();
    }
  }
  return current;
}

关于 Glide 监听生命周期的解析,传送门

2. load(String) 方法

RequestManager#load(String)

public RequestBuilder<Drawable> load(@Nullable String string) {
  return asDrawable().load(string);
}


public RequestBuilder<Drawable> asDrawable() {
  return as(Drawable.class);
}

public <ResourceType> RequestBuilder<ResourceType> as(
    @NonNull Class<ResourceType> resourceClass) {
  return new RequestBuilder<>(glide, this, resourceClass, context);
}

其中 asDrawable() 最终会构建 RequestBuilder<Drawable> 对象,其中对于 load() 传入 String 类型参数时,RequestBuilder#resourceClass 为 Drawable.class。resourceClass 用于指定加载资源被交付的类型。

比如 RequestBuilder<GifDrawable> asGif()、 RequestBuilder<File> asFile()、RequestBuilder<Bitmap> asBitmap() 等等。其中,asDrawable() 是默认的,会根据情况来确定加载的资源被交付时的类型。

然后 load(String) 会进一步调用 RequestBuilder#loadGeneric(),这里只是将传递进来的 url 字符串设置给成员变量 model。

private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
  this.model = model;
  isModelSet = true;
  return this;
}

因此,load() 主要用于生成一个 RequestBuilder,该对象在后面用于对于资源的请求。

3. into() 方法

经过 Glide.with(Context).load(String) 之后,最终会得到 RequestBuilder<Drawable>,因此 Glide.with(Context).into(ImageView) 实际上就是调用 RequestBuilder<Drawable>#into(ImageView)。

public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
  Util.assertMainThread();
  Preconditions.checkNotNull(view);
  // RequestBuilder 继承自 BaseRequestOptions
  BaseRequestOptions<?> requestOptions = this;
  
  // 根据 ImageView 的 scaleType 来判断是否克隆并设置额外的 option
  if (!requestOptions.isTransformationSet()
      && requestOptions.isTransformationAllowed()
      && view.getScaleType() != null) {
    // Clone in this method so that if we use this RequestBuilder to load into a View and then
    // into a different target, we don't retain the transformation applied based on the previous
    // View's scale type.
    switch (view.getScaleType()) {
      case CENTER_CROP:
        requestOptions = requestOptions.clone().optionalCenterCrop();
        break;
      ...
      default:
        // Do nothing.
    }
  }
  
  // 然后进一步调用冲载的 into() 方法
  return into(
      glideContext.buildImageViewTarget(view, transcodeClass),
      /*targetListener=*/ null,
      requestOptions,
      Executors.mainThreadExecutor());
}

(1)其中 glideContext.buildImageViewTarget(view, transcodeClass) 会构建出一个 ViewTarget<ImageView, X> 对象。这里的 transcodeClass 就是前面在 new RequestBuilder 对象时设置的,即 Drawable.class,且 glideContext 为 GlideContext。

// GlideContext.java
public <X> ViewTarget<ImageView, X> buildImageViewTarget(
    @NonNull ImageView imageView, @NonNull Class<X> transcodeClass) {
  // 进一步调用 ImageViewTargetFactory#buildTarget()
  return imageViewTargetFactory.buildTarget(imageView, transcodeClass);
}

// ImageViewTargetFactory.java
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)) {
    return (ViewTarget<ImageView, Z>) new DrawableImageViewTarget(view);
  } else {
    throw new IllegalArgumentException(
        "Unhandled class: " + clazz + ", try .as*(Class).transcode(ResourceTranscoder)");
  }
}

最终,会得到实际为 DrawableImageViewTarget 的 ViewTarget 对象,该对象是用来最终展示图片用的。

(2)进一步调用的 into() 重载方法为:

// RequestBuilder.java

// target 为前面所说的 DrawableImageViewTarget 实例
// targetListener 为 null
// options 是从上一个方法传递过来的
// callbackExecutor 为 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()");
  }
  
  // 根据传递进来的参数构建出 Request
  // 在主流程中,就得到一个 SingleRequest 对象,里面
  Request request = buildRequest(target, targetListener, options, callbackExecutor);
  Request previous = target.getRequest();
  ...
    return target;
  }
  requestManager.clear(target);
  target.setRequest(request);
  // 执行 request
  requestManager.track(target, request);
  return target;
}

在 requestManager.track(target, request) 中会调用 requestTracker.runRequest(request)。

// RequestTracker.java
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);
  }
}

先判断 Glide 当前是不是处理暂停状态,如果不是暂停状态就调用 Request 的 begin() 方法来执行开启请求,否则的话就先将 Request 添加到待执行队列里面,等暂停状态解除了之后再执行。

正常情况下,Request 的具体类型会是 SingleRequest,因此进入到 SingleRequest#begin()。

// SingleRequest.java
public synchronized void begin() {
  assertNotCallingCallbacks();
  stateVerifier.throwIfRecycled();
  startTime = LogTime.getLogTime();
  
  // 如果 model 等于 null,model 也就是我们在第二步 load() 方法中传入的图片 URL 地址,
  // 这个时候会调用 onException() 方法。
  // 在 onException() 中会调用 setErrorPlaceholder()。
  // 而在 setErrorPlaceholder() 根据实际情况来选择调用 fallback()、error()、placeholder() 这几个方法
  // 时设置的图片显示到 ImageView 上
  if (model == null) {
    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
      width = overrideWidth;
      height = overrideHeight;
    }
    // Only log at more verbose log levels if the user has set a fallback drawable, because
    // fallback Drawables indicate the user expects null models occasionally.
    int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;
    onLoadFailed(new GlideException("Received null model"), logLevel);
    return;
  }
  ...
  status = Status.WAITING_FOR_SIZE;
  if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
    // 如果使用了 override() API 为图片指定了一个固定的宽高
    onSizeReady(overrideWidth, overrideHeight);
  } else {
  	 // 没有指定则调用 target.getSize()
  	 // target.getSize() 方法的内部会根据 ImageView 的
  	 // layout_width 和 layout_height 值做一系列的计算,来算出图片应该的宽高
  	 // 计算完之后,它也会调用 onSizeReady() 方法
    target.getSize(this);
  }
  ...
}

根据前面的流程,正常情况下都会进入到 onSizeReady() 方法:

public synchronized void onSizeReady(int width, int height) {
  ...
  status = Status.RUNNING;
  float sizeMultiplier = requestOptions.getSizeMultiplier();
  this.width = maybeApplySizeMultiplier(width, sizeMultiplier);
  this.height = maybeApplySizeMultiplier(height, sizeMultiplier);
  loadStatus =
      engine.load(
          glideContext,
          model,
          requestOptions.getSignature(),
          this.width,
          this.height,
          requestOptions.getResourceClass(),
          transcodeClass,
          priority,
          requestOptions.getDiskCacheStrategy(),
          requestOptions.getTransformations(),
          requestOptions.isTransformationRequired(),
          requestOptions.isScaleOnlyOrNoTransform(),
          requestOptions.getOptions(),
          requestOptions.isMemoryCacheable(),
          requestOptions.getUseUnlimitedSourceGeneratorsPool(),
          requestOptions.getUseAnimationPool(),
          requestOptions.getOnlyRetrieveFromCache(),
          this,
          callbackExecutor);
  // This is a hack that's only useful for testing right now where loads complete synchronously
  // even though under any executor running on any thread but the main thread, the load would
  // have completed asynchronously.
  if (status != Status.RUNNING) {
    loadStatus = null;
  }
}

然后进一步调用 Engine#load(),这一部分就是加载资源的关键了。

// Engine.java
public synchronized <R> LoadStatus load(
    ...) {
  long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;
  // 首先根据目标资源的相关参数得到对应的 key 值
  EngineKey key = keyFactory.buildKey(model, signature, width, height, transform
      resourceClass, transcodeClass, options);
  // 然后从 ActiveResources 内存缓存中去取
  EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
  // 如果取到了则直接回调后续方法
  if (active != null) {
    cb.onResourceReady(active, DataSource.MEMORY_CACHE);
    return null;
  }
  // 如果在 ActiveResources 内存缓存中没有取到,则再去基于 LruCache 的内存缓存中去取
  EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
  if (cached != null) {
    cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
    return null;
  }
  // 从缓存中查找 key 对应的任务,即加载资源的任务
  EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
  if (current != null) {
    // 如果取到了则表示任务已经完成,进行相应的回调即可
    current.addCallback(cb, callbackExecutor);
    return new LoadStatus(cb, current);
  }
  // 否则,在内存中没有获取到缓存的资源,则构建相关任务,从磁盘或者远程加载
  // 构建一个新的引擎任务
  EngineJob<R> engineJob =
      engineJobFactory.build(
          ...);
  // 构建解码任务
  DecodeJob<R> decodeJob =
      decodeJobFactory.build(
          ...);
  jobs.put(key, engineJob);
  engineJob.addCallback(cb, callbackExecutor);
  engineJob.start(decodeJob);
  return new LoadStatus(cb, engineJob);
}

可以看到,则 Engine#load() 中,会先从内存缓存中去获取目标资源,如果没有,才会进一步去从磁盘缓存或者远程去加载资源(当然,这部分是后面的内容)。

上面说到会构建 EngineJob 和 DecodeJob。

其中,EngineJob 对象的主要作用就是用来开启线程的,为后面的异步加载图片做准备。

而 DecodeJob 对象实现了 Runnable 接口,然后会调用 engineJob.start(decodeJob) 来启动 decodeJob。

// EngineJob.java
public synchronized void start(DecodeJob<R> decodeJob) {
  this.decodeJob = decodeJob;
  // 得到对应的 GlideExecutor,用于提交并执行 decodeJob
  GlideExecutor executor = decodeJob.willDecodeFromCache()
      ? diskCacheExecutor
      : getActiveSourceExecutor();
 
  executor.execute(decodeJob);
}

GlideExecutor 有好几种类型的,diskCacheExecutor、sourceExecutor、sourceUnlimitedExecutor、animationExecutor,不同类型的 GlideExecutor 用于从不同的地方 load/decode/transform data,这里的地方指的磁盘缓存、Glide 自己的缓存区等。

不同的 GlideExecutor 对象内部会持有不同的线程池,执行 GlideExecutor#execute(),会间接调用线程池的 execute() 方法。

因此这里可以理解为将 decodeJob 提交到对应的线程池去处理。因为 decodeJob 为 Runnable,当其被处理的时候,实际上会调用其 run() 方法。

// DecodeJob.java
public void run() {
  ...
  // Methods in the try statement can invalidate currentFetcher, so set a local variable here to
  // ensure that the fetcher is cleaned up either way.
  DataFetcher<?> localFetcher = currentFetcher;
  try {
    if (isCancelled) {
      notifyFailed();
      return;
    }
    runWrapped();
  } 
  ...
}

private void runWrapped() {
  switch (runReason) {
    case INITIALIZE:
      // 1、得到 stage
      stage = getNextStage(Stage.INITIALIZE);
      // 2、根据 stage 得到对应的 Generator
      currentGenerator = getNextGenerator();
      runGenerators();
      break;
    case SWITCH_TO_SOURCE_SERVICE:
      runGenerators();
      break;
    case DECODE_DATA:
      decodeFromRetrievedData();
      break;
    default:
      throw new IllegalStateException("Unrecognized run reason: " + runReason);
  }
}

private Stage getNextStage(Stage current) {
  switch (current) {
    case INITIALIZE:
      // decodeCachedResource() 为 true 表示尝试 decode 缓存的被转换的图片
      return diskCacheStrategy.decodeCachedResource()
          ? Stage.RESOURCE_CACHE
          : getNextStage(Stage.RESOURCE_CACHE);
    case RESOURCE_CACHE:
      // decodeCachedData() 为 true 表示尝试 decode 缓存的原始图片
      return diskCacheStrategy.decodeCachedData()
          ? Stage.DATA_CACHE
          : getNextStage(Stage.DATA_CACHE);
    case DATA_CACHE:
      // Skip loading from source if the user opted to only retrieve the resource from cache.
      // 如果用户选择只用缓存中检索资源,则跳过从源头加载
      return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
    case SOURCE:
    case FINISHED:
      return Stage.FINISHED;
    default:
      throw new IllegalArgumentException("Unrecognized stage: " + current);
  }
}

private DataFetcherGenerator getNextGenerator() {
  switch (stage) {
    case RESOURCE_CACHE:
      // 对应被转换的图片的缓存的 Generator
      return new ResourceCacheGenerator(decodeHelper, this);
    case DATA_CACHE:
      // 对应原始图片的缓存的 Generator
      return new DataCacheGenerator(decodeHelper, this);
    case SOURCE:
      // 对应加载的图片的 Generator(此时)
      return new SourceGenerator(decodeHelper, this);
    case FINISHED:
      return null;
    default:
      throw new IllegalStateException("Unrecognized stage: " + stage);
  }
}

private void runGenerators() {
  currentThread = Thread.currentThread();
  startFetchTime = LogTime.getLogTime();
  boolean isStarted = false;
  while (!isCancelled
      && currentGenerator != null
      // currentGenerator 执行加载逻辑
      && !(isStarted = currentGenerator.startNext())) {
    stage = getNextStage(stage);
    currentGenerator = getNextGenerator();
    if (stage == Stage.SOURCE) {
      reschedule();
      return;
    }
  }
  // We've run out of stages and generators, give up.
  if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
    notifyFailed();
  }
  // Otherwise a generator started a new load and we expect to be called back in
  // onDataFetcherReady.
}

在 run() 方法中,会进一步调用 runWrapped(),此时会走 case INITIALIZE 的语句。

此时会根据不同的场景的获取不同的 Generator 来获取资源。这里主要针对 SourceGenerator 进行分析,其表示从数据源头请求数据(如通过网路从远程加载)。

(对于 ResourceCache 与 DataCache,是针对磁盘缓存来说的,因为当资源图片从网络加载后之后,可能会经过一些列的转换,而被转换后的图片资源则对应为 Resource,而没有经过转换的原始图片,则对应为 Source。相关内容在讲关于缓存的章节有说:Glide 4.9.0 的缓存机制)

后续的逻辑,主要是在 runGenerators() 中调用 currentGenerator.startNext() 来执行加载逻辑。

// SourceGenerator.java
public boolean startNext() {
  ...
  sourceCacheGenerator = null;
  loadData = null;
  boolean started = false;
  while (!started && hasNextModelLoader()) {
    // 1. 从 DecodeHelper 的数据加载集合中, 获取一个数据加载器
    loadData = helper.getLoadData().get(loadDataListIndex++);
    if (loadData != null
        && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
            || helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
      started = true;
      // 2. 使用加载器中 fetcher 执行数据加载
      // 网络的 url 资源对应的就是 HttpGlideUrlLoader,它对应的 ModelLoader.LoadData 中的
      //  fetcher 为 HttpUrlFetcher 类型
      loadData.fetcher.loadData(helper.getPriority(), this);
    }
  }
  return started;
}

对于从网络加载资源,是由 HttpUrlFetcher#loadData() 来处理的。

// HttpUrlFetcher.java
public void loadData(
    @NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
  long startTime = LogTime.getLogTime();
  try {
    // 获取网络图片, 内部使用了 HttpURLConnection 实现, 仅仅做了重定向的处理
    InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
    // 回调 callback.onDataReady() 方法将结果回传
    // callback 是在调用方法时传递过来的,这里即 SourceGenerator#startNext() 方法中传递的,即 SourceGenerator 对象自身
    callback.onDataReady(result);
  } catch (IOException e) {
    callback.onLoadFailed(e);
  } finally {
    ...
  }
}

数据加载的过程也是很简单的, HttpUrlFetcher 它使用了 HttpURLConnection 发起了网络请求, 获取了数据流, 至此数据资源的获取就已经完成了, 后面要做的便是最重要的数据处理了, 它通过回调的方式将 InputStream 类型的结果一步一步的进行回调, 最终会回溯到 DecodeJob#onDataFetcherReady 这个方法中。

具体的回调流程,参见:Glide 4.9.0 关于数据加载之后的回调过程

然后,后续的对于原始数据资源的处理,参见:Glide 4.9 源码分析(一) —— 一次完整加载流程 —— 第五点 。(由于时间与篇幅原因,暂时不单独进行后续分析)


相关内容索引:
(1)Glide 4.9.0 的缓存机制
(2)Glide 4.9.0 关于数据加载之后的回调过程
(3)Glide 4.9.0 关于监听生命周期


主要参考文章:
1、Android图片加载框架最全解析(二),从源码的角度理解Glide的执行流程(郭神的博客,基于 3.7.0 分析的)
2、Glide 源码分析解读-基于最新版Glide 4.9.0
3、Glide 4.9 源码分析(一) —— 一次完整加载流程

Android 开发框架源码解析:“Retrofit、Okhttp、ButterKnife、Glide....” 想要更系统、真切的提升自己的能力,阅读源码是一个非常好的方式:不仅限于阅读各大系统源码,还包括各种优秀的开源库。通过阅读源码,除了学习优秀的代码风格和设计思想,还能真正做到“知其然,并知其所以然”,从而使自己开发的过程中更加得心应手,更加快速地找到系统bug的根源。要想成为一名资深的Android开发工程师,你必须要去研究源码的。学习其中的设计模式、思想、架构,让你的代码变得优雅起来。源码也是目前大厂面试比较喜欢问的,研究过源码要从广度和深度去挖掘。为什么要进行源码分析。其中包括下面一些好处:为了帮助大家能 阅读详情

相关推荐

glide-4.9.0.jar

Glide4.9.0.jar是glide图片加载框架最新版本的jar文件,支持eclipse和Android studio。

Android图片加载框架之Glide的使用

简介          对于Glide这个图片加载框架,相信很多人都在使用,我以前一直用的Picasso,相比于Picasso而言,Glide功能更加丰富。Glide是在泰国举行的谷歌开发者论坛上,谷歌为我们介绍了一个名叫 Glide 的图片加载库,作者是bumptech。这个库被广泛的运用在google的开源项目中,包括2014年google I/O大会上发布的官方app。 特点 (1)使...

wen_haha的博客 1258

Glide 4.3.0 jar包下载

包括glide-3.5.0.jar,glide-3.6.0.jar,glide-3.6.1.jar,glide3.7.0.jar,glide-full-4.0.0.jar,glide-full-4.3.0.jar

Glide使用详解

该文章基于Glide v3.7.0版本 Glide v4版本详解请移步: http://blog.csdn.net/shangmingchao/article/details/78219558一. 下载在build.gradle中添加依赖: compile 'com.github.bumptech.glide:glide:3.7.0'需要support-v4库的支持,如果你的项目没有supp...

栀夏暖阳的博客 1564

Glide最新jar包下载

一个牛逼的图片加载库,支持gif图片的加载,包含Glide的使用介绍和相关的技术(中英)文档

Glide最新4.5.0-4.9.0.zip

花费大把积分和精力整合最新glide4.9.0和okhttp glide兼容jar、glide4.5.0、4.7.0、4.7.1

图片框架Glide的使用(二)

在上一篇博客《图片框架Glide的使用(一)》中介绍了部分图片加载框架Glide的使用方法,在这一篇博客中继续将介绍Glide框架中的另外一些使用。 废话不多说,接着上一篇博客来: ⑪自定义图形变换 Glide.with(MainActivity.this) .load(ImageUrl.images[34]) //.transform(new MyTransformation(Ma...

ITRenj的博客 946

Glide 4.7.0 jar

Glide4.7.0jar 最新jar包 Glide最流行的一款图片框架,使用方便快捷 大家免费下载吧 有其他问题也可以问我

第三方图片加载框架Glide的使用方法(在ImageView中加载jif动画)

概述 在线添加依赖包配置网络权限 Mainacvitityjava 加载静态图片和本地视频 概述: Glide是一款第三方开源的图片加载框架,其作者是Google的一名员工,也是Google官方推荐的图片加载库。Glide与Picasso非常相似,拥有非常简洁、灵活的API,并且支持加载Gif图片。它出现的目的有两个,一个是实现平滑的图片列表滚动效果,另一个是支持远程图片的获取、大小调

The blog of 优快云 in 杨天福 6757

告别Sourceforge安装器!手把手教你下载MinGW-w64离线包并配置环境变量(Windows 10/11)

本文详细介绍了如何在Windows 10/11系统上通过下载MinGW-w64离线包并配置环境变量,完全绕过Sourceforge安装器的问题。从选择正确的版本、解压到本地目录,到设置系统环境变量和验证编译环境,手把手指导开发者快速搭建稳定的GCC编译环境,解决常见的安装困扰。

weixin_30896825的博客 335

基于halcon的HSV颜色匹配实例

HSV颜色识别原图代码结果 原图 代码 dev_update_off () dev_close_window () *颜色数组 FuseColors := ['Orange','Red','Blue','Yellow','Green'] *画笔颜色数组 DisplayColors := ['coral','red','blue','goldenrod','forest green'] * HSV模型中H分量: Orange 10-30, Red 0-10,Blue 125-162,Yellow 3

qq_39467811的博客 3543

【笔记整理】Glide 4.9.0 关于数据加载之后的回调过程

当 Glide 从网络加载原始的数据的时候,会来到 HttpUrlFetcher#loadData() 方法,在 占位符 中说过,当加载完成后,会通过 callback.onDataReady() 方法将结果回传,最终会回溯到 DecodeJob#onDataFetcherReady 这个方法中,下面将会回溯的具体流程进行分析。 // HttpUrlFetcher.java public void...

一个有思想的搬运工 1907

ios生成海报使用dom-to-image中toPng报错isTrusted:true

主要是ios中safari浏览器解析错误,引起的报错,主要引起原因toPng会调用toSvg,toSvg直接将html标签内容通过svg中foreignObject标签直接包裹,然后转base64, 通过data:image/svg+xml;解决办法:二次生成(调用两次),判断ios or 安卓,生成第一次生成,不用参数,例如:scale: 3,在第二次生成再进行清晰度操作,避免多次生成,引起性能消耗。以上种种很多案例,使用canvase进行转换的,标签丢失,直接toPng版本的,兼容性问题。

weixin_44601570的博客 2105

【笔记整理】Glide 4.9.0 关于监听生命周期

对于 with() 方法传递的参数(包括 Activity、Fragment、View 等),如果对应的 context 为非 Application 类型的,Glide 会为作为参数传递进with() 方法的 Activity 或者 Fragment 添加一个隐藏的且隐藏的(即没有 UI 的) Fragment(SupportRequestManagerFragment 或者 RequestMa...

一个有思想的搬运工 1229

从12摄像头到纯视觉:小鹏AI鹰眼方案如何用LOFIC技术省掉激光雷达?

本文深入解析小鹏汽车AI鹰眼方案如何通过LOFIC技术实现纯视觉自动驾驶,替代传统激光雷达。LOFIC摄像头在动态范围、噪声控制和HDR优化方面表现卓越,结合800万像素双目广角摄像头,达到激光雷达级测距精度。该方案不仅降低成本,还提升系统可靠性和用户体验,展示了纯视觉路线的技术潜力与边界。

t8u9v0w1x的博客 930

Sigrity Power DC Package Multi-Resistor Compact ET Model Extraction模式提取封装基板电热阻模型

DIE(定义DIE的参数):material设置为silicon(硅),Attach material(贴片的材料,设置为Die Attach),Attach thickness(贴片的材料的厚度,厚度设置为0.001mm,点击ok。Cap为覆盖材料,Material定义材料类型,thickness定义厚度,length和width定义长宽,Adhesive Material为胶的材料,Adhesive Material为胶的厚度,本例不定义,跳过即可。

weixin_54787054的博客 437

Glide源码分析

本文将尝试梳理Glide 3.7.0版本常用方法的源码实现,关于Glide的使用方法网上已经有足够多的资料可供查阅,因此本文默认阅读者对于Glide的使用方法已无障碍。第一次尝试将自己的学习结果整理成文字,有不足之处和建议请多多指出。

xmt328的博客 315

AUTOSAR TcpIp模块 以太网配置开发2

someip协议之tcpIp协议

汽车电子电气 1085

2024年Android最全Android主流三方库源码分析(三、深入理解Glide源码)(1),2024年最新附大厂真题面经

开发是面向对象。我们找工作应该更多是面向面试。哪怕进大厂真的只是去宁螺丝,但你要进去得先学会面试的时候造飞机不是么?作者13年java转Android开发,在小厂待过,也去过华为,OPPO等,去年四月份进了阿里一直到现在。等大厂待过也面试过很多人。深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。这里附上上述的技术体系图相关的几十套腾讯、头条、阿里、美团等公司的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含。

2401_84538135的博客 782

Unity零基础到入门 ☀️| 轻松学会 Unity界面布局和简单实例——入门级!(^_−)☆

入门级Unity知识,一起加油呀!

努力前行,总会成为自己心中的那道光 1万+

人体红外传感器HC-SR501

本文记录的是如何用stm32使用HC-SR501。实验平台:野火指南者stm32f103vet6。实验现象:一旦有人体经过,LED会亮红色。HC-SR501 传感器工作原理人体都有恒定的体温,一般在 37 度,所以会发出特定波长 10UM 左右的红外线,被动式红外探头就是靠探测人体发射的 10um 左右的红外,进行工作的。人体发射的 10um 左右。

weixin_43772611的博客 1万+

Glide源码解析(一)基本用法

转载自 guolin,略有删减改动 Glide源码解析(一)基本用法开始加载图片占位图指定图片格式指定图片大小 现在Android上的图片加载框架非常成熟,从最早的老牌图片加载框架UniversalImageLoader,到后来Google推出的Volley,再到后来的新兴军Glide和Picasso,当然还有Facebook的Fresco。每一个都非常稳定,功能也都十分强大。但是它们的使用场景基本都是重合的,也就是说我们基本只需要选择其中一个来进行学习和使用就足够了,每一个框架都尝试去掌握的话则有些浪费时

aha_jasper的博客 342

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

Android图片加载框架最全解析(一),Glide的基本用法

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/53759439 本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 郭霖 即可关注,每天都有文章更新。 现在Android上的图片加载框架非常成熟,从最早的老牌图片加载框架UniversalImageLoader,到后来Google推出的Volley,再到后

郭霖的专栏 20万+

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

Android 面试之Glide做了哪些优化 _glide 对bitmap 做了哪些优化

在Android 3.0(API 级别 11)开始,系统引入了字段。如果设置了此选项,那么采用Options对象的解码方法会在生成目标Bitmap时尝试复用inBitmap,这意味着inBitmap的内存得到了重复使用,从而提高了性能,同时移除了内存分配和取消分配。不过inBitmap的使用方式存在某些限制,在Android 4.4(API 级别 19)之前系统仅支持复用大小相同的位图,4.4 之后只要inBitmap的大小比目标Bitmap大即可通过上文我们知道了可以通过inBitmap。

2401_84104460的博客 1111

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

glide源码中包含了那种设计模式_内部泄露版!2020 美团 Android 面试真题解析

这份Android面试真题涵盖了图片,网络和安全机制,网络,数据库,插件化、模块化、组件化、热修复、增量更新、Gradle,架构设计和设计模式,Android Framework 、Android优秀三方库源码等。适合中高级工程师。一、图片1、图片库对比2、LRUCache原理3、图片加载原理4、自己去实现图片库,怎么做?5、Glide源码解析6、Glide使用什么缓存?7、Glide内存缓存如何...

weixin_39768695的博客 293

2023送大家一份Android开发优秀源码合集(附解析)

本文是一位阿里P8大佬花了将近半个月时间,收纳了近百个知名开源项目的源码注释和源码阅读笔记,整理而成的Android开发源码合集。

大模型学习路线 1782
上一篇: 【LeetCode】135. 分糖果
下一篇: 【笔记整理】Glide 4.9.0 的缓存机制
OneDeveloper
博客等级 码龄9年 61粉丝 161原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值