Picasso 用法及源码解析

来源:互联网 发布:php获取html内容 编辑:程序博客网 时间:2024/06/07 01:15

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

  • 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<String, BitmapHunter>();    this.failedActions = new WeakHashMap<Object, Action>();    this.pausedActions = new WeakHashMap<Object, Action>();    this.pausedTags = new HashSet<Object>();    // 注意该 handler 是在子线程的    this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this);    this.downloader = downloader;    // 在主线程    this.mainThreadHandler = mainThreadHandler;    this.cache = cache;    this.stats = stats;    this.batch = new ArrayList<BitmapHunter>(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<RequestHandler> 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<RequestHandler> allRequestHandlers =        new ArrayList<RequestHandler>(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<Object, Action>();    this.targetToDeferredRequestCreator = new WeakHashMap<ImageView, DeferredRequestCreator>();    this.indicatorsEnabled = indicatorsEnabled;    this.loggingEnabled = loggingEnabled;    this.referenceQueue = new ReferenceQueue<Object>();    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<RequestHandler> 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() 方法具体实现:

@Overridepublic 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() 方法为例来讲解是如何实现下载的,代码如下:

@Overridepublic 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<BitmapHunter> copy = new ArrayList<BitmapHunter>(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<BitmapHunter> batch = (List<BitmapHunter>) 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<Action> 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() 具体实现:

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

参考资料

http://skykai521.github.io/2016/02/25/Picasso%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 老婆性格太倔结婚一年想离婚怎么办 天正打图窗户线条太粗怎么办 孩子在幼儿园不敢跟老师说话怎么办 温州教育准考证号密码忘了怎么办 高等继续教育网打不开课程怎么办 安运继续教育的登录密码忘了怎么办 金蝶k3账套管理打不开了怎么办 仁和会计课堂app不能用怎么办 光大银行已经下卡了终审被拒怎么办 过了上诉期和申诉期该怎么办 北外大四学生要实习半年课程怎么办 电脑发给手机的文件过期了怎么办 农民给土地卖了30年后怎么办 家长发家长群作业太多老师怎么办 在考试中心补不了四级成绩怎么办 微信登录密码不记得了怎么办 欠农民工工资不给怎么办老板说没钱 国外期刊催问稿件不理睬怎么办 老公离不开老婆也离不开小三怎么办 出轨被老婆发现还和小三联系怎么办 老公出轨后回家老婆不想原谅怎么办 小三和原配打架都住院了怎么办 毕业太多年查不到学历认证怎么办 没有做税种核定开了票怎么办 在学信网上查不到学历信息怎么办 学信网手机号换了密码忘了怎么办 学信网手机号换了密码也忘了怎么办 学信网上学习形式是星号怎么办 新手机号已被注册微店买家怎么办 微信号被冻结了里面的钱怎么办 不懂公司产品却要接待老外怎么办 上菜时发现桌面摆不下新菜怎么办 超市买到过期产品商家不赔尝怎么办 皇帝成长计划2俘虏的士兵怎么办 晚上楼上有挪桌子的声音怎么办 金灶茶具出故障码e7怎么办 起亚k2灯泡掉进大灯总成怎么办 衣服上拆过线的针孔怎么办 驾考科目二坡道定点熄火怎么办 穿着超短裤感觉要漏屁股怎么办 台式电脑开机后无法进入系统怎么办