频道栏目
首页 > 资讯 > 其他 > 正文

Picasso 用法及源码解析

17-06-08        来源:[db:作者]  
收藏   我要投稿

Picasso 用法及源码解析。Picasso 是 Square 公司开源的一个 Android 平台优秀图片加载框架,易用、代码简洁、可读性高。自己接触的第一个开源图片加载框架也是 Picasso,以前只停留在会用阶段,根本不知道是如何实现的,最近花了点时间看了 Picasso 源码,学到的东西还是蛮多;大概理解实现基本流程。

对于看源码这事,一开始真不知从哪里下手。于是 Google 了,发现很多都是从使用方法入手,理清方法间是如何调用,最后形成自己的线索。本文也是按照这种方式来分析 Picasso 源码。

Picasso 使用方法

使用 Picasso 加载一张图片很简单,一行代码就搞定
Picasso.whth(context)
    .load(R.mipmap.ic_default)
    .placeholder(R.mipmap.ic_default)
    .error(R.mipmap.ic_default)
    .into(imageView);
加载一张图片并且按照指定尺寸以 centerCrop 形式对图片进行缩放
Picasso.with(this)
    .load(R.mipmap.ic_default)
    .resize(200, 200)
    .centerCrop()
    .into(imageView);
加载一张图片并且按照指定尺寸以 centerInside 形式图片进行缩放(注:对图片进行处理时,centerCropcenterInside 只能选择一种方式,并且必须调用方法 resize(targetWidth, targetHeight) 或者 resizeDimen(targetWidthResId, targetHeightResId) 设置大小)
Picasso.with(this)
    .load(R.mipmap.ic_default)
    .resizeDimen(R.dimen.width, R.dimen.height)
    .centerInside()
    .into(imageView);
加载一张图片并且按照一定角度对其进行旋转
Picasso.with(this)
    .load(R.mipmap.ic_launcher)
    .rotate(20)
    .into(imageView);
加载一张图片自适应目标视图(由于调整大小适应目标视图,结果导致请求被延迟,直到调整完毕才会发送请求;目标视图只能是 ImageView)
Picasso.with(this)
    .load(R.mipmap.ic_launcher)
    .fit()
    .into(imageView);
加载一张图片并设置回调接口
Picasso.with(this).load(R.mipmap.ic_launcher).into(mImageView, new Callback() {
    @Override
    public void onSuccess() {

    }

    @Override
    public void onError() {

    }
});

以上只是 Picasso 简单的用法,至于其它用法看API;接下来分析下源码。

Picasso 源码解析

Picasso.with() 方法解析

为了探究 Picasso.with() 方法如何实现,唯独从源代码找答案。代码如下:

/**
   * The global default {@link Picasso} instance.
   * 

* This instance is automatically initialized with defaults that are suitable to most * implementations. *

  • *
  • LRU memory cache of 15% the available application RAM
  • *
  • Disk cache of 2% storage space up to 50MB but no less than 5MB. (Note: this is only * available on API 14+ or if you are using a standalone library that provides a disk * cache on all API levels like OkHttp)
  • *
  • Three download threads for disk and network access.
  • *
*

* 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 采用两级缓存:内存缓存和磁盘缓存。

LRU 内存缓存大小占整个应用可用 RAM 容量的 15%。

磁盘缓存大小占存储空间的 5%,不少于 2 MB,不超过 50 MB。(注:仅适于 API 14+ 或者所使用的第三方库包含 API,比如 OkHttp)。

为磁盘访问和网络访问提供 3 个线程。

这些配置是 Picasso 默认配置的,如果不满足自己的需求,可以自己定制。通过 Picasso.Builder 创建 Picasson 实例,根据自己需要配置相关属性,并调用方法 setSingletonInstance(picasso) 设置为全局实例。

很明显可以看到,使用单例模式来创建 Picasso 实例,保证全局只有一个实例存在。简单说下 with() 方法的实现,singleton 为 null 时调用 Builder 类中 build() 方法创建 singleton 实例并返回,那么接下来就来看 Builder 类中 build() 方法是如何实现的?

Picasso.Builder 中 build() 方法解析

源码如下:

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, requestHandlers, stats,
          defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
}

Builder 类是 Picasso 这个类中的静态内部类,而 build() 方法是 Builder 类中的成员方法,可以看出采用建造者模式。那么 build() 方法实现的功能主要有:

创建默认下载器 Downloader

创建默认内存缓存 LruCache (由于接口 Cache 支持多线程访问,所以实现该接口时需确保线程安全)

创建默认线程池 PicassoExecutorService

创建默认请求转发器 RequestTransformer

创建默认统计 Stats

创建默认调度器 Dispatcher

创建 Picasso 实例

那么这些实例是如何创建的,下面主要通过流程图来解析(主要是方法间的调用以及方法内部部分细节)。

Downloader 是一个接口,无法实例化,需要通过实现类创建实例,该接口的功能主要从磁盘缓存加载图片或网络下载图片。OkHttpDownloader 和 UrlConnectionDownloader 分别实现该接口,那么创建实例也是通过这两个实现类来创建的,那么来看下实例化 Downloader 流程。

这里写图片描述

LruCache 是内存缓存类,实现接口 Cache,采用最近最少使用算法。

这里写图片描述

PicassoExecutorService 继承 ThreadPoolExecutor,是线程池,供图片下载,线程数根据不同网络类型设置,默认的线程数是 3 个,直接通过 new 操作符实例化对象。

RequestTransformer 是一个接口,功能是在发送请求之前对图片进行转换处理。从源码中可以看出,这是一个测试功能,在后续版本可能不兼容,使用该功能时得谨慎。

对于 Stats 实例的创建,直接 new 一个对象,那么主要来看该构造方法做了哪些操作?代码如下:

Stats(Cache cache) {
    this.cache = cache;
    // statsThread 是子线程,注意记得调用 start() 方法
    this.statsThread = new HandlerThread(STATS_THREAD_NAME, THREAD_PRIORITY_BACKGROUND);
    this.statsThread.start();
    Utils.flushStackLocalLeaks(statsThread.getLooper());
    // 注意该 handler 是在子线程里的
    this.handler = new StatsHandler(statsThread.getLooper(), this);
 }

主要是实例化对象,结合注释应该不难理解。

Dispatcher 实例的创建与 Stats 类似,代码如下:

Dispatcher(Context context, ExecutorService service, Handler mainThreadHandler,
      Downloader downloader, Cache cache, Stats stats) {
    // dispatcherThread 是子线程,注意记得调用 start() 方法  
    this.dispatcherThread = new DispatcherThread();
    this.dispatcherThread.start();
    Utils.flushStackLocalLeaks(dispatcherThread.getLooper());
    this.context = context;
    this.service = service;
    this.hunterMap = new LinkedHashMap();
    this.failedActions = new WeakHashMap();
    this.pausedActions = new WeakHashMap();
    this.pausedTags = new HashSet();
    // 注意该 handler 是在子线程的
    this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this);
    this.downloader = downloader;
    // 在主线程
    this.mainThreadHandler = mainThreadHandler;
    this.cache = cache;
    this.stats = stats;
    this.batch = new ArrayList(4);
    this.airplaneMode = Utils.isAirplaneModeOn(this.context);
    this.scansNetworkChanges = hasPermission(context, Manifest.permission.ACCESS_NETWORK_STATE);
    this.receiver = new NetworkBroadcastReceiver(this);
    receiver.register();
 }

最后,也是最重要的一点,那就是 Picasso 实例的创建,先看下代码吧

Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
      RequestTransformer requestTransformer, List extraRequestHandlers, Stats stats,
      Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {
    this.context = context;
    this.dispatcher = dispatcher;
    this.cache = cache;
    this.listener = listener;
    this.requestTransformer = requestTransformer;
    this.defaultBitmapConfig = defaultBitmapConfig;

    int builtInHandlers = 7; // Adjust this as internal handlers are added or removed.
    int extraCount = (extraRequestHandlers != null ? extraRequestHandlers.size() : 0);
    List allRequestHandlers =
        new ArrayList(builtInHandlers + extraCount);

    // ResourceRequestHandler needs to be the first in the list to avoid
    // forcing other RequestHandlers to perform null checks on request.uri
    // to cover the (request.resourceId != 0) case.
    allRequestHandlers.add(new ResourceRequestHandler(context));
    if (extraRequestHandlers != null) {
      allRequestHandlers.addAll(extraRequestHandlers);
    }
    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);

    this.stats = stats;
    this.targetToAction = new WeakHashMap();
    this.targetToDeferredRequestCreator = new WeakHashMap();
    this.indicatorsEnabled = indicatorsEnabled;
    this.loggingEnabled = loggingEnabled;
    this.referenceQueue = new ReferenceQueue();
    this.cleanupThread = new CleanupThread(referenceQueue, HANDLER);
    this.cleanupThread.start();
  }

除了对一些对象赋值外,重要的一点就是创建和添加 RequestHandler,代码主要在 19 ~ 29 行,比如文件、网络、资源等处理器。至此,Picasso 实例也就创建完毕了,那么接下来看 load() 方法是如何实现的。

Picasso.load() 方法解析

load() 有多个重载方法,可以传入 resourceId、string、file、uri,但是最后都是返回 RequestCreator 实例,接下来就来看该方法如何实现?代码如下:

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

很明显地看出,调用 load() 方法后返回的是 RequestCreator 实例,那么来看下 RequestCreator 构造方法是咋样的,代码如下:

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

创建 Request.Builder 对象,并将需要加载图片信息封装到该对象里,采用建造者模式。对于一些操作比如 rotate、centerCrop、centerInside、resize 等,其实只是修改其状态,真正执行操作是方法 into,该方法是核心方法,以下重点分析。

into() 方法解析

into() 有 5 个重载方法,每个方法实现的主要功能基本相同,但是稍微还是有点不一样的。以常用 into(ImageView target) 这个方法为例子来讲解下内部是如何实现的?由于个人比较喜欢画流程图来理清方法之间调用关系,于是就有下面的流程图:

这里写图片描述
结合流程图再来看源码应该会比较好理解,代码如下:

public void into(ImageView target) {
    into(target, null);
}

该方法所持有的 ImageView 实例是一个弱引用,内存不足情况下可以自动被垃圾回收器回收。很明显,该方法调用重载方法 into(target, null),代码如下:

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

参数 Callback 是一个强引用,将会阻止 Activity 或者 Fragment 被回收,从而导致内存泄漏。如果使用该方法,在释放资源时最好调用 Picasso 类 cancelRequest(android.widget.ImageView) 方法取消请求,以免造成内存泄漏。从源码可以清晰地看出该方法实现的主要功能:

检查当前线程是否为主线程;如果是主线程,则继续执行;否则抛出异常。

判断目标组件 ImageView 是否为 null;如果不为 null,则继续执行;否则抛出异常。

检查发送请求是否包含要加载图片 uri 或 resourceId;如果没有,则调用Picasso 类 cancelRequest(android.widget.ImageView) 方法取消请求(后面讲解该方法),并检查是否设置默认图片;否则继续执行。

检查是否调用方法 fit(),即 deferred 是否为 true,true 表示调用,false 表示调用 unfit() 方法;调用该方法意味着延迟加载图片,并且不能与方法 resize() 同时使用。

为加载图片创建请求。

为每个请求创建 key,主要是为了方便存储。

根据缓存策略判断是否从内存缓存读取。

是否设置默认图片。

创建对应的 Action 实例,在这里是 ImageViewAction。

入队和提交 action。

以上是总体概括,接下来逐一看具体实现。

那么先来看下 cancelRequest(android.widget.ImageView) 具体实现,代码如下:

public void cancelRequest(ImageView view) {
    cancelExistingRequest(view);
}

只有一行代码,调用 cancelExistingRequest(view) 方法,看下具体实现:

private void cancelExistingRequest(Object target) {
    checkMain();
    Action action = targetToAction.remove(target);
    if (action != null) {
      action.cancel();
      dispatcher.dispatchCancel(action);
    }
    if (target instanceof ImageView) {
      ImageView targetImageView = (ImageView) target;
      DeferredRequestCreator deferredRequestCreator =
          targetToDeferredRequestCreator.remove(targetImageView);
      if (deferredRequestCreator != null) {
        deferredRequestCreator.cancel();
      }
    }
 }

按照惯例,先列出该方法实现的主要功能:

检查当前线程是否为主线程;如果是主线程,则继续执行;否则抛出异常。

通过 key 从 targetToAction 移除对应 action。

如果 action 不为 null,执行一些取消操作,相当于释放资源。

如果目标组件是 ImageView,则检查是否调用 fit() 方法,是的话就执行一些取消操作。

接着来看下取消操作方法 dispatcher.dispatchCancel(action) 具体实现:

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

很明显是通过 Handler 消息机制来处理的。即 Dispatcher 类中 DispatcherHandler 发送消息 REQUEST_CANCEL,并在其回调方法 handleMessage(final Message msg) 实现具体逻辑,注意是在子线程执行的。

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

        default:
            Picasso.HANDLER.post(new Runnable() {
                @Override public void run() {
                    throw new AssertionError("Unknown handler message received: " + msg.what);
                }
            });
        }
    }
}

接着来看下 dispatcher.performCancel(action) 具体实现:

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);
        if (action.getPicasso().loggingEnabled) {
          log(OWNER_DISPATCHER, VERB_CANCELED, action.getRequest().logId());
        }
      }
    }

    if (pausedTags.contains(action.getTag())) {
      pausedActions.remove(action.getTarget());
      if (action.getPicasso().loggingEnabled) {
        log(OWNER_DISPATCHER, VERB_CANCELED, action.getRequest().logId(),
            "because paused request got canceled");
      }
    }

    Action remove = failedActions.remove(action.getTarget());
    if (remove != null && remove.getPicasso().loggingEnabled) {
      log(OWNER_DISPATCHER, VERB_CANCELED, remove.getRequest().logId(), "from replaying");
    }
}

从以上代码很清晰地看出该方法主要做一些取消、移除操作,即释放资源。以上就是 cancelExistingRequest(view) 内部实现解析。

回到 into(target, null) 内部实现,看下创建请求 createRequest(started) 具体实现:

private Request createRequest(long started) {
    int id = nextId.getAndIncrement();

    Request request = data.build();
    request.id = id;
    request.started = started;

    boolean loggingEnabled = picasso.loggingEnabled;
    if (loggingEnabled) {
      log(OWNER_MAIN, VERB_CREATED, request.plainId(), request.toString());
    }

    Request transformed = picasso.transformRequest(request);
    if (transformed != request) {
      // If the request was changed, copy over the id and timestamp from the original.
      transformed.id = id;
      transformed.started = started;

      if (loggingEnabled) {
        log(OWNER_MAIN, VERB_CHANGED, transformed.logId(), "into " + transformed);
      }
    }

    return transformed;
}

其实实现逻辑很简单,对要加载图片的具体信息封装成 Request;通过 Request 创建对应 key,那么来看下是如何创建的,即 createKey(request) 方法具体实现:

static String createKey(Request data) {
    String result = createKey(data, MAIN_THREAD_KEY_BUILDER);
    MAIN_THREAD_KEY_BUILDER.setLength(0);
    return result;
}

逻辑很简单,调用方法 createKey(data, MAIN_THREAD_KEY_BUILDER),看下具体实现:

static String createKey(Request data, StringBuilder builder) {
    if (data.stableKey != null) {
      builder.ensureCapacity(data.stableKey.length() + KEY_PADDING);
      builder.append(data.stableKey);
    } else if (data.uri != null) {
      String path = data.uri.toString();
      builder.ensureCapacity(path.length() + KEY_PADDING);
      builder.append(path);
    } else {
      builder.ensureCapacity(KEY_PADDING);
      builder.append(data.resourceId);
    }
    builder.append(KEY_SEPARATOR);

    if (data.rotationDegrees != 0) {
      builder.append("rotation:").append(data.rotationDegrees);
      if (data.hasRotationPivot) {
        builder.append('@').append(data.rotationPivotX).append('x').append(data.rotationPivotY);
      }
      builder.append(KEY_SEPARATOR);
    }
    if (data.hasSize()) {
      builder.append("resize:").append(data.targetWidth).append('x').append(data.targetHeight);
      builder.append(KEY_SEPARATOR);
    }
    if (data.centerCrop) {
      builder.append("centerCrop").append(KEY_SEPARATOR);
    } else if (data.centerInside) {
      builder.append("centerInside").append(KEY_SEPARATOR);
    }

    if (data.transformations != null) {
      //noinspection ForLoopReplaceableByForEach
      for (int i = 0, count = data.transformations.size(); i < count; i++) {
        builder.append(data.transformations.get(i).key());
        builder.append(KEY_SEPARATOR);
      }
    }

    return builder.toString();
}

很简单,根据 Request 设置的属性拼接为字符串,作为最终的 key 并返回。

当我们调用不同 into() 方法时,Picasso 就会实例化不同的 Action,而这里我们是以 into(ImageView) 为例子,因此会实例化 ImageViewAction,在 ImageView 有回调方法,供我们使用,后续会看到。一切都准备就绪,那么就可以入队和提交 action。那么是如何实现的呢?来看下具体实现:

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

通过 key 从 targetToAction 获取 action,不存在的话就执行检查操作并将 action 插入到 targetToAction,最后调用 submit(action),具体实现如下:

void submit(Action action) {
    dispatcher.dispatchSubmit(action);
}

最后还是回到 Dispatcher,通过 DispatcherHandler 消息机制发送消息 REQUEST_SUBMIT,并在其回调方法 并在其回调方法 handleMessage(final Message msg) 实现具体逻辑,注意是在子线程执行的。

@Override 
public void handleMessage(final Message msg) {
    switch (msg.what) {
        case REQUEST_SUBMIT: {
          Action action = (Action) msg.obj;
          dispatcher.performSubmit(action);
          break;
        }

        default:
            Picasso.HANDLER.post(new Runnable() {
                @Override public void run() {
                    throw new AssertionError("Unknown handler message received: " + msg.what);
                }
            });
        }
    }
}

接着来看下 dispatcher.performSubmit(action) 具体实现:

void performSubmit(Action action) {
    performSubmit(action, true);
}

只有一行代码,调用方法 performSubmit(action, true),具体实现如下:

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

简要概括下该方法实现的主要功能:

检查标记 tag 请求是否被取消,如果被取消,则将对应 action 插入到 pausedActions 并退出程序;否则继续执行。

通过 key 从 hunterMap 获取相应 BitmapHunter 并判断其是否为 null,如果不为 null,则调用其方法 attach(Action) 并结束退出程序;否则继续执行。简要说明下,BitmapHunter 实现接口 Runnable,意味着开启线程在后台执行任务。

检查线程池是否停止工作。

创建 BitmapHunter 实例,即调用方法 forRequest(action.getPicasso(), this, cache, stats, action)

将 hunter 提交到线程池并执行,即调用 service.submit(hunter)

看下 forRequest() 方法具体实现:

static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats,
      Action action) {
    Request request = action.getRequest();
    List requestHandlers = picasso.getRequestHandlers();

    // Index-based loop to avoid allocating an iterator.
    //noinspection ForLoopReplaceableByForEach
    for (int i = 0, count = requestHandlers.size(); i < count; i++) {
      RequestHandler requestHandler = requestHandlers.get(i);
      if (requestHandler.canHandleRequest(request)) {
        return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);
      }
    }

    return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);
}

通过调用 RequestHandler 中 canHandleRequest(Request) 方法逐一检查对应 RequestHandler,并创建 BitmapHunter 对象返回。然后将 hunter 提交到线程池并执行。submit(Runnable) 是接口 ExecutorService 里的方法,PicassoExecutorService 实现接口 ExecutorService,因此 submit(Runnable) 方法的实现逻辑在 PicassoExecutorService 类里面,具体实现如下:

@Override
  public Future submit(Runnable task) {
    PicassoFutureTask ftask = new PicassoFutureTask((BitmapHunter) task);
    execute(ftask);
    return ftask;
}

execute(ftask) 执行任务,由于 BitmapHunter 实现接口 Runnable,意味着开启线程在后台执行任务;而在该方法里会调用线程 start() 方法,意味着 BtimapHunter 中 run() 会被调用,真正执行任务逻辑在该方法里面实现,接下来的重点肯定是来研究该方法内部实现逻辑,在研究源代码之前,先来看该方法内部实现流程图:

这里写图片描述

同样结合流程图来解析 run() 内部实现,代码如下:

public void run() {
    try {
        updateThreadName(data);

        if (picasso.loggingEnabled) {
            log(OWNER_HUNTER, VERB_EXECUTING, getLogIdsForHunter(this));
        }

        result = hunt();

        if (result == null) {
            dispatcher.dispatchFailed(this);
        } else {
            dispatcher.dispatchComplete(this);
        }
    } catch (Downloader.ResponseException e) {
        if (!e.localCacheOnly || e.responseCode != 504) {
            exception = e;
        }
        dispatcher.dispatchFailed(this);
        } catch (NetworkRequestHandler.ContentLengthException e) {
            exception = e;
            dispatcher.dispatchRetry(this);
    } catch (IOException e) {
            exception = e;
            dispatcher.dispatchRetry(this);
    } catch (OutOfMemoryError e) {
            StringWriter writer = new StringWriter();
            stats.createSnapshot().dump(new PrintWriter(writer));
            exception = new RuntimeException(writer.toString(), e);
            dispatcher.dispatchFailed(this);
    } catch (Exception e) {
            exception = e;
            dispatcher.dispatchFailed(this);
    } finally {
            Thread.currentThread().setName(Utils.THREAD_IDLE_NAME);
    }
}

简要概括下该方法实现的主要功能:

更新线程名字。

返回 Bitmap 对象并赋值给 result。

判断返回结果 result 是否为 null,如果为 null,则调用 dispatcher.dispatchFailed(this);否则调用 dispatcher.dispatchComplete(this)

各种异常处理。

从源代码可以看出,核心的逻辑在方法 hunt(),那么它的内部实现又是怎么呢?具体实现如下:

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

    if (bitmap != null) {
      if (picasso.loggingEnabled) {
        log(OWNER_HUNTER, VERB_DECODED, data.logId());
      }
      stats.dispatchBitmapDecoded(bitmap);
      if (data.needsTransformation() || exifRotation != 0) {
        synchronized (DECODE_LOCK) {
          if (data.needsMatrixTransform() || exifRotation != 0) {
            bitmap = transformResult(data, bitmap, exifRotation);
            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;
}

代码实现确实很复杂,没事,先概括下其实现的主要小功能,再逐一解析:

根据缓存策略判断是否从内存缓存读取 bitmap,true 表示直接读取并返回 bitmap;否则继续执行。

根据不同请求资源调用相应 RequestHandler 中 load() 方法下载图片,这里以网络请求资源为例子来讲解,即 NetworkRequestHandler。

从下载返回结果 RequestHandler.Result 获取 bitmap,判断是否为 null 做出相应的处理。

重点来 NetworkRequestHandler 类 load() 方法具体实现:

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

第 3 行代码实现图片下载,即客户端发送网络请求,服务端对客户端的请求作出响应,客户端根据服务端返回的结果作出处理。那么是如何实现下载的呢?downloader 是 Downloader 实例,而 Downloader 是接口,无法实例化,需要在子类实例化,而该接口有两个实现类:OkHttpDownloader 和 UrlConnectionDownloader。至于调用哪个类 load() 方法,取决于当前 sdk 最低版本是否在 API 14及以上。这里以 OkHttpDownloader 类 load() 方法为例来讲解是如何实现下载的,代码如下:

@Override
public Response load(Uri uri, int networkPolicy) throws IOException {
    CacheControl cacheControl = null;
    if (networkPolicy != 0) {
      if (NetworkPolicy.isOfflineOnly(networkPolicy)) {
        cacheControl = CacheControl.FORCE_CACHE;
      } else {
        CacheControl.Builder builder = new CacheControl.Builder();
        if (!NetworkPolicy.shouldReadFromDiskCache(networkPolicy)) {
          builder.noCache();
        }
        if (!NetworkPolicy.shouldWriteToDiskCache(networkPolicy)) {
          builder.noStore();
        }
        cacheControl = builder.build();
      }
    }

    Request.Builder builder = new Request.Builder().url(uri.toString());
    if (cacheControl != null) {
      builder.cacheControl(cacheControl);
    }

    com.squareup.okhttp.Response response = client.newCall(builder.build()).execute();
    int responseCode = response.code();
    if (responseCode >= 300) {
      response.body().close();
      throw new ResponseException(responseCode + " " + response.message(), networkPolicy,
          responseCode);
    }

    boolean fromCache = response.cacheResponse() != null;

    ResponseBody responseBody = response.body();
    return new Response(responseBody.byteStream(), fromCache, responseBody.contentLength());
  }

第 3 - 17 行代码主要是设置缓存策略;第 24 行通过调用 OkHttp 库 API 实现图片下载任务,并返回响应结果 com.squareup.okhttp.Response;最后对响应结果作出相应处理并创建 Response 对象返回。

再回到 NetworkRequestHandler 类 load() 方法,从返回 Response 对象调用 getBitmap() 方法获取 Bitmap 对象并判断其是否为 null,如果不为 null,则将 bitmap 和 loadedfrom 传入 Result 构造器方法中,创建 Result 对象并返回;否则调用 getInputStream() 方法获取输入流,不为 null 的话则创建 Result 对象并返回。

回到 hunt() 方法,从返回结果 result 获取数据类型为 Bitmap 对象 bitmap,并判断其是否为 null 作出不同的处理。如果 bitmap 不为 null,则判断原先发送加载图片请求 Request 是否需要对图片进行转换处理,即裁剪、缩放、重置大小等;不需要的话直接返回 bitmap;需要的话做转换处理后再返回 bitmap;如果 bitmap 为 null,则从 result 获取输入流 InputStream,并对 is 进行解析转换成 Bitmap 类型,将获取到的结果返回。

hunt() 方法解析完了,回到 run() 方法。根据调用方法 hunt() 返回结果 result,类型为 Bitmap,判断其是否为 null 并作出相应的处理。那么就来看下不为 null 的情况下又做了什么操作,即第 14 行代码,具体实现如下:

void dispatchComplete(BitmapHunter hunter) {
    handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter));
}

很明显,又采用 Handler 消息机制处理,即 Dispatcher 类中 DispatcherHandler 发送消息 HUNTER_COMPLETE,其回调方法 handleMessage(final Message msg) 收到消息后并处理。说明一下,DispatcherHandler 所在的线程为子线程,即 DispatcherThread。

@Override 
public void handleMessage(final Message msg) {
    switch (msg.what) {
        case HUNTER_COMPLETE: {
          BitmapHunter hunter = (BitmapHunter) msg.obj;
          dispatcher.performComplete(hunter);
          break;

        default:
            Picasso.HANDLER.post(new Runnable() {
                @Override public void run() {
                    throw new AssertionError("Unknown handler message received: " + msg.what);
                }
            });
        }
    }
}

在其回调方法中,核心代码是第 6 行,具体实现如下:

void performComplete(BitmapHunter hunter) {
    if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) {
      cache.set(hunter.getKey(), hunter.getResult());
    }
    hunterMap.remove(hunter.getKey());
    batch(hunter);
    if (hunter.getPicasso().loggingEnabled) {
      log(OWNER_DISPATCHER, VERB_BATCHED, getLogIdsForHunter(hunter), "for completion");
    }
}

根据发送请求设置的内存缓存策略判断是否将其结果写入到内存中,并通过 key 从 hunterMap 移除 hunter,最后再对 hunter 作出处理,即 第 6 行代码,具体实现如下:

private void batch(BitmapHunter hunter) {
    if (hunter.isCancelled()) {
      return;
    }
    batch.add(hunter);
    if (!handler.hasMessages(HUNTER_DELAY_NEXT_BATCH)) {
      handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH, BATCH_DELAY);
    }
 }

看第 7 行代码,同时采用 Handler 消息机制发送消息,跟上面一样。最终会转到 Dispatcher 类 performBatchComplete() 方法,具体实现如下:

void performBatchComplete() {
    List copy = new ArrayList(batch);
    batch.clear();
    mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));
    logBatch(copy);
}

核心代码是第 4 行,同样才 Handler 消息机制发送消息,但是此时与之上有所不一样,即 mainThreadHandler 所在线程是主线程,也就是说,此时将任务从子线程切换到主线程,以便可以进行 UI 更新操作,那么赶快来看下在主线程是如何实现的?代码如下:

@Override 
public void handleMessage(final Message msg) {
    switch (msg.what) {
        case HUNTER_BATCH_COMPLETE: 
          @SuppressWarnings("unchecked") List batch = (List) msg.obj;
          //noinspection ForLoopReplaceableByForEach
          for (int i = 0, n = batch.size(); i < n; i++) {
            BitmapHunter hunter = batch.get(i);
            hunter.picasso.complete(hunter);
          }
          break;

         default:
          throw new AssertionError("Unknown handler message received: " + msg.what);        
    }
}

根据获取到 BitmapHunter 列表大小依次调用 Picasso 中 complete(BitmapHunter) 方法,那么又是怎样实现的呢,代码如下:

void complete(BitmapHunter hunter) {
    Action single = hunter.getAction();
    List joined = hunter.getActions();

    boolean hasMultiple = joined != null && !joined.isEmpty();
    boolean shouldDeliver = single != null || hasMultiple;

    if (!shouldDeliver) {
      return;
    }

    Uri uri = hunter.getData().uri;
    Exception exception = hunter.getException();
    Bitmap result = hunter.getResult();
    LoadedFrom from = hunter.getLoadedFrom();

    if (single != null) {
      deliverAction(result, from, single);
    }

    if (hasMultiple) {
      //noinspection ForLoopReplaceableByForEach
      for (int i = 0, n = joined.size(); i < n; i++) {
        Action join = joined.get(i);
        deliverAction(result, from, join);
      }
    }

    if (listener != null && exception != null) {
      listener.onImageLoadFailed(this, uri, exception);
    }
}

从 hunter 获取单个 action、合并 actions 以及其它信息,比如 uri、exception 等。如果获取到的 action 不为空,则派发 action,即第 18 行代码,具体实现如下:

private void deliverAction(Bitmap result, LoadedFrom from, Action action) {
    if (action.isCancelled()) {
      return;
    }
    if (!action.willReplay()) {
      targetToAction.remove(action.getTarget());
    }
    if (result != null) {
      if (from == null) {
        throw new AssertionError("LoadedFrom cannot be null.");
      }
      action.complete(result, from);
      if (loggingEnabled) {
        log(OWNER_MAIN, VERB_COMPLETED, action.request.logId(), "from " + from);
      }
    } else {
      action.error();
      if (loggingEnabled) {
        log(OWNER_MAIN, VERB_ERRORED, action.request.logId());
      }
    }
}

从以上代码可以清晰地看出,根据 result 是否为空分别调用 Action 的回调方法,即 complete()error()。先来看下 complete() 具体实现:

@Override
public void complete(Bitmap result, Picasso.LoadedFrom from) {
    if (result == null) {
      throw new AssertionError(
          String.format("Attempted to complete action with no result!\n%s", this));
    }

    ImageView target = this.target.get();
    if (target == null) {
      return;
    }

    Context context = picasso.context;
    boolean indicatorsEnabled = picasso.indicatorsEnabled;
    PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);

    if (callback != null) {
      callback.onSuccess();
    }
}

逻辑很简单,前部分主要是做一些检查操作,真正核心代码是第 15 行代码,即将下载获取到的图片渲染到 UI 上,意味着成功地加载一张图片。那么 error() 方法又做了什么操作呢?具体实现如下:

public void error() {
    ImageView target = this.target.get();
    if (target == null) {
      return;
    }
    if (errorResId != 0) {
      target.setImageResource(errorResId);
    } else if (errorDrawable != null) {
      target.setImageDrawable(errorDrawable);
    }

    if (callback != null) {
      callback.onError();
    }
}

逻辑也很简单,如果我们有设置错误时显示图片的话,该方法就将错误时显示图片渲染出来。

那么这是单个 action 的处理逻辑,如果有合并 actions 的,执行的逻辑也一样,即对其进行遍历,获取单个 action,再派发 action,实现的代码在 complete(BitmapHunter) 方法第 21 - 27 行。

好吧, 使用 Picasso 开源框架成功加载一张图片的具体流程大概就这样了。由于自己能力水平有限,在讲解过程中难免有错误,如果您看到了,欢迎指正出来,大家一起学习,共同进步 !!!

相关TAG标签
上一篇:使用jQuery选择器的优势
下一篇:Android开发之定制ProgressBar
相关文章
图文推荐

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站