Picasso源代码分析:2、Picasso.java

在之前的文章《Picasso源代码分析:1、跟随代码的角度,当我们添加了一个任务,到底发生了什么?》中,我们通过我们最常使用的代码

Picasso.with(Context).load("http://www.baidu.com/img/bdlogo.png").into(imageview);

分析了这段代码是如何工作的。我们通过分析发现,该段代码依次创建了一个Picasso对象,一个ReqeustCreator对象,一个Request对象和一个ImageViewAction对象。要继续深入的理解Picasso的工作原理,我们需要首先来分析这些类到底是做什么实用的?

Picasso

Picasso是整个Picasso的入口,作者添加了如下的注释:

/**
 * Image downloading, transformation, and caching manager.
 * <p>
 * Use {@link #with(android.content.Context)} for the global singleton instance or construct your
 * own instance with {@link Builder}.
 */

简单的说,Picasso是一个图像(Image)的下载,处理和缓存的管理器。对比我们之前学习的Volley,在Volley中也有一个Volley.java的工具类,该类的代码位于com.android.volley.toolbox当中,而非com.android.volley,也就是说,在Volley当中,Volley类并不是其核心类,其维护所有队列以及线程的类是ReqeustQueue,Volley类是一个方便大家使用的对Volley的代码的封装而已。那么我们这时候要思考了,Picasso类对于Picasso而言,是核心的用于维护所有的队列即调度线程的类?还是同Volley.java一样,只是为了方便使用,对其代码的一个封装?

依次来分析Picasso的结构:

首先,Picasso定义了两个接口

Listener

/** Callbacks for Picasso events. */
public interface Listener {
  /**
   * Invoked when an image has failed to load. This is useful for reporting image failures to a
   * remote analytics service, for example.
   */
  void onImageLoadFailed(Picasso picasso, Uri uri, Exception exception);
}

该接口的用途是在图片加载失败的时候调用,注释上说,该接口会在分析失败原因的时候比较有用。

ReqeustTransformer

/**
 * A transformer that is called immediately before every request is submitted. This can be used to
 * modify any information about a request.
 * <p>
 * For example, if you use a CDN you can change the hostname for the image based on the current
 * location of the user in order to get faster download speeds.
 * <p>
 * <b>NOTE:</b> This is a beta feature. The API is subject to change in a backwards incompatible
 * way at any time.
 */
public interface RequestTransformer {
  /**
   * Transform a request before it is submitted to be processed.
   *
   * @return The original request or a new request to replace it. Must not be null.
   */
  Request transformRequest(Request request);

   /** A {@link RequestTransformer} which returns the original request. */
  RequestTransformer IDENTITY = new RequestTransformer() {
    @Override public Request transformRequest(Request request) {
      return request;
    }
  };
}

这个接口RequestTransformer这个接口我们比较熟悉一些了,该接口是用来做一些请求的预处理,比如,有两台图片服务器,a.happyhls.me/b.happyhls.me,本来所有的访问地址都是a.happyhls.me的,但是要在切换到b.happyhls.me,那么我们就可以直接通过该接口进行处理。当然Reqeust还包含了很多其他的信息,我们后面会慢慢看到。只要是Request的相关信息,我们都可以通过这个接口进行修改。

在ReqeustTransformer当中,还定义了一个默认的实现,INDETITY,该接口实际上是直接返回未修改的Reqeust。

Priority

public enum Priority {
  LOW,
  NORMAL,
  HIGH
}

Picasso中一个定了3中优先级:LOW、NORMAL、HIGH

HANDLER

还定义了一个静态变量HANDLER,其定义如下:

static final Handler HANDLER = new Handler(Looper.getMainLooper()) {
  @Override public void handleMessage(Message msg) {
    switch (msg.what) {
      case HUNTER_BATCH_COMPLETE: {
          //此处应该是和Volley一样,打包处理的图形库,因为这些图片可能地址以及大小都是一样的所有打包在一起进行处理。
        @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;
      }
      case REQUEST_GCED: {
          //此处根据意思应该是请求被GC掉了
        Action action = (Action) msg.obj;
        if (action.getPicasso().loggingEnabled) {
          log(OWNER_MAIN, VERB_CANCELED, action.request.logId(), "target got garbage collected");
        }
          //那么就取消相应的处理已经被GC对应的请求
        action.picasso.cancelExistingRequest(action.getTarget());
        break;
      }
      case REQUEST_BATCH_RESUME:
          //请求被恢复了,还不太懂其业务逻辑。
        @SuppressWarnings("unchecked") List<Action> batch = (List<Action>) msg.obj;
        for (int i = 0, n = batch.size(); i < n; i++) {
          Action action = batch.get(i);
          action.picasso.resumeAction(action);
        }
        break;
      default:
        throw new AssertionError("Unknown handler message received: " + msg.what);
    }
  }
};

我们需要明确以下几点:

  • 该handler即HANDLER是工作在主线程(Looper,getMainLooper())之上的。换句说,其中所有的Message的处理过程都是在主线程上,因此我们不能在其中执行消耗时间过长的任务。
  • 其中更定义了3个消息,还不清楚该HANDLER是谁在给他发送消息,但根据字面上的意思,我们可以如下理解:
    • HUNTER_BATCH_COMPLETE:
      • BATCH是打包的意思,按照字面的理解是,当前打包的任务已经完成了,所以在主线程上依次分发结果。我们看到,在代码中,首先获取了List<BitmapHunter> batch,然后依次对batch中每一个BitmapHunter调用hunter.picasso.complete(hunter)方法。
        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);
          }
        }

        complete(BitmapHunter hunter)方法主要做了以下的事情:

        1. 获取通过getAction()和getActions()获取对应的action,当前还不清楚这分别代表着什么
        2. 然后判断是否需要分发结果(shouldDeliver),是否有多个结果等待分发
        3. 获取result并通过deliverAction(result, from, single);分发结果
        4. 如果有多个结果需要分发,则实例获取对应的Action,并通过deliverAction(result, from, single);进行结果分发
        5. 如果出错,则回调出错的类的对应方法listener.onImageLoadFailed(this, uri, exception);。
      • 继续跟着代码往下分析,代码是调用的deliverAction(Bitmap result, LoadedFrom from, Action action)方法进行结果分发,那这个方法到底有做了什么工作呢?
        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());
            }
          }
        }

        可以发现,这里面和Volley也有一些相同的地方:首先都会判断对应的Action(Volley是对应的Request)是否已经取消,然后再判断结果是否正确,最后调用action对应的complete()方法,我们先分析Picasso,后面再去具体看看Action.complete()方法做了哪些工作

    • REQUEST_GCED
      • 请求被GC掉了,但不清楚两个问题:
        • 为什么被GC掉了?难道请求不应该是强引用吗?
        • 如何判断被GC掉,并且给主线程发送消息呢?   -> 此处可参考CleanupThread
      • 当接收到该类型的Message的时候,处理的方法比较简单,就是获取对应的Action,然后如果取消该Action对应的请求即可。
        private void cancelExistingRequest(Object target) {
          //检查是否工作在主线程
          checkMain();
          //获取target对应的Action,并从列表中删除
          Action action = targetToAction.remove(target);
          //如果action存在,那么就执行取消操作,并调用dispatcher取消对应的action
          if (action != null) {
            //调用action的cancel函数,实际上是设置cancel标志
            action.cancel();
            //实际上是通过dispatcher绑定的handler发送到相应的线程上去处理。
            dispatcher.dispatchCancel(action);
          }
          //如果target是一个ImageView的话,取消那些之前添加的,需要延时处理的请求。
          if (target instanceof ImageView) {
            ImageView targetImageView = (ImageView) target;
            DeferredRequestCreator deferredRequestCreator =
                targetToDeferredRequestCreator.remove(targetImageView);
            if (deferredRequestCreator != null) {
              deferredRequestCreator.cancel();
            }
          }
        }

        代码以上,主要做了以下的工作:检查是否工作在主线程上,然后从toDo队列当中对应对应的action,再然后通过调用dispatcher.dispatcherCancel(action)来取消对应的任务。

    • REQUEST_BATCH_RESUME
      • 此处应该是打包的请求重新恢复执行,所以要求在主线程上依次恢复其对应的Action。代码如下:
        void resumeAction(Action action) {
          Bitmap bitmap = null;
          if (!action.skipCache) {
            bitmap = quickMemoryCacheCheck(action.getKey());
          }
        
          if (bitmap != null) {
            // Resumed action is cached, complete immediately.
            deliverAction(bitmap, MEMORY, action);
            if (loggingEnabled) {
              log(OWNER_MAIN, VERB_COMPLETED, action.request.logId(), "from " + MEMORY);
            }
          } else {
            // Re-submit the action to the executor.
            enqueueAndSubmit(action);
            if (loggingEnabled) {
              log(OWNER_MAIN, VERB_RESUMED, action.request.logId());
            }
          }
        }

        其中的工作依次是:

        • 检查是否可以从Memory Cache中获取数据,如果可以则尝试读取
        • 如果成功,则调用deliverAction(bitmap, MEMORY, action)分发结果。
        • 如果不成功,则将action加入队列并提交。

到这里,我们就大体明白了工作在主线程上的HANDLER到底要做哪些事情?主要就是,处理HUNTER_BATCH_COMPLETE操作完成;处理REQUEST_GCED事件;处理REQUEST_BATCH_RESUME事件。发现了没有?HANDLER应该是主要处理那些BATCH的请求,也就是可以合并在一起进行处理的请求;那这样看来,如果Action是独立的,应该不从这里进行结果分发的,是不是呢?后面慢慢分析。

类成员变量:

依次来看看Picasso都定义了哪些类成员变量:

  static Picasso singleton = null;

  private final Listener listener; //前面提到过,Listener是出错的时候的问题回调,其中有onImageLoadFailed()方法。
  private final RequestTransformer requestTransformer; //这个则是前面定义的,用来预先处理请求使用。
  private final CleanupThread cleanupThread;  //这个是一个清理线程,具体做的工作后面慢慢分析。
  private final List<RequestHandler> requestHandlers;   //请求处理的ReqeustHandler集合,由于Picasso可以处理各种各样的请求,比如Uri,File,resourceId等等,因此各自需要不同的Handler去处理。

  final Context context;
  final Dispatcher dispatcher;   //任务分发的上下文,前面的文章有提到过,后面慢慢分析。
  final Cache cache;   //Cache
  final Stats stats;   //Stats
  final Map<Object, Action> targetToAction;  //这里保存的是Target对应的Action,Picasso中既可以处理ImageView,也可以根据我们的需要包装成Target,当结果返回时,调用Target对应的方法即可。
  final Map<ImageView, DeferredRequestCreator> targetToDeferredRequestCreator;  //给定的ImageView对应的延时执行的请求 DeferredRequestCreator。
  final ReferenceQueue<Object> referenceQueue;  //ReferenceQueue,这个还不清楚是做什么的。

  boolean indicatorsEnabled;   //还不清楚做什么的。
  volatile boolean loggingEnabled;  //是否允许Log

  boolean shutdown;  //停止Picasso服务标志。

构造函数:

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

    //Picasso内置了总共7种处理器
    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.
    // ResourceRequestHandler必须放于队列的队首,以避免其他的ReqeustHandler会检查reqeust.uri是否为null来覆盖掉reqeust.resourceId!=0
    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));
    //将所有的handler变为unmodifiedaleList
    requestHandlers = Collections.unmodifiableList(allRequestHandlers);

    this.stats = stats;
    //此处用了WeakHashMap来保存,避免OOM
    this.targetToAction = new WeakHashMap<Object, Action>();
    //此处用了WeakHashMap来保存,避免OOM
    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();
  }

如上是构造函数,比较简单,主要是变量的赋值,我觉得需要注意的有以下几点:

  • ReqeustHandler
    • 默认添加了7个ReqeustHandler,根据名字可以基本上明白其处理的请求类型。
    • 所有的添加完成之后,生成unmodifiableList。
  • 两个Map,targetToAction和targetToDeferredRequestCreator:
    • 两个Map都是WeakHashMap,WeakHashMap的特点是,当如果Map之外没有引用,而且遇到GC的时候,那么其中的元素就会被当作垃圾回收。所以我们后面要特别注意这两个map–targetToAction以及targetToDeferredRequestCreator的处理。

enqueueAndSubmit && submit 任务的提交

//将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.
    // 此处说明,比如同一个ImageView,已经更换了图片,所以要取消当前的请求,提交新的请求。(这在ListView中是进场的)
    cancelExistingRequest(target);
    targetToAction.put(target, action);
  }
  submit(action);
}

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

此处的代码作用是:将Action添加到队列当中,并且提交到dispatcher。我们可以发现,其具体的原理是

  1. 从Action中获取Target(ImageView也是一种Target)
  2. 然后将Target放入到targetToAction当中(targetToAction是WeakHashMap)
  3. 将Action提交到dispatcher当中

CleanupThread

private static class CleanupThread extends Thread {
  private final ReferenceQueue<?> referenceQueue;
  private final Handler handler;

  CleanupThread(ReferenceQueue<?> referenceQueue, Handler handler) {
    this.referenceQueue = referenceQueue;
    this.handler = handler;
    setDaemon(true);
    setName(THREAD_PREFIX + "refQueue");
  }

  @Override public void run() {
    Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND);
    while (true) {
      try {
        RequestWeakReference<?> remove = (RequestWeakReference<?>) referenceQueue.remove();
        handler.sendMessage(handler.obtainMessage(REQUEST_GCED, remove.action));
      } catch (InterruptedException e) {
        break;
      } catch (final Exception e) {
        handler.post(new Runnable() {
          @Override public void run() {
            throw new RuntimeException(e);
          }
        });
        break;
      }
    }
  }

  void shutdown() {
    interrupt();
  }
}

这是清理线程的代码,先提出几个问题:

  • 为什么需要这个清理线程?按照Volley的思路,不应该是在任务删除或者完成的时候清理吗?
  • 清理线程每次是从referenceQueue中取出一个RequestWeakReference,这是一个阻塞线程,清理线程会一直等待队列中的元素,当取到元素的时候,就将其发送给handler(从调用关系上可以看出,此HANDLER即运行在主线程上的HANDLER)进行处理,如本文前面所描述的。

所以大胆猜测,应该是在Action取消或者完成的时候,就会放入到RequestWeakReference当中,然后清理线程会在后台默默的等待,当发现有元素需要清理的时候,就会发送给主线程执行清理工作。

Picasso.Builder()

我们来看一下,我们默认调用的构造者,为我们初始化的Picasso是怎么样的?

/** Create the {@link Picasso} instance. 创建Picasso的实例 */
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, indicatorsEnabled, loggingEnabled);
}

其中:

  • downloader = Utils.createDefaultDownloader(context); 其代码如果存在suqare的OKHttp库的话,就使用OKHttp,否则则使用HttpURLConnection
  •  cache = new LruCache(context);
  • service = new PicassoExecutorService();
  • transformer = RequestTransformer.IDENTITY;
  • Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);

在Picasso.java的最后,还有一个枚举类

LoadedFrom

public enum LoadedFrom {
  MEMORY(Color.GREEN),
  DISK(Color.YELLOW),
  NETWORK(Color.RED);

  final int debugColor;

  private LoadedFrom(int debugColor) {
    this.debugColor = debugColor;
  }
}

用来表示加载的来源,是MEMORY?还是DISK?还是NETWORK?

About: happyhls


发表评论

电子邮件地址不会被公开。 必填项已用*标注