Picasso中的多线程

Picasso的使用及源码提交请求流程网上都已经很多了,本篇只分析Picasso中多线程相关

  1. Picasso的线程池

Picasso的线程池是在Picasso.Builder.build()方法中创建的

if (service == null) {
        service = new PicassoExecutorService();
      }

而PicassoExecutorService是继承自ThreadPoolExecutor

  //初始化函数
  private static final int DEFAULT_THREAD_COUNT = 3;

  PicassoExecutorService() {
    super(DEFAULT_THREAD_COUNT, DEFAULT_THREAD_COUNT, 0, TimeUnit.MILLISECONDS,
        new PriorityBlockingQueue<Runnable>(), new Utils.PicassoThreadFactory());
  }

可以看到核心线程数和最大线程数都是3个,并且使用了优先级队列,PriorityBlockingQueue<Runnable>() ,而此队列是无界队列,所以不需要最大线程数比核心线程数多且不需要设置保持存活时间。
那么问题来了,提交的时候优先级怎么排序的,看下面

//PicassoExecutorService.class
  @Override
  public Future<?> submit(Runnable task) {
    PicassoFutureTask ftask = new PicassoFutureTask((BitmapHunter) task);
    execute(ftask);
    return ftask;
  }

重写了submit接口,返回了自封装的PicassoFutureTask,

private static final class PicassoFutureTask extends FutureTask<BitmapHunter>
      implements Comparable<PicassoFutureTask> {
    private final BitmapHunter hunter;

    public PicassoFutureTask(BitmapHunter hunter) {
      super(hunter, null);
      this.hunter = hunter;
    }

    @Override
    public int compareTo(PicassoFutureTask other) {
      Picasso.Priority p1 = hunter.getPriority();
      Picasso.Priority p2 = other.hunter.getPriority();

      // High-priority requests are "lesser" so they are sorted to the front.
      // Equal priorities are sorted by sequence number to provide FIFO ordering.
      return (p1 == p2 ? hunter.sequence - other.hunter.sequence : p2.ordinal() - p1.ordinal());
    }
  }

其实主要是重写了compareTo接口,根据BitmapHunter的priority对请求排序,而BitmapHunter就是提交到线程池里的Runnable

继续看线程池用到的线程工厂new Utils.PicassoThreadFactory()

//Util.java
static class PicassoThreadFactory implements ThreadFactory {
    @SuppressWarnings("NullableProblems")
    public Thread newThread(Runnable r) {
      return new PicassoThread(r);
    }
  }
private static class PicassoThread extends Thread {
    public PicassoThread(Runnable r) {
      super(r);
    }

    @Override public void run() {
      Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND);
      super.run();
    }
  }

仅仅只是设置了一下线程优先级为后台线程

  1. Picasso UI对应的handler为
static final Handler HANDLER = new Handler(Looper.getMainLooper()) {
    @Override public void handleMessage(Message msg) {
          ...
      }
    }
  };

是个静态常量,生命周期是app的生命周期。为啥呢,原因是Picasso里的Context是applicationContext()。

//Picasso$Builder的构造函数
public Builder(Context context) {
      if (context == null) {
        throw new IllegalArgumentException("Context must not be null.");
      }
      this.context = context.getApplicationContext();
    }
//build方法
public Picasso build() {
      Context context = this.context;
...
      return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
          defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
    }

那问题来了,下载图片的activity或者fragment销毁后不会内存泄漏吗?
请看下面

//Picasso构造函数
Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
      RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats,
      Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {
...
this.referenceQueue = new ReferenceQueue<Object>();
    this.cleanupThread = new CleanupThread(referenceQueue, HANDLER);
    this.cleanupThread.start();
}

构建了一个ReferenceQueue,以及CleanupThread,看过LeakCanary的是不是立马明白原理了。
CleanupThread是一个守护者线程,如下

//Picasso.java
private static class CleanupThread extends Thread {
    CleanupThread(ReferenceQueue<Object> referenceQueue, Handler handler) {
      ...
      setDaemon(true);
    }

    @Override public void run() {
      Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND);
      while (true) {
        try {
          // Prior to Android 5.0, even when there is no local variable, the result from
          // remove() & obtainMessage() is kept as a stack local variable.
          // We're forcing this reference to be cleared and replaced by looping every second
          // when there is nothing to do.
          // This behavior has been tested and reproduced with heap dumps.
          RequestWeakReference<?> remove =
              (RequestWeakReference<?>) referenceQueue.remove(THREAD_LEAK_CLEANING_MS);
          Message message = handler.obtainMessage();
          if (remove != null) {
            message.what = REQUEST_GCED;
            message.obj = remove.action;
            handler.sendMessage(message);
          } else {
            message.recycle();
          }
        ...
      }
    }
...
  }

就是起来个守护线程,死循环阻塞查询referenceQueue里是不是有回收的对象,有的话发送REQUEST_GCED请求给UI线程的HANDLER。

//上面的UI线程HANDLER
public void handleMessage(Message msg) {
...
case REQUEST_GCED: {
          Action action = (Action) msg.obj;
          ...
          action.picasso.cancelExistingRequest(action.getTarget());
          break;
        }
}
//Picasso.java
private void cancelExistingRequest(Object target) {
    checkMain();
    Action action = targetToAction.remove(target);
    if (action != null) {
      ...
      dispatcher.dispatchCancel(action);
    }
    ...
  }

//Dispatcher.java
void dispatchCancel(Action action) {
    handler.sendMessage(handler.obtainMessage(REQUEST_CANCEL, action));
  }

//Dispatcher$DispatchHandler
@Override public void handleMessage(final Message msg) {
...
case REQUEST_CANCEL: {
          Action action = (Action) msg.obj;
          dispatcher.performCancel(action);
          break;
        }
}

//Dispatcher
void performCancel(Action action) {
    String key = action.getKey();
    BitmapHunter hunter = hunterMap.get(key);
    if (hunter != null) {
      hunter.detach(action);
      if (hunter.cancel()) {
        hunterMap.remove(key);
       ...
      }
    }
//BitmapHunter
boolean cancel() {
    return action == null
        && (actions == null || actions.isEmpty())
        && future != null
        && future.cancel(false);
  }

可以看到UI线程Handler收到cancel请求后调用dispatcher分派后台线程DispatcherHandler进而通过future给cancel掉这一次的请求。

  1. 当然了DispatcherHander是个后台线程,用于在后台分发请求,以及将任务提交到线程池、取消请求、将结果批量保存发送给UI线程等。主要就是将处理逻辑尽量放后台这样不阻塞UI。
    是在Dispatcher初始化的时候构建的
Dispatcher(Context context, ExecutorService service, Handler mainThreadHandler,
      Downloader downloader, Cache cache, Stats stats) {
    this.dispatcherThread = new DispatcherThread();
    this.dispatcherThread.start();
    ...
    this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this);
}

分析完毕

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容

  • Picasso,看的版本是v.2.5.2 使用方法,大概这么几种加载资源的形式 还可以对图片进行一些操作:设置大小...
    Jinjins1129阅读 342评论 0 3
  • picasso 是一个强大的图片加载缓存框架 1.首先看下picasso 如何使用: Picasso和Glide相...
    轻轻笑声阅读 2,204评论 0 1
  • 目录 Picasso加载一张图片的流程 创建//通过定义一个PicassoProvider来获取Context//...
    _Ryan阅读 449评论 0 3
  • 一.资源抢夺 2> 资源抢夺解决方案 @sychronized{ } dispatch_barrier_async...
    蓝心儿的蓝色之旅阅读 1,363评论 0 4
  • 概述 Picasso是大名鼎鼎的Square公司提供的一个适用于Android的强大的图片下载缓存库。 简单使用 ...
    憨人_Vivam阅读 209评论 0 1