Android 源码解析: 图片加载库Picasso 1


Picasso是一个轻量级的图片缓存库。Picasso不仅实现了图片异步加载的功能,还解决了android中加载图片时需要解决的一些常见问题:

   1.在adapter中需要取消已经不在视野范围的ImageView图片资源的加载,否则会导致图片错位,Picasso已经解决了这个问题。

   2.使用复杂的图片压缩转换来尽可能的减少内存消耗

   3.自带内存和硬盘二级缓存功能


Picasso有如下特性:

  • 处理Adapter中的 

ImageView

  • 使用最少的内存完成复杂的图片转换,比如把下载的图片转换为圆角等
  • 自动添加磁盘和内存缓存


和其他一些下载图片项目的主要区别之一是:使用4.0+系统上的HTTP缓存来代替磁盘缓存。

同类比较

picasso 和Volley都是用http响应的内容做缓存, 这样可以检测该内容是否过期,如果过期则重新请求新数据,和浏览器缓存一样的策略。 

但是在实际应用中我发现大部分的图片缓存都不会过期,也就是一个地址对应一个图片(大部分情况下都是这样 不存在图片过期的问题),并且有些情况应用还有保存图片到本地的功能。 

所以综合对比情况如下: 

1. picasso 比 AUIL(Android-Universal-Image-Loader) 的代码简单写 

2. picasso 具有检测缓存是否失效并重新获取功能 

3. AUIL具有保存图片或者二次利用图片的性能优势,直接拿过来就可以用了 不用再次解析http响应数据。

使用:

Picasso使用的方法汇总:
Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);
Picasso.with(context).load(url).into(view);
Picasso.with(context).load(url) .resize(50, 50).centerCrop().into(imageView);
//这里的placeholder将resource传入通过getResource.getDrawable取资源,所以可以是张图片也可以是color id
Picasso.with(context).load(url).placeholder(R.drawable.user_placeholder).error(R.drawable.user_placeholder_error).into(imageView);
Picasso.with(context).load(R.drawable.landing_screen).into(imageView1);
Picasso.with(context).load("file:///android_asset/DvpvklR.png").into(imageView2);
Picasso.with(context).load(new File(...)).into(imageView3);

//这里显示notification的图片
Picasso.with(activity).load(Data.URLS[new Random().nextInt(Data.URLS.length)]).resizeDimen(R.dimen.notification_icon_width_height,    R.dimen.notification_icon_width_height).into(remoteViews, R.id.photo, NOTIFICATION_ID, notification);

//这里是通过设置tag标签,就是当前传过来的context,这样就可以根据这个context tag来pause和resume显示了
Picasso.with(context).load(url).placeholder(R.drawable.placeholder).error(R.drawable.error).fit().tag(context).into(view);
//监听onScrollStateChanged的时候调用执行
picasso.resumeTag(context);
picasso.pauseTag(context);

Picasso.with(context).load(contactUri).placeholder(R.drawable.contact_picture_placeholder).tag(context).into(holder.icon);
//这个onpause方法里的这段代码还是很有意思的
@Override protected void onPause() {
    super.onPause();
    if (isFinishing()) {
      // Always cancel the request here, this is safe to call even if the image has been loaded.
      // This ensures that the anonymous callback we have does not prevent the activity from
      // being garbage collected. It also prevents our callback from getting invoked even after the
      // activity has finished.
      Picasso.with(this).cancelRequest(imageView);
    }
  }
// Trigger the download of the URL asynchronously into the image view.
    Picasso.with(context)
        .load(url)
        .placeholder(R.drawable.placeholder)
        .error(R.drawable.error)
        .resizeDimen(R.dimen.list_detail_image_size, R.dimen.list_detail_image_size)
        .centerInside()
        .tag(context)
        .into(holder.image);
//Picasso.with使用的是单例模式
Picasso.with(this).cancelTag(this);

如上,Picasso 的使用是非常简单的:



该段代码依次创建了一个Picasso对象,一个ReqeustCreator对象,一个Request对象和一个ImageViewAction对象。



查看源码:看看Picasso做了什么?



1  Picasso.with(Context):



/**
   * The global default {@link Picasso} instance.
   * <p>
   * This instance is automatically initialized with defaults that are suitable
   * to most implementations.
   * <ul>
   * <li>LRU memory cache of 15% the available application RAM</li>
   * <li>Disk cache of 2% storage space up to 50MB but no less than 5MB. (Note:
   * this is only available on API 14+ <em>or</em> if you are using a standalone
   * library that provides a disk cache on all API levels like OkHttp)</li>
   * <li>Three download threads for disk and network access.</li>
   * </ul>
   * <p>
   * If these settings do not meet the requirements of your application you can
   * construct your own with full control over the configuration by using
   * {@link Picasso.Builder} to create a {@link Picasso} instance. You can
   * either use this directly or by setting it as the global instance with
   * {@link #setSingletonInstance}.
   */
  public static Picasso with(Context context) {
    if (singleton == null) {
      synchronized (Picasso.class) {
        if (singleton == null) {
          singleton = new Builder(context).build();
        }
      }
    }
    return singleton;
  }



  • 单例模式
  • 延时初始化来保证Picasso只有在使用到的时候,才会初始化对象
  • 同步对象为类对象,保证同步范围整个JVM中。
  • 构造者模式,通过Builder类构建了一个默认配置的Picasso的对象。



/** Create the {@link Picasso} instance. */
    public Picasso build() {
      Context context = this.context;
      if (downloader == null) {
        <span style="color:#ff0000;">downloader = Utils.createDefaultDownloader(context);</span>
      }
      if (cache == null) {
        <span style="color:#ff0000;">cache = new LruCache(context);</span>
      }
      if (service == null) {
        <span style="color:#ff0000;">service = new PicassoExecutorService();</span>
      }
      if (transformer == null) {
        <span style="color:#ff0000;">transformer = RequestTransformer.IDENTITY;</span>
      }
     <span style="color:#ff0000;"> Stats stats = new Stats(cache);
      Dispatcher dispatcher = new Dispatcher(context, service, HANDLER,
          downloader, cache, stats);</span>
     <span style="color:#ff6666;"> return new Picasso(context, dispatcher, cache, listener, transformer,
          requestHandlers, stats, defaultBitmapConfig, indicatorsEnabled,
          loggingEnabled);</span>
    }
  }



  • Downloader
  • DownLoader就是下载用的工具类,在Picasso当中,如果OKHttp可以使用的话,就会默认使用OKHttp,如果无法使用的话,就会使用UrlConnectionDownloader(默认使用HttpURLConnection实现)。
  • Cache 
  • 默认实现为LruCache,就是使用LinkedHashMap实现的一个Cache类,注意的一个地方就是,在其他的地方,我们一般默认的是限制的capacity,但是这个地方我们是限制的总共使用的内存空间。因此LruCache在实现的时候,其实简单理解就是将LinkedHashMap封装,然后基于LinkedHashMap的方法实现Cache的方法,在Cache的set()方法的时候,会不断计算当前还可以使用的空间大小,要是超出范围,则删除之前保存的数据。
  • ExecutorService
  • 默认的实现为PicassoExecutorService,该类也比较简单,其实就是ThreadPoolExecutor,在其功能的基础上继续封装,在其中有一个比较细心的功能就是,Picasso通过PicassoExecutorService设置线程数量,来调整在2G/3G/4G/WiFi不同网络情况下的不同表现。
  • RequestTransformer
  • ReqeustTransformer是一个接口,用来预处理Reqeust,可以用来将请求进行预先处理,比如改个域名啥的。
  • Stats
  • 主要是一些统计信息,比如cache hit/miss,总共下载的文件大小,下载过的图片数量,转换的图片数量等等。
  • Dispatcher
  • 启动了一个DispatcherThread线程
  • 初始化了一个用来处理消息的DispatcherHandler,注意,根据Dispatcher中默认配置,该Handler所有数据的处理是在DispatcherThread之上。
  • 初始化并注册了一个网络状态广播接收器。



3  load(xx)方法:



/**
   * Start an image request using the specified URI.
   * <p>
   * Passing {@code null} as a {@code uri} will not trigger any request but will
   * set a placeholder, if one is specified.
   * 
   * @see #load(File)
   * @see #load(String)
   * @see #load(int)
   */
  public RequestCreator load(Uri uri) {
    return new <span style="color:#ff0000;">RequestCreator</span>(this, uri, 0);
  }

  /**
   * Start an image request using the specified path. This is a convenience
   * method for calling {@link #load(Uri)}.
   * <p>
   * This path may be a remote URL, file resource (prefixed with {@code file:}),
   * content resource (prefixed with {@code content:}), or android resource
   * (prefixed with {@code android.resource:}.
   * <p>
   * Passing {@code null} as a {@code path} will not trigger any request but
   * will set a placeholder, if one is specified.
   * 
   * @see #load(Uri)
   * @see #load(File)
   * @see #load(int)
   * @throws IllegalArgumentException
   *           if {@code path} is empty or blank string.
   */
  public RequestCreator load(String path) {
    if (path == null) {
      return new RequestCreator(this, null, 0);
    }
    if (path.trim().length() == 0) {
      throw new IllegalArgumentException("Path must not be empty.");
    }
    return load(Uri.parse(path));
  }
  /**
   * Start an image request using the specified image file. This is a
   * convenience method for calling {@link #load(Uri)}.
   * <p>
   * Passing {@code null} as a {@code file} will not trigger any request but
   * will set a placeholder, if one is specified.
   * <p>
   * Equivalent to calling {@link #load(Uri) load(Uri.fromFile(file))}.
   * 
   * @see #load(Uri)
   * @see #load(String)
   * @see #load(int)
   */
  public RequestCreator load(File file) {
    if (file == null) {
      return new RequestCreator(this, null, 0);
    }
    return load(Uri.fromFile(file));
  }

  /**
   * Start an image request using the specified drawable resource ID.
   * 
   * @see #load(Uri)
   * @see #load(String)
   * @see #load(File)
   */
  public RequestCreator load(int resourceId) {
    if (resourceId == 0) {
      throw new IllegalArgumentException("Resource ID must not be zero.");
    }
    return new RequestCreator(this, null, resourceId);
  }


Picasso通过调用load(Uri uri),返回了一个传入Picasso对象和要访问的网址或路径的Uri的ReqeustCreator。


接下来就是ReqeustCreator的构建方法。通过ReqeustCreator 将请求去发到子线程,去完成该任务。



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;
    data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig);
  }








4 into(xx)方法



/**
   * Asynchronously fulfills the request into the specified {@link ImageView}.
   * <p>
   * <em>Note:</em> This method keeps a weak reference to the {@link ImageView}
   * instance and will automatically support object recycling.
   */
  public void into(ImageView target) {
    into(target, null);
  }


into(ImageView imageview)实际上调用的是into(ImageView imageview, Callback callback)方法,我们查看其源代码:

/**
   * Asynchronously fulfills the request into the specified {@link ImageView}
   * and invokes the target {@link Callback} if it's not {@code null}.
   * <p>
   * <em>Note:</em> The {@link Callback} param is a strong reference and will
   * prevent your {@link android.app.Activity} or {@link android.app.Fragment}
   * from being garbage collected. If you use this method, it is <b>strongly</b>
   * recommended you invoke an adjacent
   * {@link Picasso#cancelRequest(android.widget.ImageView)} call to prevent
   * temporary leaking.
   */
  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, request.isShowGif);
        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. 退出。
  1. 退出。
  2. 判断是否设置过targetSize,如果已经设置过,则抛出异常退出 (不太明白为什么要检查是否已经要设置过targetSize)
  3. 然后获取target,即ImageView对应的长和宽,如果长和宽都是0,那么就设置默认的图片,并构建一个DeferredRequestCreator,放入Picasso对应的队列当中。
  4. 重新设置data即ReqeustCreator对应的targetWidth和targetHeight
  1. 创建Reqeust,生成requestKey
  2. 判断是否需要跳过MemoryCache,如果不跳过,那么就尝试获取图片,并取消对应的请求,进行回调。
  3. 如果需要设置默认的图片,则在这里进行设置
  4. 生成对应的ImageViewAction
  5. 将生成的ImageViewAction添加到队列当中。

into()方法总结:检查配置的合法性,然后根据配置决定是放入延时队列还是立刻执行,如果立刻执行,则创建对应的请求并尝试从MemoryCache中获取,如果不成功,则生成对应的Action并提交到Picasso的队列当中。然后就是Picasso的任务调度了。