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月前
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案
|
10月前
|
存储 SQL 关系型数据库
京东面试:mysql深度分页 严重影响性能?根本原因是什么?如何优化?
京东面试:mysql深度分页 严重影响性能?根本原因是什么?如何优化?
京东面试:mysql深度分页 严重影响性能?根本原因是什么?如何优化?
|
9月前
|
数据采集 存储 Web App开发
多线程爬虫优化:快速爬取并写入CSV
多线程爬虫优化:快速爬取并写入CSV
|
机器学习/深度学习 人工智能 JSON
Resume Matcher:增加面试机会!开源AI简历优化工具,一键解析简历和职位描述并优化
Resume Matcher 是一款开源AI简历优化工具,通过解析简历和职位描述,提取关键词并计算文本相似性,帮助求职者优化简历内容,提升通过自动化筛选系统(ATS)的概率,增加面试机会。
1509 18
Resume Matcher:增加面试机会!开源AI简历优化工具,一键解析简历和职位描述并优化
|
9月前
|
机器学习/深度学习 监控 算法
局域网行为监控软件 C# 多线程数据包捕获算法:基于 KMP 模式匹配的内容分析优化方案探索
本文探讨了一种结合KMP算法的多线程数据包捕获与分析方案,用于局域网行为监控。通过C#实现,该系统可高效检测敏感内容、管理URL访问、分析协议及审计日志。实验表明,相较于传统算法,KMP在处理大规模网络流量时效率显著提升。未来可在算法优化、多模式匹配及机器学习等领域进一步研究。
252 0
|
11月前
|
人工智能 算法 数据库
美团面试:LLM大模型存在哪些问题?RAG 优化有哪些方法?_
美团面试:LLM大模型存在哪些问题?RAG 优化有哪些方法?_
|
11月前
|
数据采集 存储 网络协议
Java HttpClient 多线程爬虫优化方案
Java HttpClient 多线程爬虫优化方案
|
Java 调度 Python
多线程优化For循环:实战指南
本文介绍如何使用多线程优化For循环,提高程序处理大量数据或耗时操作的效率。通过并行任务处理,充分利用多核处理器性能,显著缩短执行时间。文中详细解释了多线程基础概念,如线程、进程、线程池等,并提供了Python代码示例,包括单线程、多线程和多进程实现方式。最后,还总结了使用多线程或多进程时需要注意的事项,如线程数量、任务拆分、共享资源访问及异常处理等。
535 7
|
并行计算 算法 安全
面试必问的多线程优化技巧与实战
多线程编程是现代软件开发中不可或缺的一部分,特别是在处理高并发场景和优化程序性能时。作为Java开发者,掌握多线程优化技巧不仅能够提升程序的执行效率,还能在面试中脱颖而出。本文将从多线程基础、线程与进程的区别、多线程的优势出发,深入探讨如何避免死锁与竞态条件、线程间的通信机制、线程池的使用优势、线程优化算法与数据结构的选择,以及硬件加速技术。通过多个Java示例,我们将揭示这些技术的底层原理与实现方法。
929 3
|
存储 监控 小程序
Java中的线程池优化实践####
本文深入探讨了Java中线程池的工作原理,分析了常见的线程池类型及其适用场景,并通过实际案例展示了如何根据应用需求进行线程池的优化配置。文章首先介绍了线程池的基本概念和核心参数,随后详细阐述了几种常见的线程池实现(如FixedThreadPool、CachedThreadPool、ScheduledThreadPool等)的特点及使用场景。接着,通过一个电商系统订单处理的实际案例,分析了线程池参数设置不当导致的性能问题,并提出了相应的优化策略。最终,总结了线程池优化的最佳实践,旨在帮助开发者更好地利用Java线程池提升应用性能和稳定性。 ####