Picasso的使用和源码解析

一、基本介绍

picasso是Square公司开源的一个Android图片下载缓存库,github地址https://github.com/square/picasso,可以实现图片下载和缓存功能。

Picassso的特点有:
自动将图像缓存在本地,自带内存和硬盘二级缓存功能

通过图片压缩转换以减少内存消耗
自动处理了ImageView的回收,自动取消不在视野范围内的ImageView视图资源的加载支持网络图片,drawable资源,asset资源,本地图片等多种资源加载支持调试,调用函数 Picasso.setIndicatorsEnabled(true) 可以在加载的图片左上角显示一个三形,不同的颜色代表不同的加载来源。
二、使用方法
1、gradle 配置

compile 'com.squareup.picasso:picasso:2.5.2'

2、普通图片加载方式

只需要一行代码就能完全实现图片的异步加载:

Picasso.with(context).load("http://img.my.csdn.net/uploads/201605/08/1462674108_9582.jpg").into(imageView);

3、ADAPTER 中加载图片:Adapter的重用会被自动检测到,Picasso会取消上次的加载

@Override 
public void getView(int position, View convertView, ViewGroup parent) {
  Imageview view = (ImageView) convertView;
  if (view == null) {
    view = new ImageView(context);
  }
  String url = getItem(position);
  Picasso.with(context).load(url).into(view);
}

4、图片转换:转换图片以适应布局大小并减少内存占用

 Picasso.with(context)
  .load(url)
  .resize(50, 50)
  .centerCrop()
  .into(imageView);

5、Place holders-空白或者错误占位图片:

picasso提供了两种占位图片,未加载完成或者加载发生错误的时需要一张图片作为提示。

Picasso.with(context)
  .load(url)
  .placeholder(R.drawable.placeholder)
  .error(R.drawable.placeholder_error)
.into(imageView);

6、多种资源文件的加载:

除了加载网络图片picasso还支持加载Resources, assets, files, content providers中的资源文件。

Picasso.with(context).load(R.drawable.landing_screen).into(imageview);
Picasso.with(context).load(new File(...)).into(imageview);
Picasso.with(context).load("file:///android_asset/robert.png").into(imageview);

三、源码解析

1、构建方式

Picasso使用Builder创建对象,我们一般使用public static Picasso with(Context context)方法

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

Picasso的with方法返回Picasso的单例,但是有Builderg构造器,Picasso不是严格意义上的单例模式。
多次build还是可以创建多个实例的。

使用build构建可以自定义线程池、缓存、下载器等方法。

2、资源加载方式

实现RequestHandler接口就可以定义资源加载方式,默认有7种

allRequestHandlers.add(new ResourceRequestHandler(context));//drawable资源图
allRequestHandlers.add(new ContactsPhotoRequestHandler(context));//通讯录图片
allRequestHandlers.add(new MediaStoreRequestHandler(context));//多么媒体资源库图片
allRequestHandlers.add(new ContentStreamRequestHandler(context));//Provider图片
allRequestHandlers.add(new AssetRequestHandler(context));//asset中的图片
allRequestHandlers.add(new FileRequestHandler(context));//存储设备中的图片
allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats));//网络图片

可以通过extraRequestHandlers添加新的支持方法,定义新的RequestHandler,需要实现两个方法

public abstract boolean canHandleRequest(Request data);
public abstract Result load(Request request, int networkPolicy) throws IOException;

canHandleRequest定义了在什么情况下用这种方式
load定义了加载具体加载方式
具体看一下NetworkRequestHandler的具体实现,相关解析直接加注释

class NetworkRequestHandler extends RequestHandler {
  static final int RETRY_COUNT = 2;//重试次数

  private static final String SCHEME_HTTP = "http";//识别的scheme
  private static final String SCHEME_HTTPS = "https";//识别的scheme

  private final Downloader downloader;//下载器
  private final Stats stats;//状态
 /**
  * 构造方法
  */
  public NetworkRequestHandler(Downloader downloader, Stats stats) {
    this.downloader = downloader;
    this.stats = stats;
  }

  @Override public boolean canHandleRequest(Request data) {
    String scheme = data.uri.getScheme();
    return (SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme));
  }

  @Override public Result load(Request request, int networkPolicy) throws IOException {
    Response response = downloader.load(request.uri, request.networkPolicy);
    if (response == null) {
      return null;
    }
    Picasso.LoadedFrom loadedFrom = response.cached ? DISK : NETWORK;
    Bitmap bitmap = response.getBitmap();
    if (bitmap != null) {
      return new Result(bitmap, loadedFrom);
    }

    InputStream is = response.getInputStream();
    if (is == null) {
      return null;
    }
    // Sometimes response content length is zero when requests are being replayed. Haven't found
    // root cause to this but retrying the request seems safe to do so.
    if (loadedFrom == DISK && response.getContentLength() == 0) {
      Utils.closeQuietly(is);
      throw new ContentLengthException("Received response with 0 content-length header.");
    }
    if (loadedFrom == NETWORK && response.getContentLength() > 0) {
      stats.dispatchDownloadFinished(response.getContentLength());
    }
    return new Result(is, loadedFrom);
  }

  @Override int getRetryCount() {
    return RETRY_COUNT;
  }

  @Override boolean shouldRetry(boolean airplaneMode, NetworkInfo info) {
    return info == null || info.isConnected();
  }

  @Override boolean supportsReplay() {
    return true;
  }
  static class ContentLengthException extends IOException {
    public ContentLengthException(String message) {
      super(message);
    }
  }
}

3、请求创建器RequestCreator

Picasso的load方法返回RequestCreator,RequestCreator有两个功能
配置加载参数。
包括placeHolder与error图片,加载图片的大小、旋转、居中等属性。
执行加载。
通过调用into(object)方法进行加载。

 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));
    }

into方法只能在主线程调用,否则会抛出异常。如果没有要加载的资源,请求会被取消,提高了执行效率

static void checkMain() {
  if (!isMain()) {
    throw new IllegalStateException("Method call should happen from the main thread.");
      }
}

取消操作是Picasso的方法,也只能在主线程调用

/**
 * Cancel any existing requests for the specified target {@link ImageView}.
 */
public void cancelRequest(ImageView view) {
    // checkMain() is called from cancelExistingRequest()
    if (view == null) {
        throw new IllegalArgumentException("view cannot be null.");
    }
    cancelExistingRequest(view);
}

如果target不为空,执行线程正常,就可以正常构建request,创建requestkey,requestkey使用简单的属性拼接方法
如果设置了内存缓存,那么从内存缓存中读取图片,图片不为空,直接设置图片。
如果没有设置缓存,或者从缓存中读取到的图片为空,那么创建图片获取任务,

4、执行任务Action

Action是一个抽象类,可以理解为一个加载任务,
FetchAction 缓存图片
RemoteViewsAction 给RemoteView设置图片
GetAction 同步获取图片
ImageViewAction 给ImageView设置图片

TargetAction 对Target设置图片

调用RequestCreator对应方法会对应创建所需要的Action

public void into(Target target)
public void fetch() 
public Bitmap get() throws IOException
public void into(ImageView target)
public void into(RemoteViews remoteViews, int viewId, int notificationId,Notification notification)

需要特别注意的是 public Bitmap get() throws IOException 是同步方法,其他几个是异步方法

异步方法将Action提交到Dispatcher,同步方法直接使用BitmapHunter获取图片

获取完图片,处理图片的方法是

  abstract void complete(Bitmap result, Picasso.LoadedFrom from);

不同子类对应不同实现

5、事件分发器

默认的Dispatcher创建方法

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

context参数就不用多说了,service是Pecasso的异步线程池,HANDLER用来向主线程抛消息,downloader是下载器

cache是图片缓存,stats是执行状态

Dispatcher是分发器,由Picasso或Hunter来调用。Dispatcher主要方法有
dispatcherSubmit()和dispatcherCancel()
hunter中加入action便调用dispatcherSubmit(),hunter中取消action便调用dispatcherCancel()
dispatcherComplete()和dispatcherError()
加载结束时调用。均调用batch方法,不过complete操作会将bitmap加入到cache中,以便后续调用。
batch()
起缓冲作用,每隔200毫秒执行一次performBatchComplete()批处理。批处理将hunterList回调给Picasso,Picasso对每个hunter的每个action进行结果回调。

Dispatcher启动了自己的线程dispatcherThread。DispatcherHandler运行在DispatcherThread中。

RequestCreator中调用了Picasso的submit方法,将acton提交到Dispatcher,
DispatcherHandler发送了REQUEST_SUBMIT这个消息,然后在DispatcherHandler所在线程中执行了performSubmit,
在performSubmit中创建BitmapHunter,进行下载。

  void performSubmit(Action action, boolean dismissFailed) {
    if (pausedTags.contains(action.getTag())) {
      pausedActions.put(action.getTarget(), action);
      if (action.getPicasso().loggingEnabled) {
        log(OWNER_DISPATCHER, VERB_PAUSED, action.request.logId(),
            "because tag '" + action.getTag() + "' is paused");
      }
      return;
    }

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

    if (service.isShutdown()) {
      if (action.getPicasso().loggingEnabled) {
        log(OWNER_DISPATCHER, VERB_IGNORED, action.request.logId(), "because shut down");
      }
      return;
    }

    hunter = forRequest(action.getPicasso(), this, cache, stats, action);
    hunter.future = service.submit(hunter);
    hunterMap.put(action.getKey(), hunter);
    if (dismissFailed) {
      failedActions.remove(action.getTarget());
    }

    if (action.getPicasso().loggingEnabled) {
      log(OWNER_DISPATCHER, VERB_ENQUEUED, action.request.logId());
    }
  }

6、图片获取BitmapHunter

BitmapHunter是一个Runnable,作用是获取图片。
BitmapHunter的执行流程:在run()方法中执行hunt()方法尝试获取图片,把结果交给Dispatcher回调。

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

    if (shouldReadFromMemoryCache(memoryPolicy)) {
      bitmap = cache.get(key);
      if (bitmap != null) {
        stats.dispatchCacheHit();
        loadedFrom = MEMORY;
        if (picasso.loggingEnabled) {
          log(OWNER_HUNTER, VERB_DECODED, data.logId(), "from cache");
        }
        return bitmap;
      }
    }

    data.networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
    RequestHandler.Result result = requestHandler.load(data, networkPolicy);
    if (result != null) {
      loadedFrom = result.getLoadedFrom();
      exifOrientation = result.getExifOrientation();
      bitmap = result.getBitmap();
      ........
    }

    if (bitmap != null) {
      if (picasso.loggingEnabled) {
        log(OWNER_HUNTER, VERB_DECODED, data.logId());
      }
      stats.dispatchBitmapDecoded(bitmap);
      if (data.needsTransformation() || exifOrientation != 0) {
        synchronized (DECODE_LOCK) {
          if (data.needsMatrixTransform() || exifOrientation != 0) {
            bitmap = transformResult(data, bitmap, exifOrientation);
            if (picasso.loggingEnabled) {
              log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId());
            }
          }
          if (data.hasCustomTransformations()) {
            bitmap = applyCustomTransformations(data.transformations, bitmap);
            if (picasso.loggingEnabled) {
              log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId(), "from custom transformations");
            }
          }
        }
        if (bitmap != null) {
          stats.dispatchBitmapTransformed(bitmap);
        }
      }
    }

    return bitmap;
  }

7、缓存处理

内存缓存使用LruCache,实现是在LruCache.java中,动态分配缓存大小,大小为可用内存的1/7,

磁盘缓存使用了网络框架的缓存方案。

8、网络请求处理

定义了三种下载器,分别用于okhttp3,okhttp,urlconneciton

static Downloader createDefaultDownloader(Context context) {
    if (SDK_INT >= GINGERBREAD) {
      try {
        Class.forName("okhttp3.OkHttpClient");
        return OkHttp3DownloaderCreator.create(context);
      } catch (ClassNotFoundException ignored) {
      }
      try {
        Class.forName("com.squareup.okhttp.OkHttpClient");
        return OkHttpDownloaderCreator.create(context);
      } catch (ClassNotFoundException ignored) {
      }
    }
    return new UrlConnectionDownloader(context);
  }

9、线程池优化

PicassoExecutorService根据网络状况,使用不同的线程池,命中次数多的,优先级高

    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);
    }

发表评论

电子邮件地址不会被公开。