Android面试题经典之Glide取消加载以及线程池优化

简介: Glide通过生命周期管理在`onStop`时暂停请求,`onDestroy`时取消请求,减少资源浪费。在`EngineJob`和`DecodeJob`中使用`cancel`方法标记任务并中断数据获取。当网络请求被取消时,`HttpUrlFetcher`的`cancel`方法设置标志,之后的数据获取会返回`null`,中断加载流程。Glide还使用定制的线程池,如AnimationExecutor、diskCacheExecutor、sourceExecutor和newUnlimitedSourceExecutor,其中某些禁止网络访问,并根据CPU核心数动态调整线程数。

本文首发于公众号“AntDream”,欢迎微信搜索“AntDream”或扫描文章底部二维码关注,和我一起每天进步一点点

Glide通过生命周期取消加载

生命周期回调过程
onStop
--->RequestManager.onStop
-->RequestTracker.pauseRequest
--> SingleRequest.pause
--> SingleRequest.cancel
---> Engine.cancel
---> EngineJob.removeCallback
---> 如果所有的回调监听都移除了
--> DecodeJob.cancel

//EngineJob.class
synchronized void removeCallback(ResourceCallback cb) {
   
   stateVerifier.throwIfRecycled();
   cbs.remove(cb);
   if (cbs.isEmpty()) {
   
     cancel();
     boolean isFinishedRunning = hasResource || hasLoadFailed;
     if (isFinishedRunning && pendingCallbacks.get() == 0) {
   
       release();
     }
   }
}
  • 如果任务没有执行,就从队列里移除,取消任务
  • 如果任务已经执行,就利用isCancelled标记在停止流程
//DecodeJob.class
public void cancel() {
   
      isCancelled = true;
      DataFetcherGenerator local = currentGenerator;
      if (local != null) {
   
          local.cancel();
      }
}

上面最终会调用到具体的获取图片的任务,比如从网络获取图片的HttpUrlFetcher

 //HttpUrlFetcher.class
private volatile boolean isCancelled;
@Override
public void cancel() {
   
    // TODO: we should consider disconnecting the url connection here, but we can't do so
    // directly because cancel is often called on the main thread.
    isCancelled = true;
}

//内部是通过HttpURLConnection来从网络获取数据
private InputStream loadDataWithRedirects(
      URL url, int redirects, URL lastUrl, Map<String, String> headers) throws HttpException {
   
    ...

    urlConnection = buildAndConfigureConnection(url, headers);

    try {
   
      // 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();
    } catch (IOException e) {
   
      ...
    }

    if (isCancelled) {
   
      return null;
    }

    final int statusCode = getHttpStatusCodeOrInvalid(urlConnection);
    if (isHttpOk(statusCode)) {
   
      return getStreamForSuccessfulRequest(urlConnection);
    } else if (isHttpRedirect(statusCode)) {
   
      String redirectUrlString = urlConnection.getHeaderField(REDIRECT_HEADER_FIELD);
      ...
      cleanup();
      return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
    } else if (statusCode == INVALID_STATUS_CODE) {
   
      throw new HttpException(statusCode);
    } else {
   
      ...
    }
}

由于cancel方法一般是在主线程调用的,所以这里采用的是volatile关键字这种方式,在正式获取网络数据时会进行拦截;

  • 如果拦截到了,那直接返回null;如果没拦截到,就获取到数据
  • 以上最终都会回调到DecodeJob的onDataFetcherReady方法

onDataFetcherReady ---> decodeFromRetrievedData ---> decodeFromData

private void decodeFromRetrievedData() {
   
     ...
     Resource<R> resource = null;
     try {
   
       resource = decodeFromData(currentFetcher, currentData, currentDataSource);
     } catch (GlideException e) {
   
       ...
     }
     if (resource != null) {
   
       notifyEncodeAndRelease(resource, currentDataSource, isLoadingFromAlternateCacheKey);
     } else {
   
       //任务被拦截,尝试其他的加载方式
       runGenerators();
     }
}

decodeFromData中会进行判断,如果data为Null就直接返回Null(被拦截时会是null),这个时候会执行runGenerators方法

runGenerators方法实际上就是加载流程的流转,比如先从文件中加载,文件中没有,就去网络加载,一个个去试这样。当然这里面肯定也有cancel拦截的

private void runGenerators() {
   
    currentThread = Thread.currentThread();
    startFetchTime = LogTime.getLogTime();
    boolean isStarted = false;

    //被cancel拦截就不会尝试其他加载方式,直接任务取消

    while (!isCancelled
        && currentGenerator != null
        && !(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();
    }
}

所以当加载数据的流程被拦截了,比如网络请求返回null,那到这里这个流程就拦截下来了,直接

notifyFailed任务失败了。

那如果网络加载的时候没有拦截住呢?data就不会为null,就会走notifyEncodeAndRelease方法,最终会一直走到EngineJob的notifyCallbacksOfResult方法,这个方法里面又会有cancel拦截。这样也就是数据加载前加载后都被拦截下来了

Glide中的线程池

自定义ThreadFactory,主要的一个功能是实现限制部分线程网络访问,利用的是严苛模式

@Override
public Thread newThread(@NonNull final Runnable runnable) {
   
   Thread newThread =
       delegate.newThread(
           new Runnable() {
   
             @Override
             public void run() {
   
               if (preventNetworkOperations) {
   
                 StrictMode.setThreadPolicy(
                     new ThreadPolicy.Builder().detectNetwork().penaltyDeath().build());
               }
               try {
   
                 runnable.run();
               } catch (Throwable t) {
   
                 uncaughtThrowableStrategy.handle(t);
               }
             }
           });
   newThread.setName("glide-" + name + "-thread-" + threadNum.getAndIncrement());
   return newThread;
}

线程池主要有这几种:

  • AnimationExecutor:加载动画相关,禁止访问网络;如果CPU核心数大于4,就是2个线程,否则是一个线程,核心线程数和最大线程数相同

  • diskCacheExecutor:从磁盘加载图片,禁止访问网络;线程数为1,核心线程数和最大线程数相同

  • sourceExecutor:可以访问网络,线程数跟CPU核心数有关,不大于4,核心线程数和最大线程数相同

  • newUnlimitedSourceExecutor:用于网络请求图片,没有核心线程,只有非核心线程,类似CacheThreadPoll

Glide做了线程池优化,核心线程也会遵循超时策略

public GlideExecutor build() {
   
  if (TextUtils.isEmpty(name)) {
   
    throw new IllegalArgumentException(
        "Name must be non-null and non-empty, but given: " + name);
  }
  ThreadPoolExecutor executor =
      new ThreadPoolExecutor(
          corePoolSize,
          maximumPoolSize,
          /*keepAliveTime=*/ threadTimeoutMillis,
          TimeUnit.MILLISECONDS,
          new PriorityBlockingQueue<Runnable>(),
          new DefaultThreadFactory(
              threadFactory, name, uncaughtThrowableStrategy, preventNetworkOperations));

  if (threadTimeoutMillis != NO_THREAD_TIMEOUT) {
   
    executor.allowCoreThreadTimeOut(true);
  }

  return new GlideExecutor(executor);
}

欢迎关注我的公众号AntDream查看更多精彩文章!

目录
相关文章
|
6月前
|
JavaScript Linux 网络安全
Termux安卓终端美化与开发实战:从下载到插件优化,小白也能玩转Linux
Termux是一款安卓平台上的开源终端模拟器,支持apt包管理、SSH连接及Python/Node.js/C++开发环境搭建,被誉为“手机上的Linux系统”。其特点包括零ROOT权限、跨平台开发和强大扩展性。本文详细介绍其安装准备、基础与高级环境配置、必备插件推荐、常见问题解决方法以及延伸学习资源,帮助用户充分利用Termux进行开发与学习。适用于Android 7+设备,原创内容转载请注明来源。
1213 77
|
4月前
|
缓存 编解码 Android开发
Android内存优化之图片优化
本文主要探讨Android开发中的图片优化问题,包括图片优化的重要性、OOM错误的成因及解决方法、Android支持的图片格式及其特点。同时介绍了图片储存优化的三种方式:尺寸优化、质量压缩和内存重用,并详细讲解了相关的实现方法与属性。此外,还分析了图片加载优化策略,如异步加载、缓存机制、懒加载等,并结合多级缓存流程提升性能。最后对比了几大主流图片加载框架(Universal ImageLoader、Picasso、Glide、Fresco)的特点与适用场景,重点推荐Fresco在处理大图、动图时的优异表现。这些内容为开发者提供了全面的图片优化解决方案。
161 1
|
3月前
|
机器学习/深度学习 监控 算法
局域网行为监控软件 C# 多线程数据包捕获算法:基于 KMP 模式匹配的内容分析优化方案探索
本文探讨了一种结合KMP算法的多线程数据包捕获与分析方案,用于局域网行为监控。通过C#实现,该系统可高效检测敏感内容、管理URL访问、分析协议及审计日志。实验表明,相较于传统算法,KMP在处理大规模网络流量时效率显著提升。未来可在算法优化、多模式匹配及机器学习等领域进一步研究。
89 0
|
8月前
|
监控 Kubernetes Java
阿里面试:5000qps访问一个500ms的接口,如何设计线程池的核心线程数、最大线程数? 需要多少台机器?
本文由40岁老架构师尼恩撰写,针对一线互联网企业的高频面试题“如何确定系统的最佳线程数”进行系统化梳理。文章详细介绍了线程池设计的三个核心步骤:理论预估、压测验证和监控调整,并结合实际案例(5000qps、500ms响应时间、4核8G机器)给出具体参数设置建议。此外,还提供了《尼恩Java面试宝典PDF》等资源,帮助读者提升技术能力,顺利通过大厂面试。关注【技术自由圈】公众号,回复“领电子书”获取更多学习资料。
|
8月前
|
安全 Java 程序员
面试必看:如何设计一个可以优雅停止的线程?
嘿,大家好!我是小米。今天分享一篇关于“如何停止一个正在运行的线程”的面试干货。通过一次Java面试经历,我明白了停止线程不仅仅是技术问题,更是设计问题。Thread.stop()已被弃用,推荐使用Thread.interrupt()、标志位或ExecutorService来优雅地停止线程,避免资源泄漏和数据不一致。希望这篇文章能帮助你更好地理解Java多线程机制,面试顺利! 我是小米,喜欢分享技术的29岁程序员。欢迎关注我的微信公众号“软件求生”,获取更多技术干货!
209 53
|
7月前
|
数据采集 Java Linux
面试大神教你:如何巧妙回答线程优先级这个经典考题?
大家好,我是小米。本文通过故事讲解Java面试中常见的线程优先级问题。小明和小华的故事帮助理解线程优先级:高优先级线程更可能被调度执行,但并非越高越好。实际开发需权衡业务需求,合理设置优先级。掌握线程优先级不仅能写出高效代码,还能在面试中脱颖而出。最后,小张因深入分析成功拿下Offer。希望这篇文章能助你在面试中游刃有余!
120 4
面试大神教你:如何巧妙回答线程优先级这个经典考题?
|
5月前
|
数据采集 存储 网络协议
Java HttpClient 多线程爬虫优化方案
Java HttpClient 多线程爬虫优化方案
|
7月前
|
Java 程序员 开发者
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
452 14
|
7月前
|
安全 Java 程序员
Java 面试必问!线程构造方法和静态块的执行线程到底是谁?
大家好,我是小米。今天聊聊Java多线程面试题:线程类的构造方法和静态块是由哪个线程调用的?构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节有助于掌握Java多线程机制。下期再见! 简介: 本文通过一个常见的Java多线程面试题,详细讲解了线程类的构造方法和静态块是由哪个线程调用的。构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节对掌握Java多线程编程至关重要。
130 13
|
7月前
|
缓存 安全 Java
面试中的难题:线程异步执行后如何共享数据?
本文通过一个面试故事,详细讲解了Java中线程内部开启异步操作后如何安全地共享数据。介绍了异步操作的基本概念及常见实现方式(如CompletableFuture、ExecutorService),并重点探讨了volatile关键字、CountDownLatch和CompletableFuture等工具在线程间数据共享中的应用,帮助读者理解线程安全和内存可见性问题。通过这些方法,可以有效解决多线程环境下的数据共享挑战,提升编程效率和代码健壮性。
233 6