Picasso源码解析

本文深入剖析基于Picasso 2.4.0的图片加载流程,涵盖with、load和into方法。讲解了Picasso的缓存策略、线程池管理、下载器选择,以及如何利用Okhttp。详细阐述了请求创建、内存缓存检查、图片尺寸适配、取消和提交操作,揭示了BitmapHunter和RequestHandler在图片加载过程中的作用,以助于理解并优化图片加载性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Picasso源码解析(本文基于Picasso2.4.0版本)

Picasso加载图片最简单的调用方法是

Picasso.with(mContext).load(url).into(iv);

我们一起来看看这三个方法里面做了什么

方法解析

1.with

这个方法首先会返回一个单例的Picasso对象(里面用了两个判断一把锁的单例模式)

public static Picasso with(Context context) {
    if(singleton == null) {
        Class var1 = Picasso.class;
        synchronized(Picasso.class) {
            if(singleton == null) {
                singleton = (new Picasso.Builder(context)).build();
            }
        }
    }

    return singleton;
}

如果是第一次创建,会对Picasso进行初始化,主要的有Cache,线程池,downloader,Dispatcher

public Picasso build() {
      Context context = this.context;

      if (downloader == null) {
        downloader = Utils.createDefaultDownloader(context);
      }
      if (cache == null) {
        cache = new LruCache(context);
      }
      if (service == null) {
        service = new PicassoExecutorService();
      }
      if (transformer == null) {
        transformer = RequestTransformer.IDENTITY;
      }

      Stats stats = new Stats(cache);

      Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);

      return new Picasso(context, dispatcher, cache, listener, transformer, stats, debugging);
    }
}

(1) Cache:是一个LruCache 他的最大缓存大小大约是15%(通过下面的方法计算的)

static int calculateMemoryCacheSize(Context context) {
    ActivityManager am = getService(context, ACTIVITY_SERVICE);
    boolean largeHeap = (context.getApplicationInfo().flags & FLAG_LARGE_HEAP) != 0;
    int memoryClass = am.getMemoryClass();
    if (largeHeap && SDK_INT >= HONEYCOMB) {
      memoryClass = ActivityManagerHoneycomb.getLargeMemoryClass(am);
    }
    // Target ~15% of the available heap.
    return 1024 * 1024 * memoryClass / 7;
  }

(2) 线程池:会根据不同的网络状态来改变核心线程的数量(动态设置线程数量的代码在下面,可以作为以后封装线程池的参考)

  void adjustThreadCount(NetworkInfo info) {
if (info == null || !info.isConnectedOrConnecting()) {
  setThreadCount(DEFAULT_THREAD_COUNT);
  return;
}
switch (info.getType()) {
  case ConnectivityManager.TYPE_WIFI:
  case ConnectivityManager.TYPE_WIMAX:
  case ConnectivityManager.TYPE_ETHERNET:
    setThreadCount(4);
    break;
  case ConnectivityManager.TYPE_MOBILE:
    switch (info.getSubtype()) {
      case TelephonyManager.NETWORK_TYPE_LTE:  // 4G
      case TelephonyManager.NETWORK_TYPE_HSPAP:
      case TelephonyManager.NETWORK_TYPE_EHRPD:
        setThreadCount(3);
        break;
      case TelephonyManager.NETWORK_TYPE_UMTS: // 3G
      case TelephonyManager.NETWORK_TYPE_CDMA:
      case TelephonyManager.NETWORK_TYPE_EVDO_0:
      case TelephonyManager.NETWORK_TYPE_EVDO_A:
      case TelephonyManager.NETWORK_TYPE_EVDO_B:
        setThreadCount(2);
        break;
      case TelephonyManager.NETWORK_TYPE_GPRS: // 2G
      case TelephonyManager.NETWORK_TYPE_EDGE:
        setThreadCount(1);
        break;
      default:
        setThreadCount(DEFAULT_THREAD_COUNT);
    }
    break;
  default:
    setThreadCount(DEFAULT_THREAD_COUNT);
}

}

(3) downloader:他会根据你是否有集成Okhttp,如果有就用Okhttp,如果有就用OkhttpDowmLoader,没有就自己创建一个UrlConnectionDownloader(通过反射,看是否集成了okhttp)

static Downloader createDefaultDownloader(Context context) {
    try {
      Class.forName("com.squareup.okhttp.OkHttpClient");
      return OkHttpLoaderCreator.create(context);
    } catch (ClassNotFoundException ignored) {
    }
    return new UrlConnectionDownloader(context);
  }

(4) Dispatcher里面主要是开了一个子线程,并创建了一个该线程的handler(核心代码)

this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this);

2.load

这个方法很多重载,但最终会走load(uri)或load(id).这里主要返回了一个RequestCreator,并且创建了一个Request.Builder对象并赋值给了他的data字段.

//load(uri)
public RequestCreator load(Uri uri) {
    return new RequestCreator(this, uri, 0);
  }
//load(id)
public RequestCreator load(int resourceId) {
    if (resourceId == 0) {
      throw new IllegalArgumentException("Resource ID must not be zero.");
    }
    return new RequestCreator(this, null, resourceId);
  }

RequestCreator的构造方法(创建了一个Request.Builder对象并赋值给了他的data字段)

  RequestCreator(Picasso picasso, Uri uri, int resourceId) {
    if (picasso.shutdown) {
      throw new IllegalStateException(
          "Picasso instance already shut down. Cannot submit new requests.");
    }
    this.picasso = picasso;
    this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig);
  }

3.into(代码如下,先大致看一眼,下面会接着分析)

  public void into(ImageView target, Callback callback) {
    long started = System.nanoTime();
    checkMain();

    if (target == null) {
      throw new IllegalArgumentException("Target must not be null.");
    }

    if (!data.hasImage()) {
      picasso.cancelRequest(target);
      if (setPlaceholder) {
        setPlaceholder(target, getPlaceholderDrawable());
      }
      return;
    }

    if (deferred) {
      if (data.hasSize()) {
        throw new IllegalStateException("Fit cannot be used with resize.");
      }
      int width = target.getWidth();
      int height = target.getHeight();
      if (width == 0 || height == 0) {
        if (setPlaceholder) {
          setPlaceholder(target, getPlaceholderDrawable());
        }
        picasso.defer(target, new DeferredRequestCreator(this, target, callback));
        return;
      }
      data.resize(width, height);
    }

    Request request = createRequest(started);
    String requestKey = createKey(request);

    if (shouldReadFromMemoryCache(memoryPolicy)) {
      Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
      if (bitmap != null) {
        picasso.cancelRequest(target);
        setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.indicatorsEnabled);
        if (picasso.loggingEnabled) {
          log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY);
        }
        if (callback != null) {
          callback.onSuccess();
        }
        return;
      }
    }

    if (setPlaceholder) {
      setPlaceholder(target, getPlaceholderDrawable());
    }

    Action action =
        new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
            errorDrawable, requestKey, tag, callback, noFade);

    picasso.enqueueAndSubmit(action);
  }

(1)检查是否是主线程,不是就报错

checkMain()

(2)检查是否设了uri或者id,没设的话就直接显示placeholder图片.

if (!data.hasImage()) {
  picasso.cancelRequest(target);//取消之前的任务
  if (setPlaceholder) {
    setPlaceholder(target, getPlaceholderDrawable());//显示placeholder图片
  }
  return;
}

(3)检查是否调用了fit(),如果是,代表需要将image调整为ImageView的大小
(之后会根据这个数值进行压缩)

if (deferred) {
  if (data.hasSize()) {
    throw new IllegalStateException("Fit cannot be used with resize.");
  }
  int width = target.getWidth();
  int height = target.getHeight();
  if (width == 0 || height == 0) {
    if (setPlaceholder) {
      setPlaceholder(target, getPlaceholderDrawable());
    }
    picasso.defer(target, new DeferredRequestCreator(this, target, callback));
    return;
  }
  data.resize(width, height);
}

(4)尝试从内存中中获取图片,如果获取到了,那么就显示,并且结束

if (shouldReadFromMemoryCache(memoryPolicy)) {
  Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
  if (bitmap != null) {
    picasso.cancelRequest(target);
    setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.indicatorsEnabled);
    if (picasso.loggingEnabled) {
      log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY);
    }
    if (callback != null) {
      callback.onSuccess();
    }
    return;
  }
}

(5)如果没有接着又会让显示placeholder图片

if (setPlaceholder) {
  setPlaceholder(target, getPlaceholderDrawable());
}

(6)然后整个请求会被封装成一个Action,并处理这个请求

Action action = new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
        errorDrawable, requestKey, tag, callback, noFade);
picasso.enqueueAndSubmit(action);

(7)如果以前这个iv已经有Action了,那么就把以前的Action替换掉,并调用dispatcher.dispatchCancel方法去处理
(里面会调用dispatcher的handler去处理,最终在子线程把该任务取消,这也就是为什么listview复用imageview不会错位显示的原因)**

void enqueueAndSubmit(Action action) {
    Object target = action.getTarget();
    if (target != null && targetToAction.get(target) != action) {
      // This will also check we are on the main thread.
      cancelExistingRequest(target);
      targetToAction.put(target, action);
    }
    submit(action);
  }

(8)接着会调用dispatcher.dispatchSubmit,也是通过Handler在子线程中处理.

void submit(Action action) {
    dispatcher.dispatchSubmit(action);
}
void dispatchSubmit(Action action) {
    handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
}

(9)接着会走到dispatcher的performSubmit方法(核心代码如下,先简单看看,下面会继续做分析)

    BitmapHunter hunter = hunterMap.get(action.getKey());
    if (hunter != null) {
      hunter.attach(action);
      return;
    }

    hunter = forRequest(action.getPicasso(), this, cache, stats, action);
    hunter.future = service.submit(hunter);
    hunterMap.put(action.getKey(), hunter);

(10)forRequest方法会返回一个BitmapHunter,他会根据你传的uri和id去获取RequestHandler并赋值给BitmapHunter的一个属

(11)接着会把这个BitmapHunter添加到线程池里,他其实也是一个Runnable

(12)当线程池处理这个任务时.会走BitmapHunter的run方法,里面主要就是调用了自己的hunt方法来获取bitmap,并根据返回结果,调用dispatcher在子线程中进行不同的处理

  result = hunt();
  if (result == null) {
    dispatcher.dispatchFailed(this);
  } else {
    //如果是成功的话 会根据你的MemoryPolicy来决定是否保存在内存中
    dispatcher.dispatchComplete(this);
  }

(13)hunt方法的核心代码如下(先简单看看,下面会继续做分析)

  Bitmap hunt() throws IOException {
    Bitmap bitmap = null;

    //尝试从内存中读取
    if (shouldReadFromMemoryCache(memoryPolicy)) {
      bitmap = cache.get(key);
      if (bitmap != null) {
        stats.dispatchCacheHit();
        loadedFrom = MEMORY;
        return bitmap;
      }
    }

    //核心代码
    data.networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
    RequestHandler.Result result = requestHandler.load(data, networkPolicy);
    if (result != null) {
      loadedFrom = result.getLoadedFrom();
      exifRotation = result.getExifOrientation();

      bitmap = result.getBitmap();

      // If there was no Bitmap then we need to decode it from the stream.
      if (bitmap == null) {
        InputStream is = result.getStream();
        try {
          bitmap = decodeStream(is, data);
        } finally {
          Utils.closeQuietly(is);
        }
      }
    }

    //对bitmap做预处理
    if (bitmap != null) {
      stats.dispatchBitmapDecoded(bitmap);
      if (data.needsTransformation() || exifRotation != 0) {
        synchronized (DECODE_LOCK) {
          if (data.needsMatrixTransform() || exifRotation != 0) {
            bitmap = transformResult(data, bitmap, exifRotation);
          }
        ...
      }
    }

    return bitmap;
  }

(14)hunt方法首先会再次尝试从内存中读取图片,如果成功,就结束

    //尝试从内存中读取
    if (shouldReadFromMemoryCache(memoryPolicy)) {
      bitmap = cache.get(key);
      if (bitmap != null) {
        stats.dispatchCacheHit();
        loadedFrom = MEMORY;
        return bitmap;
      }
    }

(15)如果不行,会调用RequestHandler的load方法,因为刚才forRequest方法根据uri和id返回了不同的RequestHandler子类给到BitmapHunter中,而不同的RequestHandler子类重写了不同了load方法.

RequestHandler.Result result = requestHandler.load(data, networkPolicy);

(16)这些RequestHandler子类是在Picasso的构造函数中创建的

  Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
    ...
    allRequestHandlers.add(new ContactsPhotoRequestHandler(context));
    allRequestHandlers.add(new MediaStoreRequestHandler(context));
    allRequestHandlers.add(new ContentStreamRequestHandler(context));
    allRequestHandlers.add(new AssetRequestHandler(context));
    allRequestHandlers.add(new FileRequestHandler(context));
    allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats));
    requestHandlers = Collections.unmodifiableList(allRequestHandlers);
    ...
  }

(17)如果你的uri是网络地址的话 走的是NetworkRequestHandler的load方法

(1)这里有个细节就是Utils类里面初始化了本地缓存的路径和大小,在getCacheDir()的picasso-cache目录下,大小为2%的SD内存,最小5MB最大50MB

(2)load方法里面主要用okhttp的同步请求,而且会根据你的networkPolicy来决定从内存还是本地,还是直接冲网络读取.

(18)load方法走完后会返回一个bitmap或者InputStream.
(如果是bitmap的话,其实已经对bitmap做了压缩处理,如果是InputStream,也会接着对读处理的bitmap做压缩处理)

if (result != null) {
 ...
  bitmap = result.getBitmap();

  if (bitmap == null) {
    InputStream is = result.getStream();
    try {
      bitmap = decodeStream(is, data);//压缩处理,想要详细了解的可以进这个方法看
    } finally {
      Utils.closeQuietly(is);
    }
  }
}
如果图片的宽高大于target的宽高的话,会按比例压缩(如果宽是2倍,高是3倍,那么压缩2倍)
例:imageview的宽高都是100,图片的宽是200,高是300,那么图片会被压缩两倍,也就是宽100,高150.

(19)接着会看图片是否需要预处理,如果需要,那么就进行预处理

  if (data.needsTransformation() || exifRotation != 0) {
    synchronized (DECODE_LOCK) {
      if (data.needsMatrixTransform() || exifRotation != 0) {
        bitmap = transformResult(data, bitmap, exifRotation);      
      }
      if (data.hasCustomTransformations()) {
        bitmap = applyCustomTransformations(data.transformations, bitmap);

      }
    }
    if (bitmap != null) {
      stats.dispatchBitmapTransformed(bitmap);
    }
  }

这里加了锁,保证在同一时间,系统只在解析一个图片,防止多线程解析,出现OOM风险,也减少了同一时刻对硬件设备的性能开销。

(20)BitmapHunter的hunt方法走完会回到他的run方法中并根据返回结果,调用dispatcher在子线程中进行不同的处理(即步骤12)

  result = hunt();
  if (result == null) {
    dispatcher.dispatchFailed(this);
  } else {
    //如果是成功的话 会根据你的MemoryPolicy来决定是否保存在内存中
    dispatcher.dispatchComplete(this);
  }

3.后续

Picasso的源码分析大致就是这样,但是我们看源码最重要的原因是为了学习里面的代码和原理,并应用到我们的项目中,所以接下来我会总结一下我在项目中如何对图片加载进行相应的优化和封装,敬请期待吧~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值