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

来源:互联网 发布:.win域名 编辑:程序博客网 时间:2024/06/05 13:35

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


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

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

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

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

Picasso有如下特性:

  • 处理Adapter中的 ImageView 回收和取消已经回收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 idPicasso.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 的使用是非常简单的:

1
Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);
该段代码依次创建了一个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的对象。
2   构建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
    • Picasso当中,分发任务的线程,这是我们以后要重点研究的一个类,先标记一下,这个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. 首先检查当前是否工作在主线程,如果不是在主线程上的话,直接抛出异常退出
  2. 然后判断data.hasImage(),这里面这个函数命名比较迷惑人了,一开始我还以为是判断是否已经有解析好的图片,但再往下看一看源代码,原来这个代码是判断Uri和resourceId是否有设置了一个,如果没有,则说明该Reqeust没有数据源,是错误的。
    1. 如果没有Uri或者resource Id,那么则取消该请求,并设置默认的显示图片并退出
  3. 该图片是否需要延时执行(这个属性还不太清楚在哪里配置的,不清楚是手动指定还是自动设置)。如果需要延时执行,则按照以下步骤执行。
    1. 判断是否设置过targetSize,如果已经设置过,则抛出异常退出 (不太明白为什么要检查是否已经要设置过targetSize)
    2. 然后获取target,即ImageView对应的长和宽,如果长和宽都是0,那么就设置默认的图片,并构建一个DeferredRequestCreator,放入Picasso对应的队列当中。
    3. 重新设置data即ReqeustCreator对应的targetWidth和targetHeight
  4. 创建Reqeust,生成requestKey
  5. 判断是否需要跳过MemoryCache,如果不跳过,那么就尝试获取图片,并取消对应的请求,进行回调。
  6. 如果需要设置默认的图片,则在这里进行设置
  7. 生成对应的ImageViewAction
  8. 将生成的ImageViewAction添加到队列当中。

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


1 0